Add loading of live data

This commit is contained in:
Pieter Vander Vennet 2020-10-12 01:25:27 +02:00
parent feab5a19b3
commit 0d6412f824
14 changed files with 291 additions and 35 deletions

View file

@ -17,6 +17,7 @@ import * as bike_repair_station from "../../assets/layers/bike_repair_station/bi
import * as birdhides from "../../assets/layers/bird_hide/birdhides.json" import * as birdhides from "../../assets/layers/bird_hide/birdhides.json"
import * as nature_reserve from "../../assets/layers/nature_reserve/nature_reserve.json" import * as nature_reserve from "../../assets/layers/nature_reserve/nature_reserve.json"
import * as bike_cafes from "../../assets/layers/bike_cafe/bike_cafes.json" import * as bike_cafes from "../../assets/layers/bike_cafe/bike_cafes.json"
import * as bike_monitoring_station from "../../assets/layers/bike_monitoring_station/bike_monitoring_station.json"
import * as cycling_themed_objects from "../../assets/layers/cycling_themed_object/cycling_themed_objects.json" import * as cycling_themed_objects from "../../assets/layers/cycling_themed_object/cycling_themed_objects.json"
import * as bike_shops from "../../assets/layers/bike_shop/bike_shop.json" import * as bike_shops from "../../assets/layers/bike_shop/bike_shop.json"
import * as maps from "../../assets/layers/maps/maps.json" import * as maps from "../../assets/layers/maps/maps.json"
@ -43,6 +44,7 @@ export class FromJSON {
FromJSON.Layer(viewpoint), FromJSON.Layer(viewpoint),
FromJSON.Layer(bike_parking), FromJSON.Layer(bike_parking),
FromJSON.Layer(bike_repair_station), FromJSON.Layer(bike_repair_station),
FromJSON.Layer(bike_monitoring_station),
FromJSON.Layer(birdhides), FromJSON.Layer(birdhides),
FromJSON.Layer(nature_reserve), FromJSON.Layer(nature_reserve),
FromJSON.Layer(bike_cafes), FromJSON.Layer(bike_cafes),

View file

@ -117,7 +117,7 @@ export class FilteredLayer {
feature.properties["_lon"] = "" + lat; // We expect a string here for lat/lon feature.properties["_lon"] = "" + lat; // We expect a string here for lat/lon
feature.properties["_lat"] = "" + lon; feature.properties["_lat"] = "" + lon;
// But the codegrid SHOULD be a number! // But the codegrid SHOULD be a number!
CodeGrid.grid.getCode(lat, lon, (error, code) => { CodeGrid.getCode(lat, lon, (error, code) => {
if (error === null) { if (error === null) {
feature.properties["_country"] = code; feature.properties["_country"] = code;
} else { } else {

View file

@ -91,6 +91,14 @@ export class LayerUpdater {
self.retries.setData(0); self.retries.setData(0);
let newIds = 1;
for (const feature of geojson.features) {
if(feature.properties.id === undefined){
feature.properties.id = "ext/"+newIds;
newIds++;
}
}
function renderLayers(layers: FilteredLayer[]) { function renderLayers(layers: FilteredLayer[]) {
if (layers.length === 0) { if (layers.length === 0) {
self.runningQuery.setData(false); self.runningQuery.setData(false);

View file

@ -1,6 +1,6 @@
import {Bounds} from "../Bounds"; import {Bounds} from "../Bounds";
import {TagsFilter} from "../Tags"; import {TagsFilter} from "../Tags";
import $ from "jquery" import * as $ from "jquery"
import * as OsmToGeoJson from "osmtogeojson"; import * as OsmToGeoJson from "osmtogeojson";
/** /**
@ -34,7 +34,6 @@ export class Overpass {
console.log("Using testing URL") console.log("Using testing URL")
query = Overpass.testUrl; query = Overpass.testUrl;
} }
$.getJSON(query, $.getJSON(query,
function (json, status) { function (json, status) {
if (status !== "success") { if (status !== "success") {

View file

@ -1,7 +1,12 @@
import codegrid from "codegrid-js"; import codegrid from "codegrid-js";
export default class CodeGrid { export default class CodeGrid {
public static readonly grid = CodeGrid.InitGrid(); private static readonly grid = CodeGrid.InitGrid();
public static getCode(lat: any, lon: any, handle: (error, code) => void) {
CodeGrid.grid.getCode(lat, lon, handle);
}
private static InitGrid(): any { private static InitGrid(): any {
const grid = codegrid.CodeGrid("./tiles/"); const grid = codegrid.CodeGrid("./tiles/");
@ -15,4 +20,6 @@ export default class CodeGrid {
}); });
return grid; return grid;
} }
} }

View file

@ -0,0 +1,53 @@
/**
* Fetches data from random data sources
*/
import {UIEventSource} from "../UIEventSource";
import * as $ from "jquery"
export default class LiveQueryHandler {
private static cache = {} // url --> UIEventSource<actual data>
private static neededShorthands = {} // url -> (shorthand:paths)[]
public static FetchLiveData(url: string, shorthands: string[]): UIEventSource<any /* string -> string */> {
const shorthandsSet: string[] = LiveQueryHandler.neededShorthands[url] ?? []
for (const shorthand of shorthands) {
if (shorthandsSet.indexOf(shorthand) < 0) {
shorthandsSet.push(shorthand);
}
}
LiveQueryHandler.neededShorthands[url] = shorthandsSet;
if (LiveQueryHandler[url] === undefined) {
const source = new UIEventSource({});
LiveQueryHandler[url] = source;
console.log("Fetching live data from a third-party (unknown) API:",url)
$.getJSON(url, function (data) {
for (const shorthandDescription of shorthandsSet) {
const descr = shorthandDescription.trim().split(":");
const shorthand = descr[0];
const path = descr[1];
const parts = path.split(".");
let trail = data;
for (const part of parts) {
if (trail !== undefined) {
trail = trail[part];
}
}
source.data[shorthand] = trail;
}
source.ping();
})
}
return LiveQueryHandler[url];
}
}

View file

@ -184,3 +184,6 @@ Park icon via http://www.onlinewebfonts.com/icon/425974, CC BY 3.0 (@sterankofra
Forest icon via https://www.onlinewebfonts.com/icon/498112, CC BY Forest icon via https://www.onlinewebfonts.com/icon/498112, CC BY
Statistics icon via https://www.onlinewebfonts.com/icon/197818 Statistics icon via https://www.onlinewebfonts.com/icon/197818
Chronometer (on monitoring_station.svg): ANTU chronometer
https://commons.wikimedia.org/w/index.php?title=Antu_chronometer&action=edit&redlink=1

View file

@ -1,6 +1,8 @@
import {UIElement} from "./UIElement"; import {UIElement} from "./UIElement";
import OpeningHoursVisualization from "./OhVisualization"; import OpeningHoursVisualization from "./OhVisualization";
import {UIEventSource} from "../Logic/UIEventSource"; import {UIEventSource} from "../Logic/UIEventSource";
import {VariableUiElement} from "./Base/VariableUIElement";
import LiveQueryHandler from "../Logic/Web/LiveQueryHandler";
export default class SpecialVisualizations { export default class SpecialVisualizations {
@ -8,7 +10,7 @@ export default class SpecialVisualizations {
funcName: string, funcName: string,
constr: ((tagSource: UIEventSource<any>, argument: string[]) => UIElement), constr: ((tagSource: UIEventSource<any>, argument: string[]) => UIElement),
docs: string, docs: string,
args: {name: string, defaultValue: string, doc: string}[] args: { name: string, defaultValue?: string, doc: string }[]
}[] = }[] =
[{ [{
@ -24,6 +26,26 @@ export default class SpecialVisualizations {
} }
}, },
{
funcName: "live",
docs: "Downloads a JSON from the given URL, e.g. '{live(example.org/data.json, shorthand:x.y.z, other:a.b.c, shorthand)}' will download the given file, will create an object {shorthand: json[x][y][z], other: json[a][b][c] out of it and will return 'other' or 'json[a][b][c]. This is made to use in combination with tags, e.g. {live({url}, {url:format}, needed_value)}",
args: [{
name: "Url", doc: "The URL to load"
}, {
name: "Shorthands",
doc: "A list of shorthands, of the format 'shorthandname:path.path.path'. Seperated by ;"
}, {
name: "path", doc: "The path (or shorthand) that should be returned"
}],
constr: (tagSource: UIEventSource<any>, args) => {
const url = args[0];
const shorthands = args[1];
const neededValue = args[2];
const source = LiveQueryHandler.FetchLiveData(url, shorthands.split(";"));
return new VariableUiElement(source.map(data => data[neededValue] ?? "Loading..."));
}
}
] ]
} }

View file

@ -47,10 +47,10 @@ export default class Translation extends UIElement {
for (const knownSpecial of knownSpecials) { for (const knownSpecial of knownSpecials) {
do {
const matched = template.match(`(.*){${knownSpecial.funcName}\\((.*)\\)}(.*)`); const matched = template.match(`(.*){${knownSpecial.funcName}\\((.*)\\)}(.*)`);
if (matched === null) { if (matched === null) {
continue; break;
} }
const partBefore = matched[1]; const partBefore = matched[1];
const argument = matched[2]; const argument = matched[2];
@ -64,6 +64,10 @@ export default class Translation extends UIElement {
console.error(e); console.error(e);
template = partBefore + partAfter; template = partBefore + partAfter;
} }
} while (true);
} }
newTranslations[lang] = template; newTranslations[lang] = template;
} }

View file

@ -103,10 +103,10 @@ export default class Translations {
}), }),
respectPrivacy: new T({ respectPrivacy: new T({
en: "Please respect privacy. Do not photograph people nor license plates.<br/>Respect copyright. Only upload images you made yourself. Do not upload Google Streetview Images - these will be removed.", en: "Do not photograph people nor license plates. Do not upload Google Maps, Google Streetview or other copyrighted sources.",
ca: "Respecta la privacitat. No fotografiïs gent o matrícules", ca: "Respecta la privacitat. No fotografiïs gent o matrícules",
es: "Respeta la privacidad. No fotografíes gente o matrículas", es: "Respeta la privacidad. No fotografíes gente o matrículas",
nl: "Respecteer privacy. Fotografeer geen mensen of nummerplaten.<br/>Repecteer auteursrechten. Voeg enkel foto's toe die je zelf maakte. Screenshots van andere services (zoals Google Streetview) worden verwijderd", nl: "Fotografeer geen mensen of nummerplaten. Voeg geen Google Maps, Google Streetview of foto's met auteursrechten toe.",
fr: "Merci de respecter la vie privée. Ne publiez pas les plaques d\'immatriculation", fr: "Merci de respecter la vie privée. Ne publiez pas les plaques d\'immatriculation",
gl: "Respecta a privacidade. Non fotografes xente ou matrículas", gl: "Respecta a privacidade. Non fotografes xente ou matrículas",
de: "Bitte respektieren Sie die Privatsphäre. Fotografieren Sie weder Personen noch Nummernschilder" de: "Bitte respektieren Sie die Privatsphäre. Fotografieren Sie weder Personen noch Nummernschilder"
@ -969,6 +969,9 @@ export default class Translations {
} }
public static WT(s: string | Translation): Translation { public static WT(s: string | Translation): Translation {
if(s === undefined){
return undefined;
}
if (typeof (s) === "string") { if (typeof (s) === "string") {
return new Translation({en: s}); return new Translation({en: s});
} }

View file

@ -0,0 +1,58 @@
{
"id": "bike_monitoring_station",
"name": {
"en": "Monitoring stations"
},
"minzoom": 12,
"overpassTags": {
"and": [
"man_made=monitoring_station",
"monitoring:bicycle=yes"
]
},
"title": {
"render": {
"nl": "Fietstelstation",
"en": "Bicycle counting station"
},
"mappings": [
{
"if": "name~*",
"then": {
"en": "Bicycle counting station {name}",
"nl": "Fietstelstation {name}"
}
},
{
"if": "ref~*",
"then": {
"en": "Bicycle counting station {ref}",
"nl": "Fietstelstation {ref}"
}
}
]
},
"description": {},
"tagRenderings": [
{
"render": "<b>{live({url},{url:format},hour)}</b> cyclists last hour<br/><b>{live({url},{url:format},day)}</b> cyclists today<br/><b>{live({url},{url:format},year)}</b> cyclists this year<br/>",
"condition": {
"and": ["url~*","url:format~*"]
}
}
],
"hideUnderlayingFeaturesMinPercentage": 0,
"icon": {
"render": "./assets/layers/bike_monitoring_station/monitoring_station.svg"
},
"width": {
"render": "8"
},
"iconSize": {
"render": "40,40,center"
},
"color": {
"render": "#00f"
},
"presets": []
}

View file

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
id="svg27"
version="1.1"
fill="none"
viewBox="0 0 97 123"
height="123"
width="97">
<metadata
id="metadata31">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<path
style="fill:#ffd42a"
id="path2"
fill="#5675DF"
d="M52.1412 111.419C50.4633 115.605 44.5366 115.605 42.8588 111.419L24.7014 66.1099C23.385 62.8252 25.8039 59.25 29.3426 59.25L65.6574 59.25C69.1962 59.25 71.615 62.8252 70.2986 66.11L52.1412 111.419Z" />
<ellipse
style="fill:#ffd42a"
id="ellipse4"
fill="#5675DF"
ry="47.5"
rx="48.5"
cy="47.5"
cx="48.5" />
<defs
id="defs25">
<filter
color-interpolation-filters="sRGB"
filterUnits="userSpaceOnUse"
height="53.5"
width="40.7188"
y="25.5"
x="32.2812"
id="filter0_d">
<feFlood
id="feFlood10"
result="BackgroundImageFix"
flood-opacity="0" />
<feColorMatrix
id="feColorMatrix12"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
type="matrix"
in="SourceAlpha" />
<feOffset
id="feOffset14"
dy="4" />
<feGaussianBlur
id="feGaussianBlur16"
stdDeviation="2" />
<feColorMatrix
id="feColorMatrix18"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"
type="matrix" />
<feBlend
id="feBlend20"
result="effect1_dropShadow"
in2="BackgroundImageFix"
mode="normal" />
<feBlend
id="feBlend22"
result="shape"
in2="effect1_dropShadow"
in="SourceGraphic"
mode="normal" />
</filter>
</defs>
<g
style="fill:#ffffff"
transform="matrix(0.1210062,0,0,0.1210062,13.583344,11.282657)"
id="g844">
<path
d="m 464.99,141.27 c 0,0 8.996,9.05 15.346,0.017 l 12.1,-17.349 c 4.225,-6.103 -1.231,-10.327 -1.796,-10.744 l -73.41,-51.29 h -0.017 c -4.52,-3.145 -7.521,-1.236 -9.1,0.533 l -1.311,1.891 -11.181,16.01 c -0.053,0.061 -6.315,9.937 3.695,15.16 v 0.013 c 14.295,7.03 40.526,21.868 65.66,45.814 l 0.018,-0.051"
id="path838" />
<path
d="M 341.45,0 H 225.76 c -8.978,0 -16.334,7.213 -16.334,16.338 v 13.484 c 0,9.112 7.356,16.468 16.334,16.468 h 5.725 v 9.521 c 0.009,13.688 8.648,14.825 12.985,14.534 0.737,-0.109 1.501,-0.226 2.308,-0.33 0.009,0 0.122,-0.026 0.122,-0.026 h -0.009 c 15.397,-2.108 40.9,-4.125 70.15,-0.403 l 0.017,-0.026 c 0,0 18.737,3.474 18.686,-13.758 v -9.52 h 5.725 c 8.987,0 16.325,-7.348 16.325,-16.473 V 16.338 C 357.7939,7.221 350.437,0 341.45,0"
id="path840" />
<path
d="m 11,5.563 c -3.71,0 -6.719,3.01 -6.719,6.719 0,3.71 3.01,6.719 6.719,6.719 3.71,0 6.719,-3.01 6.719,-6.719 0,-3.711 -3.01,-6.719 -6.719,-6.719 m 0.1,2.938 c 0.542,0 1.06,0.107 1.553,0.318 0.493,0.212 0.918,0.496 1.275,0.854 0.358,0.358 0.644,0.782 0.855,1.275 0.212,0.493 0.316,1.011 0.316,1.553 0,0.542 -0.105,1.06 -0.316,1.553 -0.212,0.493 -0.498,0.918 -0.855,1.275 -0.358,0.358 -0.782,0.642 -1.275,0.854 -0.493,0.212 -1.011,0.318 -1.553,0.318 -0.597,0 -1.165,-0.125 -1.703,-0.377 C 8.859,15.872 8.401,15.516 8.022,15.058 7.998,15.023 7.987,14.984 7.989,14.941 7.991,14.898 8.006,14.863 8.034,14.836 l 0.713,-0.719 c 0.035,-0.031 0.079,-0.047 0.131,-0.047 0.056,0.007 0.095,0.028 0.119,0.063 0.253,0.33 0.564,0.585 0.932,0.766 0.368,0.181 0.759,0.27 1.172,0.27 0.361,0 0.707,-0.07 1.035,-0.211 0.328,-0.141 0.612,-0.331 0.852,-0.57 0.24,-0.24 0.43,-0.523 0.57,-0.852 0.141,-0.328 0.211,-0.672 0.211,-1.033 0,-0.361 -0.07,-0.705 -0.211,-1.033 -0.141,-0.328 -0.331,-0.612 -0.57,-0.852 -0.24,-0.24 -0.523,-0.43 -0.852,-0.57 -0.328,-0.141 -0.674,-0.211 -1.035,-0.211 -0.34,0 -0.666,0.06 -0.979,0.184 -0.312,0.123 -0.591,0.3 -0.834,0.529 l 0.715,0.719 c 0.108,0.104 0.131,0.224 0.072,0.359 -0.059,0.139 -0.161,0.209 -0.307,0.209 H 7.434 c -0.09,0 -0.168,-0.034 -0.234,-0.1 C 7.134,11.671 7.1,11.593 7.1,11.503 V 9.169 C 7.1,9.023 7.17,8.921 7.309,8.862 7.444,8.803 7.564,8.827 7.668,8.934 L 8.346,9.606 C 8.718,9.255 9.142,8.984 9.619,8.792 c 0.477,-0.193 0.97,-0.289 1.48,-0.289"
transform="matrix(35.45144,0,0,35.45144,-106.35,-106.35)"
id="path842" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

View file

@ -20,11 +20,11 @@
"icon": "./assets/themes/cyclofix/logo.svg", "icon": "./assets/themes/cyclofix/logo.svg",
"version": "0", "version": "0",
"startLat": 50.8465573, "startLat": 50.8465573,
"defaultBackgroundId": "CartoDB.Voyager",
"startLon": 4.3516970, "startLon": 4.3516970,
"startZoom": 16, "startZoom": 16,
"widenFactor": 0.05, "widenFactor": 0.05,
"socialImage": "./assets/themes/cyclofix/logo.svg", "socialImage": "./assets/themes/cyclofix/logo.svg",
"layers": ["bike_repair_station", "bike_cafes", "bike_shops", "drinking_water", "bike_parking","bike_themed_object"], "layers": ["bike_repair_station", "bike_cafes", "bike_shops", "drinking_water", "bike_parking","bike_themed_object","bike_monitoring_station"],
"roamingRenderings": [] "roamingRenderings": []
} }

15
test.ts
View file

@ -1,15 +1,16 @@
//* //*
import {UIEventSource} from "./Logic/UIEventSource"; import LiveQueryHandler from "./Logic/Web/LiveQueryHandler";
import OpeningHoursVisualization from "./UI/OhVisualization"; import {VariableUiElement} from "./UI/Base/VariableUIElement";
const oh = "Tu-Fr 09:00-17:00 'as usual'; mo off 'yyy'; su off 'xxx'" const source = LiveQueryHandler.FetchLiveData("https://data.mobility.brussels/bike/api/counts/?request=live&featureID=CJM90",
const tags = new UIEventSource<any>({opening_hours:oh}); "hour:data.hour_cnt;day:data.day_cnt;year:data.year_cnt".split(";"))
new OpeningHoursVisualization(tags, 'opening_hours').AttachTo('maindiv') source.addCallback((data) => {console.log(data)})
new VariableUiElement(source.map(data => {
return ["Data is:", data.hour, "last hour;", data.day, "last day; ", data.year, "last year;"].join(" ")
})).AttachTo('maindiv')
window.setTimeout(() => {tags.data._country = "be"; }, 5000)
/*/ /*/