Commit 81428664 authored by alain's avatar alain 🐙
Browse files

settings for calibrated data / download feature

parent ea366bf0
export const appSettings = { export const appSettings = {
language: "nl", language: "nl",
panelBreakpoint: 800 panelBreakpoint: 700
} }
\ No newline at end of file
import luchtmeetnet from "./sources/luchtmeetnet" import luchtmeetnet from "./sources/luchtmeetnet"
import teom from "./sources/teom" import teom from "./sources/teom"
import holu from "./sources/holu" //import holu from "./sources/holu"
import holu from "./sources/holu-cal"
//import holu from "./sources/holu-rivm" //import holu from "./sources/holu-rivm"
//import sentinel from "./sources/sentinel" //import sentinel from "./sources/sentinel"
...@@ -27,6 +28,7 @@ export const dataGroups = [ ...@@ -27,6 +28,7 @@ export const dataGroups = [
id: "μg/m3", id: "μg/m3",
label: " μg/m3", label: " μg/m3",
range: [0, 150], range: [0, 150],
ticks: [0, 25, 50, 75, 100, 125, 150],
legend: { legend: {
0: { label: "Goed", color: colors.green }, 0: { label: "Goed", color: colors.green },
35: { label: "Matig", color: colors.yellow }, 35: { label: "Matig", color: colors.yellow },
...@@ -55,6 +57,7 @@ export const dataGroups = [ ...@@ -55,6 +57,7 @@ export const dataGroups = [
id: "μg/m3", id: "μg/m3",
label: " μg/m3", label: " μg/m3",
range: [0, 250], range: [0, 250],
ticks: [0, 50, 100, 150, 200, 250],
legend: { legend: {
0: { label: "Goed", color: colors.green }, 0: { label: "Goed", color: colors.green },
55: { label: "Matig", color: colors.yellow }, 55: { label: "Matig", color: colors.yellow },
...@@ -83,6 +86,7 @@ export const dataGroups = [ ...@@ -83,6 +86,7 @@ export const dataGroups = [
id: "μg/m3", id: "μg/m3",
label: " μg/m3", label: " μg/m3",
range: [0, 250], range: [0, 250],
ticks: [0, 50, 100, 150, 200, 250],
legend: { legend: {
0: { label: "Goed", color: colors.green }, 0: { label: "Goed", color: colors.green },
55: { label: "Matig", color: colors.yellow }, 55: { label: "Matig", color: colors.yellow },
...@@ -92,7 +96,7 @@ export const dataGroups = [ ...@@ -92,7 +96,7 @@ export const dataGroups = [
} }
} }
], ],
tooltip: "<p>Deze monitor meet de totale hoeveelheid stof. De Engelse benaming voor deze metingen is Total Suspended Particles ‘TSP”. Tientallen jaren geleden bestonden er normen voor deze metingen. Omdat een gedeelte van dit stof te groot is om in te ademen is deze wetgeving later omgezet in PM10 normen. Van deze TSP metingen is bekend dat het een relatie kan leggen met klachten over zichtbare stofoverlast.</p>", tooltip: "<p>Deze monitor meet de totale hoeveelheid stof. De Engelse benaming voor deze metingen is Total Suspended Particles ‘TSP”. Tientallen jaren geleden bestonden er normen voor deze metingen. Omdat een gedeelte van dit stof te groot is om in te ademen is deze wetgeving later omgezet in PM10 normen. Van deze TSP metingen is bekend dat het een relatie kan leggen met klachten over zichtbare stofoverlast.</p>",
more: "", more: "",
type: "StationLayer", type: "StationLayer",
sources: [ sources: [
...@@ -108,6 +112,7 @@ export const dataGroups = [ ...@@ -108,6 +112,7 @@ export const dataGroups = [
id: "μg/m3", id: "μg/m3",
label: " μg/m3", label: " μg/m3",
range: [0, 250], range: [0, 250],
ticks: [0, 50, 100, 150, 200, 250],
legend: { legend: {
0: { label: "Goed", color: colors.green }, 0: { label: "Goed", color: colors.green },
70: { label: "Matig", color: colors.yellow }, 70: { label: "Matig", color: colors.yellow },
...@@ -134,6 +139,7 @@ export const dataGroups = [ ...@@ -134,6 +139,7 @@ export const dataGroups = [
id: "μg/m3", id: "μg/m3",
label: " μg/m3", label: " μg/m3",
range: [0, 300], range: [0, 300],
ticks: [0, 50, 100, 150, 200, 250, 300],
legend: { legend: {
0: { label: "Goed", color: colors.green }, 0: { label: "Goed", color: colors.green },
100: { label: "Matig", color: colors.yellow }, 100: { label: "Matig", color: colors.yellow },
......
...@@ -81,7 +81,7 @@ export const mapItemSettings = { ...@@ -81,7 +81,7 @@ export const mapItemSettings = {
sizeMax: 200, sizeMax: 200,
elevationMaxMin: 120, elevationMaxMin: 120,
elevationMaxMax: 2000, elevationMaxMax: 2000,
colorOffline: "#888" colorOffline: "#333"
} }
export const lightSettings = { export const lightSettings = {
......
import { addMissingDataPoints, downsampleData } from "../../data-on-a-map-app/src/util/data"
export default {
name: "holu",
sides: 24,
angle: 0,
factor: 0.5,
daysToFetch: 90,
dataStart: "2019-06-25T00:00:00.000Z",
dataStreams: {
pm25: {
raw: {
minmax: { key: "pm25_raw_minmax", type: "area", active: false, color: false },
mean: { key: "pm25_raw_mean", type: "line", active: false, color: false }
},
cal: {
minmax: { key: "pm25_cal_minmax", type: "area", active: true, color: true },
mean: { key: "pm25_cal_mean", type: "line", active: true, color: true }
}
},
pm10: {
raw: {
minmax: { key: "pm10_raw_minmax", type: "area", active: false, color: false },
mean: { key: "pm10_raw_mean", type: "line", active: false, color: false }
},
cal: {
minmax: { key: "pm10_cal_minmax", type: "area", active: true, color: true },
mean: { key: "pm10_cal_mean", type: "line", active: true, color: true }
}
}
},
all: async function(parameter) {
try {
const allResponse = await fetch(`https://data.waag.org/api/getStations`)
const all = await allResponse.json()
// const latestRawResponse = await fetch(`https://data.waag.org/api/getAllSensors`)
// let latestRaw = await latestRawResponse.json()
const latestResponse = await fetch(`https://data.waag.org/api/holu/holukit/calibrated/recent`)
let latest = await latestResponse.json()
let offlineSensors = []
let unregisteredSensors = []
//let places = {}
const data = all.map(sensor => {
const sensorData = latest.find(o => +o.id === +sensor.id)
const sensorDataReturn = {
id: sensor.id.toString(),
name: "HOLU-sensor " + sensor.id.toString() + ": " + sensor.name,
source: "holu",
coordinates: [sensor.longitude, sensor.latitude],
mean: null,
timestamp: null,
dataAge: null
}
if(sensorData) {
const dataAgeInHours = ((new Date() - new Date(sensorData.time)) / 3600000) - 1
if(dataAgeInHours >= 48 || dataAgeInHours < 0) {
sensorDataReturn.name = sensorDataReturn.name + " (offline)"
} else if(dataAgeInHours < 8) {
sensorDataReturn.mean = sensorData[`${parameter}_mean`]
} else {
sensorDataReturn.name = sensorDataReturn.name + " (no recent data)"
}
sensorDataReturn.timestamp = sensorData.time
sensorDataReturn.dataAge = dataAgeInHours
} else {
sensorDataReturn.name = sensorDataReturn.name + " (offline)"
offlineSensors.push(sensor.id)
}
return sensorDataReturn
})
latest.forEach(sensor => {
if(!all.find(o => +o.id === +sensor.id)) {
unregisteredSensors.push(sensor.id)
}
})
console.log(`Offline Sensors (${offlineSensors.length}):`, offlineSensors.sort((a, b) => a - b).join(', '))
console.log(`Unregistered Sensors (${unregisteredSensors.length}):`, unregisteredSensors.sort((a, b) => a - b).join(', '))
// Object.keys(places).forEach(function(place) {
// places[place].amountOfSensors = places[place].allAverages.length
// places[place].averageOfAverages = places[place].allAverages.reduce((a, b) => a + b, 0) / places[place].allAverages.length
// places[place].averageOfPeaks = places[place].allPeaks.reduce((a, b) => a + b, 0) / places[place].allPeaks.length
// places[place].highestAverage = Math.max(...places[place].allAverages)
// places[place].highestPeak = Math.max(...places[place].allPeaks)
// })
// console.log(places)
return data
} catch (error) {
console.log(error)
}
},
stationMeta: function(station, parameter) {
const stationMeta = {
name: station.name,
description: (station.type === "extended" ? (
'<span>Dit is een extended HOLU-meetstation. Dit prototype meet PM2.5 en PM10 en ook NO2 en O3. Als onderdeel van de pilot wordt er onderzoek gedaan naar de kwaliteit van de data afkomstig van deze meetstations.<br /><a class="more" href="https://hollandseluchten.waag.org/holu-sensorkit/">Lees meer over de HOLU kit</a></span>'
) : (
'<span>Dit is een basic HOLU-meetstation. Dit prototype meet PM2.5 en PM10. Als onderdeel van de pilot wordt er onderzoek gedaan naar de kwaliteit van de data afkomstig van deze meetstations.<br /><a class="more" href="https://hollandseluchten.waag.org/holu-sensorkit/">Lees meer over de HOLU kit</a></span>'
)
)
}
return stationMeta
},
stationData: async function(station, parameter, start, end, granularity) {
try {
var prefix
if (parameter === "pm25") prefix = parameter
if (parameter === "pm10") prefix = parameter
if (parameter === "temperature") prefix = "t_out"
if (parameter === "humidity") prefix = "h_out"
const responseRaw = await fetch(`https://data.waag.org/api/getHourlyValuesSensor?sensor_id=${station.id}&start=${start}&end=${end}`)
const jsonRaw = await responseRaw.json()
let dataRaw = jsonRaw.map(m => {
return {
timestamp: new Date(m.time).getTime(),
mean: m[`${prefix}_mean`],
minmax: [m[`${prefix}_min`], m[`${prefix}_max`]]
}
})
const responseCal = await fetch(`https://data.waag.org/api/holu/holukit/calibrated/hourly?sensor_id=${station.id}&start=${start}&end=${end}`)
const jsonCal = await responseCal.json()
let dataCal = jsonCal.map(m => {
return {
timestamp: new Date(m.time).getTime(),
mean: m[`${prefix}_mean`],
fac: m[`${prefix}_fac`],
}
})
const dataStart = (dataRaw[0].timestamp < dataCal[0].timestamp ? dataRaw[0].timestamp : dataCal[0].timestamp)
const dataEnd = new Date(end).getTime()
dataRaw = addMissingDataPoints(dataRaw, dataStart, dataEnd, granularity)
dataCal = addMissingDataPoints(dataCal, dataStart, dataEnd, granularity)
let data = []
for (let i = 0; i < dataRaw.length; i++) {
const r = dataRaw[i]
const c = dataCal[i]
const o = {
timestamp: r.timestamp
}
o[`${prefix}_raw_mean`] = r.mean
o[`${prefix}_raw_minmax`] = r.minmax
o[`${prefix}_cal_mean`] = c.mean
if(r.mean && c.mean) {
o[`${prefix}_cal_minmax`] = [r.minmax[0] * c.fac, r.minmax[1] * c.fac]
}
data.push(o)
}
return { status: "success", data }
} catch (error) {
console.log(error)
return { status: "error" }
}
},
download: function(id, parameter, start, end) {
return `https://data.waag.org/api/holu/holukit/calibrated/hourly?sensor_id=${id}&start=${start}&end=${end}&format=csv`
}
}
\ No newline at end of file
...@@ -12,7 +12,7 @@ export default { ...@@ -12,7 +12,7 @@ export default {
factor: 0.5, factor: 0.5,
dataStart: "2019-06-25T00:00:00.000Z", dataStart: "2019-06-25T00:00:00.000Z",
all: async function getAllHolu(parameter) { all: async function(parameter) {
try { try {
const allResponse = await fetch(`https://data.waag.org/api/getStations`) const allResponse = await fetch(`https://data.waag.org/api/getStations`)
...@@ -91,7 +91,7 @@ export default { ...@@ -91,7 +91,7 @@ export default {
} }
}, },
detail: async function getDetailHolu(station, parameter, start, end) { stationData: async function(station, parameter, start, end) {
try { try {
const stationMeta = { const stationMeta = {
name: station.name, name: station.name,
......
import { getLastHourISO, getNowHourISO } from "../../data-on-a-map-app/src/util/time" import { getLastHourISO, getNowISO, subtractOneHourISO } from "../../data-on-a-map-app/src/util/time"
import { groupArrayOfObjectsBy, renameObjectKey, addMissingDataPoints } from "../../data-on-a-map-app/src/util/data" import { groupArrayOfObjectsBy, renameObjectKey, addMissingDataPoints } from "../../data-on-a-map-app/src/util/data"
const officialStations = { const officialStations = {
...@@ -82,11 +82,33 @@ export default { ...@@ -82,11 +82,33 @@ export default {
angle: 0, angle: 0,
factor: 1, factor: 1,
daysToFetch: 60, daysToFetch: 60,
dataStreams: {
pm25: {
single: {
mean: { key: "pm25_mean", type: "line", active: true, color: true }
}
},
pm10: {
single: {
mean: { key: "pm10_mean", type: "line", active: true, color: true }
}
},
no2: {
single: {
mean: { key: "no2_mean", type: "line", active: true, color: true }
}
},
o3: {
single: {
mean: { key: "o3_mean", type: "line", active: true, color: true }
}
}
},
all: async function (parameter) { all: async function (parameter) {
try { try {
const lastHourISO = getLastHourISO() const lastHourISO = getLastHourISO()
const nowHourISO = getNowHourISO() const nowHourISO = getNowISO()
const allResponse = await fetch(`https://data.waag.org/api/getOfficialMeasurement?formula=${parameter.toUpperCase()}&start=${lastHourISO}&end=${nowHourISO}&station_id=${officialStations[parameter].join("&station_id=")}`) const allResponse = await fetch(`https://data.waag.org/api/getOfficialMeasurement?formula=${parameter.toUpperCase()}&start=${lastHourISO}&end=${nowHourISO}&station_id=${officialStations[parameter].join("&station_id=")}`)
const all = await allResponse.json() const all = await allResponse.json()
...@@ -98,9 +120,15 @@ export default { ...@@ -98,9 +120,15 @@ export default {
renameObjectKey(station, "value", "mean") renameObjectKey(station, "value", "mean")
renameObjectKey(station, "station_number", "id") renameObjectKey(station, "station_number", "id")
// we subtract one hour because luchtmeetnet api gives hour avarages with 'to' time as timestamp (instead of 'from' time as all other apis)
station.timestamp = subtractOneHourISO(station.timestamp_measured)
delete station.timestamp_measured
station.source = "luchtmeetnet" station.source = "luchtmeetnet"
station.name = "Officieel station: " + station.name station.name = "Officieel station: " + station.name
station.coordinates = station.coordinates.reverse() station.coordinates = station.coordinates.reverse()
station.dataAge = (new Date() - new Date(station.timestamp)) / 3600000
return station return station
}) })
...@@ -128,18 +156,20 @@ export default { ...@@ -128,18 +156,20 @@ export default {
const groupedData = groupArrayOfObjectsBy(responseData, "formula") const groupedData = groupArrayOfObjectsBy(responseData, "formula")
const conformedData = groupedData[0].map(x => { const conformedData = groupedData[0].map(x => {
return { const o = {
timestamp: new Date(x.timestamp_measured).getTime(), timestamp: new Date(x.timestamp_measured).getTime()
value: x.value
} }
o[`${parameter}_mean`] = x.value
return o
}) })
const data = addMissingDataPoints(conformedData, granularity) const data = addMissingDataPoints(conformedData, granularity)
return data return { status: "success", data }
} catch (error) { } catch (error) {
console.log(error) console.log(error)
return "error" return { status: "error" }
} }
} }
} }
\ No newline at end of file
...@@ -6,6 +6,23 @@ export default { ...@@ -6,6 +6,23 @@ export default {
angle: 0, angle: 0,
factor: 0.85, factor: 0.85,
daysToFetch: 90, daysToFetch: 90,
dataStreams: {
pm25: {
single: {
mean: { key: "pm25_mean", type: "line", active: true, color: true }
}
},
pm10: {
single: {
mean: { key: "pm10_mean", type: "line", active: true, color: true }
}
},
ps: {
single: {
mean: { key: "ps_mean", type: "line", active: true, color: true }
}
}
},
all: async function(parameter) { all: async function(parameter) {
try { try {
...@@ -42,12 +59,14 @@ export default { ...@@ -42,12 +59,14 @@ export default {
type: sensor.sensor_type, type: sensor.sensor_type,
coordinates: sensor.coordinates, coordinates: sensor.coordinates,
mean: null, mean: null,
max: null timestamp: null,
dataAge: null
} }
if(sensorData) { if(sensorData) {
sensorDataReturn.mean = sensorData[`${parameter}_mean`] sensorDataReturn.mean = sensorData[`${parameter}_mean`]
sensorDataReturn.max = sensorData[`${parameter}_max`] sensorDataReturn.timestamp = sensorData.time
sensorDataReturn.dataAge = (new Date() - new Date(sensorData.time)) / 3600000
} else { } else {
sensorDataReturn.name = sensorDataReturn.name + " (geen data)" sensorDataReturn.name = sensorDataReturn.name + " (geen data)"
} }
...@@ -81,18 +100,18 @@ export default { ...@@ -81,18 +100,18 @@ export default {
const responseData = await response.json() const responseData = await response.json()
const conformedData = responseData.map(m => { const conformedData = responseData.map(m => {
return { m.timestamp = new Date(m.time).getTime()
timestamp: new Date(m.time).getTime(), delete m.time
value: m[`${parameter}_mean`],
} return m
}) })
const data = addMissingDataPoints(conformedData, granularity) const data = addMissingDataPoints(conformedData, granularity)
return data return { status: "success", data }
} catch (error) { } catch (error) {
console.log(error) console.log(error)
return "error" console.log(error)
} return { status: "error" } }
} }
} }
\ No newline at end of file
//body: "<p>Deze kaart visualiseert data afkomstig van de HOLU-sensorkits én die van officiële meetpunten van <a href='https://www.luchtmeetnet.nl/' target='_blank' rel='noopener noreferrer'>luchtmeetnet.nl</a>. Hollandse&nbsp;Luchten is een experimenteel project. In dit project wordt er onderzoek gedaan naar de kwaliteit van de data afkomstig van de HOLU-sensorkits en naar de beste manier waarop je deze data kunt inzetten.<br />Op dit moment worden de ruwe meetdata van de HOLU-sensorkits getoond. Binnenkort verschijnen ook de gekalibreerde sensorwaarden op de website. Deze waarden zijn o.a. gecorrigeerd voor het effect van hoge luchtvochtigheid. Alle data zijn indicatief en hebben geen juridische betekenis.</p><p><a class='more' href='https://hollandseluchten.waag.org/veelgestelde-vragen/over-luchtkwaliteit/'>meer weten</a></p>"
export const texts = { export const texts = {
about: { about: {
title: "over deze kaart", title: "over deze kaart",
body: "<p>Deze kaart toont data van HOLU-sensorkits én die van officiële meetpunten van <a href='https://www.luchtmeetnet.nl/' target='_blank' rel='noopener noreferrer'>luchtmeetnet.nl</a>. Hollandse&nbsp;Luchten is een experimenteel project waarin er onderzoek wordt gedaan naar de kwaliteit van sensorkit-data en hoe je deze het beste kunt inzetten.<br />Op dit moment worden nog <strong>ruwe meetdata</strong> van de HOLU-sensorkits getoond. Deze waarden zijn dus nog niet gecorrigeerd voor o.a. het effect van hoge luchtvochtigheid.<br /><em>Alle data zijn indicatief en hebben geen juridische betekenis.</em></p><p><a class='more' href='https://hollandseluchten.waag.org/veelgestelde-vragen/over-luchtkwaliteit/'>meer weten</a></p>" body: "<p>Deze kaart toont data van HOLU-sensorkits én die van officiële meetpunten van <a href='https://www.luchtmeetnet.nl/' target='_blank' rel='noopener noreferrer'>luchtmeetnet.nl</a>. Hollandse&nbsp;Luchten is een experimenteel project waarin er onderzoek wordt gedaan naar de kwaliteit van sensorkit-data en hoe je deze het beste kunt inzetten.<br /><em>Alle data zijn indicatief en hebben geen juridische betekenis.</em></p><p><a class='more' href='https://hollandseluchten.waag.org/veelgestelde-vragen/over-luchtkwaliteit/'>meer weten</a></p>"
}, },
pilotLocations: "Pilot-locaties", pilotLocations: "Pilot-locaties",
loading: "Bezig met laden...", loading: "Bezig met laden...",
loadingError: "Laden mislukt...", loadingError: "Laden mislukt...",
loadingRetry: "Probeer opnieuw", loadingRetry: "Probeer opnieuw",
nodata: "Geen data...", nodata: "geen data...",
lastMean: "laatste uurgemiddelde", lastMean: "uurgemiddelde",
lastPeak: "piekwaarde laatste uur", lastPeak: "piekwaarde laatste uur",
lastHour: "afgelopen uur",
hoursAgo: "uur geleden",
hour: "uur",
hourShort: "u",
mean: "gemiddelde", mean: "gemiddelde",
peak: "piekwaarde", peak: "piekwaarde",
dataStreams: {
raw: "ruwe data",
cal: "gekalibreerde data",
mean: "gemiddelde",
minmax: "min-max waarde",
},
downloadData: "download data", downloadData: "download data",
startDate: "startdatum", startDate: "startdatum",
endDate: "einddatum", endDate: "einddatum",
chartHeaderAddition: "(uurwaarden)", chartHeaderAddition: "(uurwaarden)",
timeSelectionHint: "↖ tijdselectie",
downloadCsv: "download csv", downloadCsv: "download csv",
downloadInfo: "<small><a class='more' href='https://hollandseluchten.waag.org/data-downloaden/' target='_blank' rel='noopener noreferrer'>meer info over data downloaden en de API</a></small>", downloadInfo: "<small><a class='more' href='https://hollandseluchten.waag.org/data-downloaden/' target='_blank' rel='noopener noreferrer'>meer info over data downloaden en de API</a></small>",
} }
data-on-a-map-app @ bd8510c1
Subproject commit bf8eb3bb403f3e61b41f2c5891b449c7dbe06e71 Subproject commit bd8510c15dbdd324f262ba97439c0d86b7c6fcfd
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment