Add metatagging, fritures

This commit is contained in:
Pieter Vander Vennet 2020-10-19 12:08:42 +02:00
parent 1e0a1fdf97
commit 99225957cc
15 changed files with 406 additions and 38 deletions

View file

@ -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(),

View file

@ -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);

View file

@ -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
View 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);
}
}
}

View file

@ -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);

View file

@ -8,6 +8,7 @@ export default class CodeGrid {
CodeGrid.grid.getCode(lat, lon, handle);
}
private static InitGrid(): any {
const grid = codegrid.CodeGrid("./tiles/");

View file

@ -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[]>([])

View file

@ -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>";
}

View file

@ -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/>",

View file

@ -114,6 +114,12 @@ export default class PublicHolidayInput extends InputElement<string> {
mode: "off"
}
}
if(str === "PH open"){
return {
mode: "open"
}
}
if (!str.startsWith("PH ")) {
return null;

View file

@ -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[] = []

View file

@ -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);

View file

@ -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;")
})
}
]

View file

@ -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,

View 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": {}
}