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 shops from "../assets/themes/shops/shops.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 {StreetWidth} from "./StreetWidth/StreetWidth";
|
||||
|
||||
|
@ -66,6 +67,7 @@ export class AllKnownLayouts {
|
|||
FromJSON.LayoutFromJSON(nature),
|
||||
FromJSON.LayoutFromJSON(cyclestreets),
|
||||
FromJSON.LayoutFromJSON(maps),
|
||||
FromJSON.LayoutFromJSON(fritures),
|
||||
AllKnownLayouts.GenerateBuurtNatuur(),
|
||||
AllKnownLayouts.GenerateBikeMonitoringStations(),
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ import {DropDown} from "./UI/Input/DropDown";
|
|||
import {LayerSelection} from "./UI/LayerSelection";
|
||||
import {Preset} from "./Customizations/LayerDefinition";
|
||||
import {VariableUiElement} from "./UI/Base/VariableUIElement";
|
||||
import {LayerUpdater} from "./Logic/LayerUpdater";
|
||||
import {UpdateFromOverpass} from "./Logic/UpdateFromOverpass";
|
||||
import {UIEventSource} from "./Logic/UIEventSource";
|
||||
import {QueryParameters} from "./Logic/Web/QueryParameters";
|
||||
import {PersonalLayout} from "./Logic/PersonalLayout";
|
||||
|
@ -451,7 +451,7 @@ export class InitUiElements {
|
|||
)
|
||||
);
|
||||
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;
|
||||
const queryParam = QueryParameters.GetQueryParameter("background", State.state.layoutToUse.data.defaultBackground);
|
||||
|
|
|
@ -110,31 +110,6 @@ export class FilteredLayer {
|
|||
const tags = TagUtils.proprtiesToKV(feature.properties);
|
||||
|
||||
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);
|
||||
} else {
|
||||
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 State from "../State";
|
||||
import {LayerDefinition} from "../Customizations/LayerDefinition";
|
||||
import MetaTagging from "./MetaTagging";
|
||||
|
||||
export class LayerUpdater {
|
||||
export class UpdateFromOverpass {
|
||||
|
||||
public readonly sufficentlyZoomed: UIEventSource<boolean>;
|
||||
public readonly runningQuery: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||
|
@ -100,6 +101,8 @@ export class LayerUpdater {
|
|||
}
|
||||
}
|
||||
|
||||
MetaTagging.addMetatags(geojson.features);
|
||||
|
||||
function renderLayers(layers: FilteredLayer[]) {
|
||||
if (layers.length === 0) {
|
||||
self.runningQuery.setData(false);
|
|
@ -8,6 +8,7 @@ export default class CodeGrid {
|
|||
CodeGrid.grid.getCode(lat, lon, handle);
|
||||
}
|
||||
|
||||
|
||||
private static InitGrid(): any {
|
||||
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 Translations from "./UI/i18n/Translations";
|
||||
import {FilteredLayer} from "./Logic/FilteredLayer";
|
||||
import {LayerUpdater} from "./Logic/LayerUpdater";
|
||||
import {UpdateFromOverpass} from "./Logic/UpdateFromOverpass";
|
||||
import {UIEventSource} from "./Logic/UIEventSource";
|
||||
import {LocalStorageSource} from "./Logic/Web/LocalStorageSource";
|
||||
import {QueryParameters} from "./Logic/Web/QueryParameters";
|
||||
|
@ -66,7 +66,7 @@ export default class State {
|
|||
|
||||
public favouriteLayers: UIEventSource<string[]>;
|
||||
|
||||
public layerUpdater: LayerUpdater;
|
||||
public layerUpdater: UpdateFromOverpass;
|
||||
|
||||
|
||||
public filteredLayers: UIEventSource<FilteredLayer[]> = new UIEventSource<FilteredLayer[]>([])
|
||||
|
|
|
@ -33,7 +33,6 @@ export class TabbedComponent extends UIElement {
|
|||
headerBar = "<div class='tabs-header-bar'>" + headerBar + "</div>"
|
||||
|
||||
const content = this.content[this._source.data];
|
||||
console.log("Rendering tab", this._source.data);
|
||||
return headerBar + "<div class='tab-content'>" + (content?.Render() ?? "") + "</div>";
|
||||
}
|
||||
|
||||
|
|
|
@ -87,7 +87,7 @@ export class ImageUploadFlow extends UIElement {
|
|||
}
|
||||
|
||||
const extraInfo = new Combine([
|
||||
Translations.t.image.respectPrivacy,
|
||||
Translations.t.image.respectPrivacy.SetStyle("font-size:small;"),
|
||||
"<br/>",
|
||||
this._licensePicker,
|
||||
"<br/>",
|
||||
|
|
|
@ -115,6 +115,12 @@ export default class PublicHolidayInput extends InputElement<string> {
|
|||
}
|
||||
}
|
||||
|
||||
if(str === "PH open"){
|
||||
return {
|
||||
mode: "open"
|
||||
}
|
||||
}
|
||||
|
||||
if (!str.startsWith("PH ")) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -17,10 +17,6 @@ export class MoreScreen extends UIElement {
|
|||
super(State.state.locationControl);
|
||||
this.ListenTo(State.state.osmConnection.userDetails);
|
||||
this.ListenTo(State.state.installedThemes);
|
||||
|
||||
State.state.installedThemes.addCallback(themes => {
|
||||
console.log("INSTALLED THEMES COUNT:", themes.length)
|
||||
})
|
||||
}
|
||||
|
||||
private createLinkButton(layout: Layout, customThemeDefinition: string = undefined) {
|
||||
|
@ -72,7 +68,6 @@ export class MoreScreen extends UIElement {
|
|||
|
||||
InnerRender(): string {
|
||||
|
||||
console.log("Inner rendering MORE")
|
||||
const tr = Translations.t.general.morescreen;
|
||||
|
||||
const els: UIElement[] = []
|
||||
|
|
|
@ -33,7 +33,7 @@ export default class OpeningHoursVisualization extends UIElement {
|
|||
|
||||
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
|
||||
start.setDate(from.getDate() - 1);
|
||||
|
||||
|
|
|
@ -166,6 +166,21 @@ export default class SpecialVisualizations {
|
|||
const source = LiveQueryHandler.FetchLiveData(url, shorthands.split(";"));
|
||||
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": {
|
||||
"key": "description:0"
|
||||
}
|
||||
},
|
||||
{"#": "Surface are",
|
||||
"render": {
|
||||
"en": "Surface area: {_surface:ha}Ha",
|
||||
"mappings": {
|
||||
"if": "_surface:ha=0",
|
||||
"then": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"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