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/>",
|
||||||
|
|
|
@ -115,6 +115,12 @@ export default class PublicHolidayInput extends InputElement<string> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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…
Reference in a new issue