Commit 74d9e8a7 authored by taco@waag.org's avatar taco@waag.org
Browse files
parents f14df515 7b81d76e
.DS_Store
backend/dump.rdb
# installeren
gem install sinatra
gem install json
gem install rest-client
gem install csv
# hollandse-luchten-data-app backend
# uitvoeren
ruby server.rb
## to install
`gem install sinatra`
# hollandse-luchten-data-app backend
voorbeeld calls:
http://localhost:4567/sensor/204/pm10?start=2019-08-25&end=2019-08-31 # geeft alle waardes tussen start en end voor type pm10 van device 204
`gem install json`
`gem install rest-client`
`gem install csv`
## to start
`brew services start redis`
(or `redis-server`)
`ruby server.rb`
## example calls
`http://localhost:4567/sensor/204/pm10?start=2019-08-25&end=2019-08-31`
-> gives values between start and end for parameter pm10 of device with id 204
`http://localhost:4567/sensor/204/pm10/csv?start=2019-08-25&end=2019-08-31`
http://localhost:4567/sensor/204/pm10/csv?start=2019-08-25&end=2019-08-31 # geeft een csv bestand met alle waardes tussen start en end voor type pm10 van device 204
-> gives values between start and end for parameter pm10 of device with id 204 in csv format
http://localhost:4567/sensor/pm25 # geeft de laatste waarde voor ieder device van type pm25
`http://localhost:4567/sensor/all`
http://localhost:4567/sensor/all # geeft de laatste waarde voor ieder device
-> gives last value of all devices for all paramters
`http://localhost:4567/sensor/pm25`
-> gives last value of all devices for paramter pm25
......@@ -5,6 +5,15 @@ require 'rest-client'
require 'json'
require 'redis'
require 'csv'
require 'rack/cors'
use Rack::Cors do
allow do
origins '*'
resource '/*', headers: :any, methods: :get
end
end
$base_url = "https://api-samenmeten.rivm.nl/v1.0"
$redis = Redis.new({:host => 'localhost', :port => 6379, :db => 2})
......
import luchtmeetnet from "./sources/luchtmeetnet"
import teom from "./sources/teom"
import holu from "./sources/holu-rivm"
//import sentinel from "./sources/sentinel"
const colors = {
green: "#61b53d",
yellow: "#f9be38",
orange: "#ef4d1f",
red: "#ea1b20",
purple: "#911f6a"
}
export const initialLayers = ["pm25"]
export const dataGroups = [
{
title: "data-selectie",
parameters: {
"pm25": {
id: "pm25",
label: "fijn stof (PM 2.5)",
unit: " μg/m3",
range: [0, 150],
legend: {
0: { label: "Goed", color: colors.green },
35: { label: "Matig", color: colors.yellow },
70: { label: "Onvoldoende", color: colors.orange },
105: { label: "Slecht", color: colors.red },
150: { label: "Zeer slecht", color: colors.purple },
},
formula: "PM25",
tooltip: "<p>PM2.5 is een verzamelnaam voor zwevende, inhaleerbare deeltjes met een maximale doorsnede van 0,0025 millimeter. Doordat PM2.5 nog kleiner is dan PM10 kunnen deze deeltjes dieper doordringen in de longen en zijn ze schadelijker voor de gezondheid.</p><p>De wettelijke norm is een jaargemiddelde van 25 (μg/m3).</p>",
more: "https://www.luchtmeetnet.nl/uitleg",
type: "StationLayer",
sources: [
luchtmeetnet,
holu
]
},
"pm10": {
id: "pm10",
label: "fijn stof (PM 10)",
unit: " μg/m3",
range: [0, 250],
legend: {
0: { label: "Goed", color: colors.green },
55: { label: "Matig", color: colors.yellow },
100: { label: "Onvoldoende", color: colors.orange },
165.5: { label: "Slecht", color: colors.red },
250: { label: "Zeer slecht", color: colors.purple },
},
formula: "PM10",
tooltip: "<p>PM10 is een verzamelnaam voor zwevende, inhaleerbare deeltjes met een maximale doorsnede van 0,01 milimeter.</p><p>De wettelijke norm is een jaargemiddelde van 40 (μg/m3). Daarnaast mag het daggemiddelde jaarlijks maximaal 35 keer hoger zijn dan 50 (μg/m3).</p>",
more: "https://www.luchtmeetnet.nl/uitleg",
type: "StationLayer",
sources: [
luchtmeetnet,
teom,
holu
]
},
"no2": {
id: "no2",
label: "stikstofdioxide (NO2)",
unit: " μg/m3",
range: [0, 250],
legend: {
0: { label: "Goed", color: colors.green },
70: { label: "Matig", color: colors.yellow },
140: { label: "Onvoldoende", color: colors.orange },
200: { label: "Slecht", color: colors.red },
250: { label: "Zeer slecht", color: colors.purple },
},
formula: "NO2",
tooltip: "<p>NO2 ontstaat uit een reactie tussen stikstofmonoxide en ozon. Het weer en de verkeersdrukte hebben grote invloed op de concentratie.</p><p>De wettelijke norm is een jaargemiddelde van 40 (μg/m3).<p>",
more: "https://www.luchtmeetnet.nl/uitleg",
type: "StationLayer",
sources: [
luchtmeetnet
]
},
"o3": {
id: "o3",
label: "ozon (O3)",
unit: " μg/m3",
range: [0, 300],
legend: {
0: { label: "Goed", color: colors.green },
100: { label: "Matig", color: colors.yellow },
200: { label: "Onvoldoende", color: colors.orange },
240: { label: "Slecht", color: colors.red },
300: { label: "Zeer slecht", color: colors.purple },
},
formula: "O3",
tooltip: "<p>Ozon (O3) wordt niet rechtstreeks uitgestoten, maar wordt gevormd uit stikstofoxiden, vluchtige organische stoffen en koolmonoxide. De concentratie ozon is vooral afhankelijk van het weer.</p><p>In heel Europa wordt de bevolking gewaarschuwd bij ozonconcentraties boven 180 (μg/m3). Een concentratie van 240 (μg/m3) is de Europese alarmdrempel.</p>",
more: "https://www.luchtmeetnet.nl/uitleg",
type: "StationLayer",
sources: [
luchtmeetnet
]
},
}
},
// {
// title: "satelietdata",
// parameters: {
// "pm25-s": {
// id: "pm25-s",
// label: "fijn stof (PM 2.5)",
// unit: " μg/m3",
// range: [0, 150],
// legend: {
// 0: { label: "Goed", color: colors.green },
// 35: { label: "Matig", color: colors.yellow },
// 70: { label: "Onvoldoende", color: colors.orange },
// 105: { label: "Slecht", color: colors.red },
// 150: { label: "Zeer slecht", color: colors.purple },
// },
// formula: "PM25",
// tooltip: "",
// more: "https://www.luchtmeetnet.nl/uitleg",
// type: "HeatmapLayer",
// sources: [
// sentinel
// ]
// }
// }
// }
// {
// title: "weerdata",
// parameters: {
// "temperature": {
// id: "temperature",
// label: "temperatuur",
// unit: "º",
// range: [0, 60],
// legend: {
// 0: { label: null, color: colors.green },
// 60: { label: null, color: colors.yellow },
// },
// tooltip: "",
// type: "StationLayer",
// sources: [ holu ]
// },
// "humidity": {
// id: "humidity",
// label: "luchtvochtigheid",
// unit: "%",
// range: [0, 100],
// legend: {
// 0: { label: null, color: colors.green },
// 100: { label: null, color: colors.yellow },
// },
// tooltip: "",
// type: "StationLayer",
// sources: [ holu ]
// }
// }
// }
]
import luchtmeetnet from "./sources/luchtmeetnet"
import teom from "./sources/teom"
import holu from "./sources/holu"
export const initialLayers = ["pm25"]
export const dataGroups = [
{
title: "luchtkwaliteitsdata",
parameters: {
"pm25": {
id: "pm25",
label: "fijn stof (PM 2.5)",
unit: " μg/m3",
range: [0, 150],
legend: {
0: { label: "Goed", color: "#61b53d" },
35: { label: "Matig", color: "#f9be38" },
70: { label: "Onvoldoende", color: "#ef4d1f" },
105: { label: "Slecht", color: "#ea1b20" },
150: { label: "Zeer slecht", color: "#911f6a" },
},
formula: "PM25",
tooltip: "PM2.5 is een verzamelnaam voor zwevende, inhaleerbare deeltjes met een maximale doorsnede van 0,0025 millimeter. De wettelijke norm is een jaargemiddelde van 25 (μg/m3). Doordat PM2.5 nog kleiner is dan PM10 kunnen deze deeltjes dieper doordringen in de longen en zijn ze schadelijker voor de gezondheid.",
more: "https://www.luchtmeetnet.nl/uitleg",
type: "StationLayer",
sources: [ luchtmeetnet, holu ]
},
"pm10": {
id: "pm10",
label: "fijn stof (PM 10)",
unit: " μg/m3",
range: [0, 250],
legend: {
0: { label: "Goed", color: "#61b53d" },
55: { label: "Matig", color: "#f9be38" },
100: { label: "Onvoldoende", color: "#ef4d1f" },
165.5: { label: "Slecht", color: "#ea1b20" },
250: { label: "Zeer slecht", color: "#911f6a" },
},
formula: "PM10",
tooltip: "PM10 is een verzamelnaam voor zwevende, inhaleerbare deeltjes met een maximale doorsnede van 0,01 milimeter. De wettelijke norm is een jaargemiddelde van 40 (μg/m3). Daarnaast mag het daggemiddelde jaarlijks maximaal 35 keer hoger zijn dan 50 (μg/m3).",
more: "https://www.luchtmeetnet.nl/uitleg",
type: "StationLayer",
sources: [ luchtmeetnet, teom, holu ]
},
"no2": {
id: "no2",
label: "stikstofdioxide (NO2)",
unit: " μg/m3",
range: [0, 250],
legend: {
0: { label: "Goed", color: "#61b53d" },
70: { label: "Matig", color: "#f9be38" },
140: { label: "Onvoldoende", color: "#ef4d1f" },
200: { label: "Slecht", color: "#ea1b20" },
250: { label: "Zeer slecht", color: "#911f6a" },
},
formula: "NO2",
tooltip: "NO2 ontstaat uit een reactie tussen stikstofmonoxide en ozon. Het weer en de verkeersdrukte hebben grote invloed op de concentratie. De wettelijke norm is een jaargemiddelde van 40 (μg/m3).",
more: "https://www.luchtmeetnet.nl/uitleg",
type: "StationLayer",
sources: [ luchtmeetnet ]
},
"o3": {
id: "o3",
label: "ozon (O3)",
unit: " μg/m3",
range: [0, 300],
legend: {
0: { label: "Goed", color: "#61b53d" },
100: { label: "Matig", color: "#f9be38" },
200: { label: "Onvoldoende", color: "#ef4d1f" },
240: { label: "Slecht", color: "#ea1b20" },
300: { label: "Zeer slecht", color: "#911f6a" },
},
formula: "O3",
tooltip: "Ozon wordt niet rechtstreeks uitgestoten, maar wordt gevormd uit stikstofoxiden, vluchtige organische stoffen en koolmonoxide. De concentratie ozon (O3) is vooral afhankelijk van het weer. In heel Europa wordt de bevolking gewaarschuwd bij ozonconcentraties boven 180 (μg/m3). Een concentratie van 240 (μg/m3) is de Europese alarmdrempel. ",
more: "https://www.luchtmeetnet.nl/uitleg",
type: "StationLayer",
sources: [ luchtmeetnet ]
},
}
},
{
title: "weerdata",
parameters: {
"temperature": {
id: "temperature",
label: "temperatuur",
unit: "º",
range: [0, 60],
legend: {
0: { label: null, color: "#61b53d" },
60: { label: null, color: "#f9be38" },
},
tooltip: "",
type: "StationLayer",
sources: [ holu ]
},
"humidity": {
id: "humidity",
label: "luchtvochtigheid",
unit: "%",
range: [0, 100],
legend: {
0: { label: null, color: "#61b53d" },
100: { label: null, color: "#f9be38" },
},
tooltip: "",
type: "StationLayer",
sources: [ holu ]
}
}
}
]
export const texts = {
about: {
title: "over deze kaart",
body: "<p>Deze kaart visualiseert data afkomstig van 200 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 de data afkomstig van de HOLU-sensorkits, en naar de beste manier waarop je deze data kunt inzetten.</p><p><a class='more' href='https://hollandseluchten.waag.org/veelgestelde-vragen/over-luchtkwaliteit/'>meer weten</a></p>"
},
pilotLocations: "Pilot-locaties",
loading: "Bezig met laden...",
loadingError: "Laden mislukt...",
loadingRetry: "Probeer opnieuw",
lastMean: "laatste uurgemiddelde",
lastPeak: "piekwaarde laatste uur",
mean: "gemiddelde",
......@@ -9,37 +15,73 @@ export const texts = {
}
export const mapDefaults = {
latitude: 52.55958097274668,
longitude: 4.757476618488988,
zoom: 10,
pitch: 60,
bearing: 5
}
export const mapLocations = [
{
name: "Pilot IJmond",
latitude: 52.5068544,
longitude: 4.6038689,
zoom: 12
name: "IJmondregio",
bounds: [
[4.557490, 52.514173],
[4.694126, 52.436089]
],
sublocations: [
{
name: "Beverwijk / Heemskerk",
bounds: [
[52.516652, 4.613219].reverse(),
[52.476220, 4.700986].reverse()
]
},
{
name: "IJmuiden / Driehuis",
bounds: [
[52.467639, 4.554093].reverse(),
[52.442918, 4.651562].reverse()
]
},
{
name: "Velsen-Noord / Velsen-Zuid",
bounds: [
[52.479721, 4.619610].reverse(),
[52.455499, 4.662332].reverse()
]
},
{
name: "Wijk aan Zee",
bounds: [
[4.575178, 52.498957],
[4.625811, 52.482799]
]
}
]
},
{
name: "Pilot Buiksloterham",
latitude: 52.4005373,
longitude: 4.8709094,
zoom: 14
name: "Buiksloterham",
bounds: [
[4.881806, 52.409374],
[4.916532, 52.385961]
]
},
{
name: "Pilot Zaanstad",
latitude: 52.4687981,
longitude: 4.6986803,
zoom: 12
name: "Zaanstad",
bounds: [
[4.676039, 52.521136],
[4.862574, 52.418579]
]
}
]
export const mapDefaults = {
bounds: [
[4.5, 52.6],
[5, 52.3]
],
viewport: {
pitch: 60,
bearing: 0
}
}
export const mapControlSettings = {
transitionDuration: 150,
zoomStep: 0.5,
......
import { addMissingHours, pad } from "../../data-on-a-map-app/src/util/data"
export default {
name: "holu",
sides: 24,
angle: 0,
factor: 0.5,
all: async function getAllHolu(parameter) {
try {
const allResponse = await fetch(`https://data.waag.org/api/getStations`)
const all = await allResponse.json()
const latestResponse = await fetch(`http://localhost:4567/sensor/${parameter}`)
let latest = await latestResponse.json()
latest = latest.map(s => {
const key = Object.keys(s)[0]
return {
id: key.slice(-3),
data: s[key]
}
})
let offlineSensors = []
let unregisteredSensors = []
let places = {}
const data = all.map(sensor => {
const sensorData = latest.find(o => {
//return +o.id === +sensor.id
return +o.id === 211
})
let sensorDataToUse
if(sensorData.data[0][Object.keys(sensorData.data[0])[0]].value) {
sensorDataToUse = sensorData.data[0][Object.keys(sensorData.data[0])[0]]
} else {
sensorDataToUse = sensorData.data[1][Object.keys(sensorData.data[1])[0]]
}
const dataAge = (new Date() - new Date(sensorDataToUse.timestamp))
if(dataAge < 3600000) {
return {
id: sensor.id.toString(),
name: "HoLu-kit " + sensor.id.toString() + ": " + sensor.name,
source: "holu",
coordinates: [sensor.longitude, sensor.latitude],
mean: sensorDataToUse.value,
max: null
}
} else {
return {
id: sensor.id.toString(),
name: "HoLu-kit " + sensor.id.toString() + ": " + sensor.name + " (" + (dataAge < 86400000 ? "geen recente data" : "offline" ) + ")",
source: "holu",
coordinates: [sensor.longitude, sensor.latitude],
mean: null,
max: null
}
}
})
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)
}
},
detail: async function getDetailHolu(station, parameter, start, end) {
try {
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>'
)
)
}
const response = await fetch(`http://localhost:4567/sensor/${pad(station.id, 3)}/${parameter}?start=${start.slice(0,10)}&end=${end.slice(0,10)}`)
const responseData = await response.json()
const conformedData = responseData[0][Object.keys(responseData[0])[0]].map(m => {
return {
timestamp: new Date(m.timestamp).getTime(),
value: m.value,
}
})
const data = addMissingHours(conformedData)
return { stationMeta, data }
} catch (error) {
console.log(error)
}
}
}
\ No newline at end of file
......@@ -13,15 +13,21 @@ export default {
const all = await allResponse.json()
const latestResponse = await fetch(`https://data.waag.org/api/getAllSensors`)
const latest = await latestResponse.json()
let latest = await latestResponse.json()