forked from MapComplete/MapComplete
		
	Add loading of live data
This commit is contained in:
		
							parent
							
								
									feab5a19b3
								
							
						
					
					
						commit
						0d6412f824
					
				
					 14 changed files with 291 additions and 35 deletions
				
			
		|  | @ -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), | ||||||
|  |  | ||||||
|  | @ -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 { | ||||||
|  |  | ||||||
|  | @ -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); | ||||||
|  |  | ||||||
|  | @ -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"; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  | @ -28,13 +28,12 @@ export class Overpass { | ||||||
|      |      | ||||||
|     queryGeoJson(bounds: Bounds, continuation: ((any) => void), onFail: ((reason) => void)): void { |     queryGeoJson(bounds: Bounds, continuation: ((any) => void), onFail: ((reason) => void)): void { | ||||||
| 
 | 
 | ||||||
|         let query = this.buildQuery( "[bbox:" + bounds.south + "," + bounds.west + "," + bounds.north + "," + bounds.east + "]") |         let query = this.buildQuery("[bbox:" + bounds.south + "," + bounds.west + "," + bounds.north + "," + bounds.east + "]") | ||||||
| 
 | 
 | ||||||
|         if(Overpass.testUrl !== null){ |         if (Overpass.testUrl !== null) { | ||||||
|             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") { | ||||||
|  | @ -42,7 +41,7 @@ export class Overpass { | ||||||
|                     onFail(status); |                     onFail(status); | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 if(json.elements === [] && json.remarks.indexOf("runtime error") > 0){ |                 if (json.elements === [] && json.remarks.indexOf("runtime error") > 0) { | ||||||
|                     console.log("Timeout or other runtime error"); |                     console.log("Timeout or other runtime error"); | ||||||
|                     onFail("Runtime error (timeout)") |                     onFail("Runtime error (timeout)") | ||||||
|                     return; |                     return; | ||||||
|  |  | ||||||
|  | @ -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; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| } | } | ||||||
							
								
								
									
										53
									
								
								Logic/Web/LiveQueryHandler.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								Logic/Web/LiveQueryHandler.ts
									
										
									
									
									
										Normal 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]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -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 | ||||||
|  | @ -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,13 +10,13 @@ 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 }[] | ||||||
|     }[] = |     }[] = | ||||||
| 
 | 
 | ||||||
|         [{ |         [{ | ||||||
|             funcName: "opening_hours_table", |             funcName: "opening_hours_table", | ||||||
|             docs: "Creates an opening-hours table. Usage: {opening_hours_table(opening_hours)} to create a table of the tag 'opening_hours'.", |             docs: "Creates an opening-hours table. Usage: {opening_hours_table(opening_hours)} to create a table of the tag 'opening_hours'.", | ||||||
|             args:[{name:"key", defaultValue: "opening_hours", doc: "The tag from which the table is constructed"}], |             args: [{name: "key", defaultValue: "opening_hours", doc: "The tag from which the table is constructed"}], | ||||||
|             constr: (tagSource: UIEventSource<any>, args) => { |             constr: (tagSource: UIEventSource<any>, args) => { | ||||||
|                 let keyname = args[0]; |                 let keyname = args[0]; | ||||||
|                 if (keyname === undefined || keyname === "") { |                 if (keyname === undefined || keyname === "") { | ||||||
|  | @ -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...")); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|         ] |         ] | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | @ -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; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -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}); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -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": [] | ||||||
|  | } | ||||||
							
								
								
									
										96
									
								
								assets/layers/bike_monitoring_station/monitoring_station.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								assets/layers/bike_monitoring_station/monitoring_station.svg
									
										
									
									
									
										Normal 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 | 
|  | @ -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
									
										
									
									
									
								
							
							
						
						
									
										15
									
								
								test.ts
									
										
									
									
									
								
							|  | @ -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) |  | ||||||
| /*/ | /*/ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue