forked from MapComplete/MapComplete
		
	Add metatagging, fritures
This commit is contained in:
		
							parent
							
								
									1e0a1fdf97
								
							
						
					
					
						commit
						99225957cc
					
				
					 15 changed files with 406 additions and 38 deletions
				
			
		|  | @ -13,6 +13,7 @@ import * as nature from "../assets/themes/nature/nature.json" | ||||||
| import * as maps from "../assets/themes/maps/maps.json" | import * as maps from "../assets/themes/maps/maps.json" | ||||||
| import * as shops from "../assets/themes/shops/shops.json" | import * as shops from "../assets/themes/shops/shops.json" | ||||||
| import * as bike_monitoring_stations from "../assets/themes/bike_monitoring_station/bike_monitoring_stations.json" | import * as bike_monitoring_stations from "../assets/themes/bike_monitoring_station/bike_monitoring_stations.json" | ||||||
|  | import * as fritures from "../assets/themes/fritures/fritures.json" | ||||||
| import {PersonalLayout} from "../Logic/PersonalLayout"; | import {PersonalLayout} from "../Logic/PersonalLayout"; | ||||||
| import {StreetWidth} from "./StreetWidth/StreetWidth"; | import {StreetWidth} from "./StreetWidth/StreetWidth"; | ||||||
| 
 | 
 | ||||||
|  | @ -66,6 +67,7 @@ export class AllKnownLayouts { | ||||||
|         FromJSON.LayoutFromJSON(nature), |         FromJSON.LayoutFromJSON(nature), | ||||||
|         FromJSON.LayoutFromJSON(cyclestreets), |         FromJSON.LayoutFromJSON(cyclestreets), | ||||||
|         FromJSON.LayoutFromJSON(maps), |         FromJSON.LayoutFromJSON(maps), | ||||||
|  |         FromJSON.LayoutFromJSON(fritures), | ||||||
|         AllKnownLayouts.GenerateBuurtNatuur(), |         AllKnownLayouts.GenerateBuurtNatuur(), | ||||||
|         AllKnownLayouts.GenerateBikeMonitoringStations(), |         AllKnownLayouts.GenerateBikeMonitoringStations(), | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -15,7 +15,7 @@ import {DropDown} from "./UI/Input/DropDown"; | ||||||
| import {LayerSelection} from "./UI/LayerSelection"; | import {LayerSelection} from "./UI/LayerSelection"; | ||||||
| import {Preset} from "./Customizations/LayerDefinition"; | import {Preset} from "./Customizations/LayerDefinition"; | ||||||
| import {VariableUiElement} from "./UI/Base/VariableUIElement"; | import {VariableUiElement} from "./UI/Base/VariableUIElement"; | ||||||
| import {LayerUpdater} from "./Logic/LayerUpdater"; | import {UpdateFromOverpass} from "./Logic/UpdateFromOverpass"; | ||||||
| import {UIEventSource} from "./Logic/UIEventSource"; | import {UIEventSource} from "./Logic/UIEventSource"; | ||||||
| import {QueryParameters} from "./Logic/Web/QueryParameters"; | import {QueryParameters} from "./Logic/Web/QueryParameters"; | ||||||
| import {PersonalLayout} from "./Logic/PersonalLayout"; | import {PersonalLayout} from "./Logic/PersonalLayout"; | ||||||
|  | @ -451,7 +451,7 @@ export class InitUiElements { | ||||||
|             ) |             ) | ||||||
|         ); |         ); | ||||||
|         State.state.bm = bm; |         State.state.bm = bm; | ||||||
|         State.state.layerUpdater = new LayerUpdater(State.state); |         State.state.layerUpdater = new UpdateFromOverpass(State.state); | ||||||
| 
 | 
 | ||||||
|         State.state.availableBackgroundLayers = new AvailableBaseLayers(State.state).availableEditorLayers; |         State.state.availableBackgroundLayers = new AvailableBaseLayers(State.state).availableEditorLayers; | ||||||
|         const queryParam = QueryParameters.GetQueryParameter("background", State.state.layoutToUse.data.defaultBackground); |         const queryParam = QueryParameters.GetQueryParameter("background", State.state.layoutToUse.data.defaultBackground); | ||||||
|  |  | ||||||
|  | @ -110,31 +110,6 @@ export class FilteredLayer { | ||||||
|             const tags = TagUtils.proprtiesToKV(feature.properties); |             const tags = TagUtils.proprtiesToKV(feature.properties); | ||||||
|              |              | ||||||
|             if (this.filters.matches(tags)) { |             if (this.filters.matches(tags)) { | ||||||
|                 const centerPoint = GeoOperations.centerpoint(feature); |  | ||||||
|                 const sqMeters = GeoOperations.surfaceAreaInSqMeters(feature); |  | ||||||
|                 feature.properties["_surface"] = "" + sqMeters; |  | ||||||
|                 feature.properties["_surface:ha"] = "" + Math.floor(sqMeters / 1000)/10; |  | ||||||
| 
 |  | ||||||
|                 const lat = centerPoint.geometry.coordinates[1]; |  | ||||||
|                 const lon = centerPoint.geometry.coordinates[0] |  | ||||||
|                 feature.properties["_lon"] = "" + lat; // We expect a string here for lat/lon
 |  | ||||||
|                 feature.properties["_lat"] = "" + lon; |  | ||||||
|                 // But the codegrid SHOULD be a number!
 |  | ||||||
|                 CodeGrid.getCode(lat, lon, (error, code) => { |  | ||||||
|                     if (error === null) { |  | ||||||
|                         feature.properties["_country"] = code; |  | ||||||
|                     } else { |  | ||||||
|                         console.warn("Could not determine country for", feature.properties.id, error); |  | ||||||
|                     } |  | ||||||
|                 }) |  | ||||||
| 
 |  | ||||||
|                 if (feature.geometry.type !== "Point") { |  | ||||||
|                     if (this._wayHandling === LayerDefinition.WAYHANDLING_CENTER_AND_WAY) { |  | ||||||
|                         selfFeatures.push(centerPoint); |  | ||||||
|                     } else if (this._wayHandling === LayerDefinition.WAYHANDLING_CENTER_ONLY) { |  | ||||||
|                         feature = centerPoint; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 selfFeatures.push(feature); |                 selfFeatures.push(feature); | ||||||
|             } else { |             } else { | ||||||
|                 leftoverFeatures.push(feature); |                 leftoverFeatures.push(feature); | ||||||
|  |  | ||||||
							
								
								
									
										124
									
								
								Logic/MetaTagging.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								Logic/MetaTagging.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,124 @@ | ||||||
|  | import {GeoOperations} from "./GeoOperations"; | ||||||
|  | import CodeGrid from "./Web/CodeGrid"; | ||||||
|  | import State from "../State"; | ||||||
|  | import opening_hours from "opening_hours"; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class SimpleMetaTagger { | ||||||
|  |     private _f: (feature: any) => void; | ||||||
|  |     public readonly keys: string[]; | ||||||
|  |     public readonly doc: string; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     constructor(keys: string[], doc: string, f: ((feature: any) => void)) { | ||||||
|  |         this.keys = keys; | ||||||
|  |         this.doc = doc; | ||||||
|  |         this._f = f; | ||||||
|  |         for (const key of keys) { | ||||||
|  |             if (!key.startsWith('_')) { | ||||||
|  |                 throw `Incorrect metakey ${key}: it should start with underscore (_)` | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     addMetaTags(features: any[]) { | ||||||
|  |         for (const feature of features) { | ||||||
|  |             this._f(feature); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Metatagging adds various tags to the elements, e.g. lat, lon, surface area, ... | ||||||
|  |  * | ||||||
|  |  * All metatags start with an underscore | ||||||
|  |  */ | ||||||
|  | export default class MetaTagging { | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     public static metatags = [ | ||||||
|  |         new SimpleMetaTagger(["_lat", "_lon"], "The latitude and longitude of the point (or centerpoint in the case of a way/area)", | ||||||
|  |             (feature => { | ||||||
|  |                 const centerPoint = GeoOperations.centerpoint(feature); | ||||||
|  |                 const lat = centerPoint.geometry.coordinates[1]; | ||||||
|  |                 const lon = centerPoint.geometry.coordinates[0]; | ||||||
|  |                 feature.properties["_lat"] = "" + lat; | ||||||
|  |                 feature.properties["_lon"] = "" + lon; | ||||||
|  |             }) | ||||||
|  |         ), | ||||||
|  |         new SimpleMetaTagger( | ||||||
|  |             ["_surface", "_surface:ha"], "The surface area of the feature, in square meters and in hectare. Not set on points and ways", | ||||||
|  |             (feature => { | ||||||
|  |                 const sqMeters = GeoOperations.surfaceAreaInSqMeters(feature); | ||||||
|  |                 feature.properties["_surface"] = "" + sqMeters; | ||||||
|  |                 feature.properties["_surface:ha"] = "" + Math.floor(sqMeters / 1000) / 10; | ||||||
|  | 
 | ||||||
|  |             }) | ||||||
|  |         ), | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         new SimpleMetaTagger( | ||||||
|  |             ["_country"], "The country code of the point", | ||||||
|  |             (feature => { | ||||||
|  |                 const centerPoint = GeoOperations.centerpoint(feature); | ||||||
|  |                 const lat = centerPoint.geometry.coordinates[1]; | ||||||
|  |                 const lon = centerPoint.geometry.coordinates[0] | ||||||
|  |                 // But the codegrid SHOULD be a number!
 | ||||||
|  |                 CodeGrid.getCode(lat, lon, (error, code) => { | ||||||
|  |                     if (error === null) { | ||||||
|  |                         feature.properties["_country"] = code; | ||||||
|  |                         State.state.allElements.addOrGetElement(feature).ping(); | ||||||
|  |                     } else { | ||||||
|  |                         console.warn("Could not determine country for", feature.properties.id, error); | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|  |             }) | ||||||
|  |         ), | ||||||
|  |         new SimpleMetaTagger( | ||||||
|  |             ["_isOpen", "_isOpen:description"], "If 'opening_hours' is present, it will add the current state of the feature (being 'yes' or 'no", | ||||||
|  |             (feature => { | ||||||
|  |                 const tagsSource = State.state.allElements.addOrGetElement(feature); | ||||||
|  |                 tagsSource.addCallback(tags => { | ||||||
|  | 
 | ||||||
|  |                     if (tags["opening_hours"] !== undefined && tags["_country"] !== undefined) { | ||||||
|  | 
 | ||||||
|  |                         const oh = new opening_hours(tags["opening_hours"], { | ||||||
|  |                             lat: tags._lat, | ||||||
|  |                             lon: tags._lon, | ||||||
|  |                             address: { | ||||||
|  |                                 country_code: tags._country | ||||||
|  |                             } | ||||||
|  |                         }, {tag_key: "opening_hours"}); | ||||||
|  | 
 | ||||||
|  |                         const updateTags = () => { | ||||||
|  |                             tags["_isOpen"] = oh.getState() ? "yes" : "no"; | ||||||
|  |                             const comment = oh.getComment(); | ||||||
|  |                             if (comment) { | ||||||
|  |                                 tags["_isOpen:description"] = comment; | ||||||
|  |                             } | ||||||
|  |                             const nextChange = oh.getNextChange() as Date; | ||||||
|  |                             window.setTimeout( | ||||||
|  |                                 updateTags, | ||||||
|  |                                 (nextChange.getTime() - (new Date()).getTime()) | ||||||
|  |                             ) | ||||||
|  |                         } | ||||||
|  |                         updateTags(); | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                 }) | ||||||
|  | 
 | ||||||
|  |             }) | ||||||
|  |         ) | ||||||
|  |     ]; | ||||||
|  | 
 | ||||||
|  |     static addMetatags(features: any[]) { | ||||||
|  | 
 | ||||||
|  |         for (const metatag of MetaTagging.metatags) { | ||||||
|  |             metatag.addMetaTags(features); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -5,8 +5,9 @@ import {Bounds} from "./Bounds"; | ||||||
| import {Overpass} from "./Osm/Overpass"; | import {Overpass} from "./Osm/Overpass"; | ||||||
| import State from "../State"; | import State from "../State"; | ||||||
| import {LayerDefinition} from "../Customizations/LayerDefinition"; | import {LayerDefinition} from "../Customizations/LayerDefinition"; | ||||||
|  | import MetaTagging from "./MetaTagging"; | ||||||
| 
 | 
 | ||||||
| export class LayerUpdater { | export class UpdateFromOverpass { | ||||||
| 
 | 
 | ||||||
|     public readonly sufficentlyZoomed: UIEventSource<boolean>; |     public readonly sufficentlyZoomed: UIEventSource<boolean>; | ||||||
|     public readonly runningQuery: UIEventSource<boolean> = new UIEventSource<boolean>(false); |     public readonly runningQuery: UIEventSource<boolean> = new UIEventSource<boolean>(false); | ||||||
|  | @ -100,6 +101,8 @@ export class LayerUpdater { | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|          |          | ||||||
|  |         MetaTagging.addMetatags(geojson.features); | ||||||
|  |          | ||||||
|         function renderLayers(layers: FilteredLayer[]) { |         function renderLayers(layers: FilteredLayer[]) { | ||||||
|             if (layers.length === 0) { |             if (layers.length === 0) { | ||||||
|                 self.runningQuery.setData(false); |                 self.runningQuery.setData(false); | ||||||
|  | @ -8,6 +8,7 @@ export default class CodeGrid { | ||||||
|         CodeGrid.grid.getCode(lat, lon, handle); |         CodeGrid.grid.getCode(lat, lon, handle); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|     private static InitGrid(): any { |     private static InitGrid(): any { | ||||||
|         const grid = codegrid.CodeGrid("./tiles/"); |         const grid = codegrid.CodeGrid("./tiles/"); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										4
									
								
								State.ts
									
										
									
									
									
								
							
							
						
						
									
										4
									
								
								State.ts
									
										
									
									
									
								
							|  | @ -8,7 +8,7 @@ import {OsmConnection} from "./Logic/Osm/OsmConnection"; | ||||||
| import Locale from "./UI/i18n/Locale"; | import Locale from "./UI/i18n/Locale"; | ||||||
| import Translations from "./UI/i18n/Translations"; | import Translations from "./UI/i18n/Translations"; | ||||||
| import {FilteredLayer} from "./Logic/FilteredLayer"; | import {FilteredLayer} from "./Logic/FilteredLayer"; | ||||||
| import {LayerUpdater} from "./Logic/LayerUpdater"; | import {UpdateFromOverpass} from "./Logic/UpdateFromOverpass"; | ||||||
| import {UIEventSource} from "./Logic/UIEventSource"; | import {UIEventSource} from "./Logic/UIEventSource"; | ||||||
| import {LocalStorageSource} from "./Logic/Web/LocalStorageSource"; | import {LocalStorageSource} from "./Logic/Web/LocalStorageSource"; | ||||||
| import {QueryParameters} from "./Logic/Web/QueryParameters"; | import {QueryParameters} from "./Logic/Web/QueryParameters"; | ||||||
|  | @ -66,7 +66,7 @@ export default class State { | ||||||
| 
 | 
 | ||||||
|     public favouriteLayers: UIEventSource<string[]>; |     public favouriteLayers: UIEventSource<string[]>; | ||||||
| 
 | 
 | ||||||
|     public layerUpdater: LayerUpdater; |     public layerUpdater: UpdateFromOverpass; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     public filteredLayers: UIEventSource<FilteredLayer[]> = new UIEventSource<FilteredLayer[]>([]) |     public filteredLayers: UIEventSource<FilteredLayer[]> = new UIEventSource<FilteredLayer[]>([]) | ||||||
|  |  | ||||||
|  | @ -33,7 +33,6 @@ export class TabbedComponent extends UIElement { | ||||||
|         headerBar = "<div class='tabs-header-bar'>" + headerBar + "</div>" |         headerBar = "<div class='tabs-header-bar'>" + headerBar + "</div>" | ||||||
| 
 | 
 | ||||||
|         const content = this.content[this._source.data]; |         const content = this.content[this._source.data]; | ||||||
|         console.log("Rendering tab", this._source.data); |  | ||||||
|         return headerBar + "<div class='tab-content'>" + (content?.Render() ?? "") + "</div>"; |         return headerBar + "<div class='tab-content'>" + (content?.Render() ?? "") + "</div>"; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -87,7 +87,7 @@ export class ImageUploadFlow extends UIElement { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const extraInfo = new Combine([ |         const extraInfo = new Combine([ | ||||||
|             Translations.t.image.respectPrivacy, |             Translations.t.image.respectPrivacy.SetStyle("font-size:small;"), | ||||||
|             "<br/>", |             "<br/>", | ||||||
|             this._licensePicker, |             this._licensePicker, | ||||||
|             "<br/>", |             "<br/>", | ||||||
|  |  | ||||||
|  | @ -114,6 +114,12 @@ export default class PublicHolidayInput extends InputElement<string> { | ||||||
|                 mode: "off" |                 mode: "off" | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |          | ||||||
|  |         if(str === "PH open"){ | ||||||
|  |             return { | ||||||
|  |                 mode: "open" | ||||||
|  |             } | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         if (!str.startsWith("PH ")) { |         if (!str.startsWith("PH ")) { | ||||||
|             return null; |             return null; | ||||||
|  |  | ||||||
|  | @ -17,10 +17,6 @@ export class MoreScreen extends UIElement { | ||||||
|         super(State.state.locationControl); |         super(State.state.locationControl); | ||||||
|         this.ListenTo(State.state.osmConnection.userDetails); |         this.ListenTo(State.state.osmConnection.userDetails); | ||||||
|         this.ListenTo(State.state.installedThemes); |         this.ListenTo(State.state.installedThemes); | ||||||
|          |  | ||||||
|         State.state.installedThemes.addCallback(themes => { |  | ||||||
|             console.log("INSTALLED THEMES COUNT:", themes.length) |  | ||||||
|         }) |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private createLinkButton(layout: Layout, customThemeDefinition: string = undefined) { |     private createLinkButton(layout: Layout, customThemeDefinition: string = undefined) { | ||||||
|  | @ -72,7 +68,6 @@ export class MoreScreen extends UIElement { | ||||||
| 
 | 
 | ||||||
|     InnerRender(): string { |     InnerRender(): string { | ||||||
| 
 | 
 | ||||||
|         console.log("Inner rendering MORE") |  | ||||||
|         const tr = Translations.t.general.morescreen; |         const tr = Translations.t.general.morescreen; | ||||||
| 
 | 
 | ||||||
|         const els: UIElement[] = [] |         const els: UIElement[] = [] | ||||||
|  |  | ||||||
|  | @ -33,7 +33,7 @@ export default class OpeningHoursVisualization extends UIElement { | ||||||
| 
 | 
 | ||||||
|         const values = [[], [], [], [], [], [], []]; |         const values = [[], [], [], [], [], [], []]; | ||||||
| 
 | 
 | ||||||
|        const start  = new Date(from); |         const start = new Date(from); | ||||||
|         // We go one day more into the past, in order to force rendering of holidays in the start of the period
 |         // We go one day more into the past, in order to force rendering of holidays in the start of the period
 | ||||||
|         start.setDate(from.getDate() - 1); |         start.setDate(from.getDate() - 1); | ||||||
|          |          | ||||||
|  |  | ||||||
|  | @ -166,6 +166,21 @@ export default class SpecialVisualizations { | ||||||
|                     const source = LiveQueryHandler.FetchLiveData(url, shorthands.split(";")); |                     const source = LiveQueryHandler.FetchLiveData(url, shorthands.split(";")); | ||||||
|                     return new VariableUiElement(source.map(data => data[neededValue] ?? "Loading...")); |                     return new VariableUiElement(source.map(data => data[neededValue] ?? "Loading...")); | ||||||
|                 } |                 } | ||||||
|  |             }, | ||||||
|  |              | ||||||
|  |             { | ||||||
|  |                 funcName: "all_tags", | ||||||
|  |                 docs: "Prints all key-value pairs of the object - used for debugging", | ||||||
|  |                 args:[], | ||||||
|  |                 constr: ((tags: UIEventSource<any>) => { | ||||||
|  |                     return new VariableUiElement(tags.map(tags => { | ||||||
|  |                         const parts = []; | ||||||
|  |                         for (const key in tags) { | ||||||
|  |                             parts.push(key+"="+tags[key]); | ||||||
|  |                         } | ||||||
|  |                         return parts.join("<br/>") | ||||||
|  |                     })).SetStyle("border: 1px solid black; border-radius: 1em;padding:1em;display:block;") | ||||||
|  |                 }) | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|         ] |         ] | ||||||
|  |  | ||||||
|  | @ -327,6 +327,15 @@ | ||||||
|       "freeform": { |       "freeform": { | ||||||
|         "key": "description:0" |         "key": "description:0" | ||||||
|       } |       } | ||||||
|  |     }, | ||||||
|  |     {"#": "Surface are", | ||||||
|  |     "render": { | ||||||
|  |       "en": "Surface area: {_surface:ha}Ha", | ||||||
|  |       "mappings": { | ||||||
|  |         "if": "_surface:ha=0", | ||||||
|  |         "then": "" | ||||||
|  |       } | ||||||
|  |     } | ||||||
|     } |     } | ||||||
|   ], |   ], | ||||||
|   "hideUnderlayingFeaturesMinPercentage": 10, |   "hideUnderlayingFeaturesMinPercentage": 10, | ||||||
|  |  | ||||||
							
								
								
									
										239
									
								
								assets/themes/fritures/fritures.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										239
									
								
								assets/themes/fritures/fritures.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,239 @@ | ||||||
|  | { | ||||||
|  |   "id": "id", | ||||||
|  |   "title": { | ||||||
|  |     "nl": "Friturenkaart", | ||||||
|  |     "fr": "Carte des fritures" | ||||||
|  |   }, | ||||||
|  |   "description": { | ||||||
|  |     "nl": "Op deze kaart vind je je favoriete frituur!" | ||||||
|  |   }, | ||||||
|  |   "language": [ | ||||||
|  |     "nl", | ||||||
|  |     "fr" | ||||||
|  |   ], | ||||||
|  |   "maintainer": "", | ||||||
|  |   "icon": "https://upload.wikimedia.org/wikipedia/commons/5/55/French_fries_juliane_kr_r.svg", | ||||||
|  |   "version": "0", | ||||||
|  |   "startLat": 0, | ||||||
|  |   "startLon": 0, | ||||||
|  |   "startZoom": 1, | ||||||
|  |   "widenFactor": 0.05, | ||||||
|  |   "socialImage": "", | ||||||
|  |   "layers": [ | ||||||
|  |     { | ||||||
|  |       "id": "fritures", | ||||||
|  |       "name": { | ||||||
|  |         "nl": "Frituren", | ||||||
|  |         "fr": "Fritures" | ||||||
|  |       }, | ||||||
|  |       "minzoom": 8, | ||||||
|  |       "overpassTags": { | ||||||
|  |         "and": [ | ||||||
|  |           "cuisine~.*friture.*" | ||||||
|  |         ] | ||||||
|  |       }, | ||||||
|  |       "title": { | ||||||
|  |         "render": { | ||||||
|  |           "nl": "Frituur", | ||||||
|  |           "fr": "Friture" | ||||||
|  |         }, | ||||||
|  |         "mappings": [ | ||||||
|  |           { | ||||||
|  |             "if": { | ||||||
|  |               "and": [ | ||||||
|  |                 "name~*" | ||||||
|  |               ] | ||||||
|  |             }, | ||||||
|  |             "then": { | ||||||
|  |               "nl": " <i>{name}</i>", | ||||||
|  |               "fr": " <i>{name}</i>" | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         ] | ||||||
|  |       }, | ||||||
|  |       "description": {}, | ||||||
|  |       "tagRenderings": [ | ||||||
|  |         { | ||||||
|  |           "render": { | ||||||
|  |             "nl": "<a href='tel:{phone}'>{phone}</a>", | ||||||
|  |             "fr": "<a href='tel:{phone}'>{phone}</a>" | ||||||
|  |           }, | ||||||
|  |           "question": { | ||||||
|  |             "en": "What is the phone number?", | ||||||
|  |             "nl": "Wat is het telefoonnummer van deze frituur?", | ||||||
|  |             "fr": "Quel est le numéro de téléphone de cette friture?" | ||||||
|  |           }, | ||||||
|  |           "freeform": { | ||||||
|  |             "key": "phone", | ||||||
|  |             "type": "phone" | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "render": { | ||||||
|  |             "en": "<a href='{website}'>{website}</a>" | ||||||
|  |           }, | ||||||
|  |           "question": { | ||||||
|  |             "en": "What is the website of this shop?", | ||||||
|  |             "nl": "Wat is de website van deze frituur?", | ||||||
|  |             "fr": "Quel est le site web de cette  friture?" | ||||||
|  |           }, | ||||||
|  |           "freeform": { | ||||||
|  |             "key": "website", | ||||||
|  |             "type": "url" | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "render": { | ||||||
|  |             "nl": "<h3>Openingsuren</h3>{opening_hours_table(opening_hours)}", | ||||||
|  |             "fr": "<h3>Horaires</h3>{opening_hours_table(opening_hours)}" | ||||||
|  |           }, | ||||||
|  |           "question": { | ||||||
|  |             "nl": "Wat zijn de openinguren van deze frituur?", | ||||||
|  |             "fr": "Quand est ce-que ce friture ouvert?" | ||||||
|  |           }, | ||||||
|  |           "freeform": { | ||||||
|  |             "key": "opening_hours", | ||||||
|  |             "type": "opening_hours" | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "render": { | ||||||
|  |             "nl": "" | ||||||
|  |           }, | ||||||
|  |           "question": { | ||||||
|  |             "nl": "Heeft deze frituur vegetarische snacks?", | ||||||
|  |             "fr": "Cette friture est-elle équipée de snacks végétariens ?" | ||||||
|  |           }, | ||||||
|  |           "mappings": [ | ||||||
|  |             { | ||||||
|  |               "if": { | ||||||
|  |                 "and": [ | ||||||
|  |                   "diet:vegetarian=yes" | ||||||
|  |                 ] | ||||||
|  |               }, | ||||||
|  |               "then": { | ||||||
|  |                 "nl": "Er zijn vegetarische snacks aanwezig", | ||||||
|  |                 "fr": "Des collations végétariennes sont disponibles" | ||||||
|  |               } | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               "if": { | ||||||
|  |                 "and": [ | ||||||
|  |                   "diet:vegetarian=limited" | ||||||
|  |                 ] | ||||||
|  |               }, | ||||||
|  |               "then": { | ||||||
|  |                 "nl": "Slechts enkele vegetarische snacks", | ||||||
|  |                 "fr": "Quelques snacks végétariens seulement" | ||||||
|  |               } | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               "if": { | ||||||
|  |                 "and": [ | ||||||
|  |                   "diet:vegetarian=no" | ||||||
|  |                 ] | ||||||
|  |               }, | ||||||
|  |               "then": { | ||||||
|  |                 "nl": "Geen vegetarische snacks beschikbaar", | ||||||
|  |                 "fr": "Pas d'en-cas végétariens disponibles" | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           ] | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "render": { | ||||||
|  |             "nl": "" | ||||||
|  |           }, | ||||||
|  |           "question": { | ||||||
|  |             "nl": "Heeft deze frituur veganistische snacks?", | ||||||
|  |             "fr": "Cette friture est-elle équipée de snacks végétaliens ?" | ||||||
|  |           }, | ||||||
|  |           "mappings": [ | ||||||
|  |             { | ||||||
|  |               "if": { | ||||||
|  |                 "and": [ | ||||||
|  |                   "diet:vegan=yes" | ||||||
|  |                 ] | ||||||
|  |               }, | ||||||
|  |               "then": { | ||||||
|  |                 "nl": "Er zijn veganistische snacks aanwezig", | ||||||
|  |                 "fr": "Des collations végétaliens sont disponibles" | ||||||
|  |               } | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               "if": { | ||||||
|  |                 "and": [ | ||||||
|  |                   "diet:vegan=limited" | ||||||
|  |                 ] | ||||||
|  |               }, | ||||||
|  |               "then": { | ||||||
|  |                 "nl": "Slechts enkele veganistische snacks", | ||||||
|  |                 "fr": "Quelques snacks végétaliens seulement" | ||||||
|  |               } | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               "if": { | ||||||
|  |                 "and": [ | ||||||
|  |                   "diet:vegetarian=no" | ||||||
|  |                 ] | ||||||
|  |               }, | ||||||
|  |               "then": { | ||||||
|  |                 "nl": "Geen veganistische snacks beschikbaar", | ||||||
|  |                 "fr": "Pas d'en-cas végétaliens disponibles" | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           ] | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "question": { | ||||||
|  |             "nl": "Bakt deze frituur in dierlijk  vetof plantaardig olie?", | ||||||
|  |             "fr": "Cette friteuse fonctionne-t-elle avec de la graisse animale ou végétale ?" | ||||||
|  |           }, | ||||||
|  |           "mappings": [ | ||||||
|  |             { | ||||||
|  |               "if": { | ||||||
|  |                 "and": [ | ||||||
|  |                   "friture:oil=vegetable" | ||||||
|  |                 ] | ||||||
|  |               }, | ||||||
|  |               "then": { | ||||||
|  |                 "nl": "Plantaardige olie", | ||||||
|  |                 "fr": "Huile végétale" | ||||||
|  |               } | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               "if": { | ||||||
|  |                 "and": [ | ||||||
|  |                   "friture:oil=animal" | ||||||
|  |                 ] | ||||||
|  |               }, | ||||||
|  |               "then": { | ||||||
|  |                 "nl": "Dierlijk vet", | ||||||
|  |                 "fr": "Graisse animale" | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           ], | ||||||
|  |           "freeform": { | ||||||
|  |             "key": "" | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       ], | ||||||
|  |       "icon": { | ||||||
|  |         "render": "https://upload.wikimedia.org/wikipedia/commons/5/55/French_fries_juliane_kr_r.svg" | ||||||
|  |       }, | ||||||
|  |       "width": { | ||||||
|  |         "render": "8" | ||||||
|  |       }, | ||||||
|  |       "iconSize": { | ||||||
|  |         "render": "40,40,center" | ||||||
|  |       }, | ||||||
|  |       "color": { | ||||||
|  |         "render": "#00f" | ||||||
|  |       }, | ||||||
|  |       "presets": [], | ||||||
|  |       "wayHandling": 1 | ||||||
|  |     } | ||||||
|  |   ], | ||||||
|  |   "roamingRenderings": [], | ||||||
|  |   "shortDescription": {} | ||||||
|  | } | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue