Merge develop

This commit is contained in:
Pieter Vander Vennet 2021-10-10 23:54:34 +02:00
commit b4b72a0995
15 changed files with 336 additions and 249 deletions

View file

@ -35,9 +35,20 @@ export default class SelectedElementTagsUpdater {
state.selectedElement.addCallbackAndRunD(s => { state.selectedElement.addCallbackAndRunD(s => {
const id = s.properties?.id let id = s.properties?.id
OsmObject.DownloadObjectAsync(id).then(obj => {
SelectedElementTagsUpdater.applyUpdate(state, obj, id) const backendUrl = state.osmConnection._oauth_config.url
if(id.startsWith(backendUrl)){
id = id.substring(backendUrl.length)
}
if(!(id.startsWith("way") || id.startsWith("node") || id.startsWith("relation"))){
// This object is _not_ from OSM, so we skip it!
return;
}
OsmObject.DownloadPropertiesOf(id).then(tags => {
SelectedElementTagsUpdater.applyUpdate(state, tags, id)
}).catch(e => { }).catch(e => {
console.error("Could not update tags of ", id, "due to", e) console.error("Could not update tags of ", id, "due to", e)
}) })
@ -50,13 +61,11 @@ export default class SelectedElementTagsUpdater {
allElements: ElementStorage, allElements: ElementStorage,
changes: Changes, changes: Changes,
osmConnection: OsmConnection osmConnection: OsmConnection
}, obj: OsmObject, id: string }, latestTags: any, id: string
) { ) {
const pendingChanges = state.changes.pendingChanges.data const pendingChanges = state.changes.pendingChanges.data
.filter(change => change.type === obj.type && change.id === obj.id) .filter(change => change.type +"/"+ change.id === id)
.filter(change => change.tags !== undefined); .filter(change => change.tags !== undefined);
const latestTags = obj.tags
console.log("Applying updates of ", id, " got tags", latestTags, "and still have to apply changes: ", pendingChanges)
for (const pendingChange of pendingChanges) { for (const pendingChange of pendingChanges) {
const tagChanges = pendingChange.tags; const tagChanges = pendingChange.tags;
@ -84,7 +93,7 @@ export default class SelectedElementTagsUpdater {
const localValue = currentTags[key] const localValue = currentTags[key]
if (localValue !== osmValue) { if (localValue !== osmValue) {
console.log("Local value:", localValue, "upstream", osmValue) console.log("Local value for ", key ,":", localValue, "upstream", osmValue)
somethingChanged = true; somethingChanged = true;
currentTags[key] = osmValue currentTags[key] = osmValue
} }
@ -92,6 +101,8 @@ export default class SelectedElementTagsUpdater {
if (somethingChanged) { if (somethingChanged) {
console.log("Detected upstream changes to the object when opening it, updating...") console.log("Detected upstream changes to the object when opening it, updating...")
currentTagsSource.ping() currentTagsSource.ping()
}else{
console.debug("Fetched latest tags for ", id, "but detected no changes")
} }
} }

View file

@ -164,32 +164,13 @@ export class ExtraFunction {
} }
) )
private static readonly AspectedRouting = new ExtraFunction(
{
name: "score",
doc: "Given the path of an aspected routing json file, will calculate the score. This score is wrapped in a UIEventSource, so for further calculations, use `.map(score => ...)`" +
"\n\n" +
"For example: `_comfort_score=feat.score('https://raw.githubusercontent.com/pietervdvn/AspectedRouting/master/Examples/bicycle/aspects/bicycle.comfort.json')`",
args: ["path"]
},
(_, feature) => {
return (path) => {
return UIEventSourceTools.downloadJsonCached(path).map(config => {
if (config === undefined) {
return
}
return new AspectedRouting(config).evaluate(feature.properties)
})
}
}
)
private static readonly allFuncs: ExtraFunction[] = [ private static readonly allFuncs: ExtraFunction[] = [
ExtraFunction.DistanceToFunc, ExtraFunction.DistanceToFunc,
ExtraFunction.OverlapFunc, ExtraFunction.OverlapFunc,
ExtraFunction.ClosestObjectFunc, ExtraFunction.ClosestObjectFunc,
ExtraFunction.ClosestNObjectFunc, ExtraFunction.ClosestNObjectFunc,
ExtraFunction.Memberships, ExtraFunction.Memberships
ExtraFunction.AspectedRouting
]; ];
private readonly _name: string; private readonly _name: string;
private readonly _args: string[]; private readonly _args: string[];
@ -243,6 +224,7 @@ export class ExtraFunction {
const maxFeatures = options?.maxFeatures ?? 1 const maxFeatures = options?.maxFeatures ?? 1
const maxDistance = options?.maxDistance ?? 500 const maxDistance = options?.maxDistance ?? 500
const uniqueTag: string | undefined = options?.uniqueTag const uniqueTag: string | undefined = options?.uniqueTag
console.log("Requested closestN")
if (typeof features === "string") { if (typeof features === "string") {
const name = features const name = features
const bbox = GeoOperations.bbox(GeoOperations.buffer(GeoOperations.bbox(feature), maxDistance)) const bbox = GeoOperations.bbox(GeoOperations.buffer(GeoOperations.bbox(feature), maxDistance))
@ -254,22 +236,18 @@ export class ExtraFunction {
return; return;
} }
const selfCenter = GeoOperations.centerpointCoordinates(feature)
let closestFeatures: { feat: any, distance: number }[] = []; let closestFeatures: { feat: any, distance: number }[] = [];
for (const featureList of features) { for (const featureList of features) {
for (const otherFeature of featureList) { for (const otherFeature of featureList) {
if (otherFeature === feature || otherFeature.id === feature.id) { if (otherFeature === feature || otherFeature.id === feature.id) {
continue; // We ignore self continue; // We ignore self
} }
let distance = undefined; const distance = GeoOperations.distanceBetween(
if (otherFeature._lon !== undefined && otherFeature._lat !== undefined) {
distance = GeoOperations.distanceBetween([otherFeature._lon, otherFeature._lat], [feature._lon, feature._lat]);
} else {
distance = GeoOperations.distanceBetween(
GeoOperations.centerpointCoordinates(otherFeature), GeoOperations.centerpointCoordinates(otherFeature),
[feature._lon, feature._lat] selfCenter
) )
} if (distance === undefined || distance === null || isNaN(distance)) {
if (distance === undefined || distance === null) {
console.error("Could not calculate the distance between", feature, "and", otherFeature) console.error("Could not calculate the distance between", feature, "and", otherFeature)
throw "Undefined distance!" throw "Undefined distance!"
} }

View file

@ -4,12 +4,14 @@
* Technically, more an Actor then a featuresource, but it fits more neatly this ay * Technically, more an Actor then a featuresource, but it fits more neatly this ay
*/ */
import {FeatureSourceForLayer} from "../FeatureSource"; import {FeatureSourceForLayer} from "../FeatureSource";
import SimpleMetaTagger from "../../SimpleMetaTagger";
export default class SaveTileToLocalStorageActor { export default class SaveTileToLocalStorageActor {
public static readonly storageKey: string = "cached-features"; public static readonly storageKey: string = "cached-features";
public static readonly formatVersion: string = "1" public static readonly formatVersion: string = "1"
constructor(source: FeatureSourceForLayer, tileIndex: number) { constructor(source: FeatureSourceForLayer, tileIndex: number) {
source.features.addCallbackAndRunD(features => { source.features.addCallbackAndRunD(features => {
const key = `${SaveTileToLocalStorageActor.storageKey}-${source.layer.layerDef.id}-${tileIndex}` const key = `${SaveTileToLocalStorageActor.storageKey}-${source.layer.layerDef.id}-${tileIndex}`
const now = new Date() const now = new Date()

View file

@ -243,7 +243,7 @@ export default class FeaturePipeline {
this.runningQuery = updater.runningQuery.map( this.runningQuery = updater.runningQuery.map(
overpass => { overpass => {
console.log("FeaturePipeline: runningQuery state changed. Overpass", overpass ? "is querying," : "is idle,", console.log("FeaturePipeline: runningQuery state changed. Overpass", overpass ? "is querying," : "is idle,",
"osmFeatureSource is", osmFeatureSource.isRunning ? "is running ("+ +")" : "is idle") "osmFeatureSource is", osmFeatureSource.isRunning ? "is running and needs "+neededTilesFromOsm.data?.length+" tiles (already got "+ osmFeatureSource.downloadedTiles.size +" tiles )" : "is idle")
return overpass || osmFeatureSource.isRunning.data; return overpass || osmFeatureSource.isRunning.data;
}, [osmFeatureSource.isRunning] }, [osmFeatureSource.isRunning]
) )
@ -361,7 +361,6 @@ export default class FeaturePipeline {
const self = this const self = this
window.setTimeout( window.setTimeout(
() => { () => {
console.debug("Applying metatagging onto ", src.name)
const layerDef = src.layer.layerDef; const layerDef = src.layer.layerDef;
MetaTagging.addMetatags( MetaTagging.addMetatags(
src.features.data, src.features.data,
@ -384,7 +383,6 @@ export default class FeaturePipeline {
private updateAllMetaTagging() { private updateAllMetaTagging() {
const self = this; const self = this;
console.log("Reupdating all metatagging")
this.perLayerHierarchy.forEach(hierarchy => { this.perLayerHierarchy.forEach(hierarchy => {
hierarchy.loadedTiles.forEach(src => { hierarchy.loadedTiles.forEach(src => {
self.applyMetaTags(src) self.applyMetaTags(src)

View file

@ -28,7 +28,7 @@ export default class OsmFeatureSource {
}, },
markTileVisited?: (tileId: number) => void markTileVisited?: (tileId: number) => void
}; };
private readonly downloadedTiles = new Set<number>() public readonly downloadedTiles = new Set<number>()
private readonly allowedTags: TagsFilter; private readonly allowedTags: TagsFilter;
constructor(options: { constructor(options: {
@ -53,13 +53,16 @@ export default class OsmFeatureSource {
return; return;
} }
neededTiles = neededTiles.filter(tile => !self.downloadedTiles.has(tile))
if(neededTiles.length == 0){
return;
}
self.isRunning.setData(true) self.isRunning.setData(true)
try { try {
for (const neededTile of neededTiles) { for (const neededTile of neededTiles) {
if (self.downloadedTiles.has(neededTile)) {
continue;
}
console.log("Tile download", Tiles.tile_from_index(neededTile).join("/"), "started") console.log("Tile download", Tiles.tile_from_index(neededTile).join("/"), "started")
self.downloadedTiles.add(neededTile) self.downloadedTiles.add(neededTile)
self.LoadTile(...Tiles.tile_from_index(neededTile)).then(_ => { self.LoadTile(...Tiles.tile_from_index(neededTile)).then(_ => {

View file

@ -1,6 +1,5 @@
import SimpleMetaTagger from "./SimpleMetaTagger"; import SimpleMetaTagger from "./SimpleMetaTagger";
import {ExtraFuncParams, ExtraFunction} from "./ExtraFunction"; import {ExtraFuncParams, ExtraFunction} from "./ExtraFunction";
import {UIEventSource} from "./UIEventSource";
import LayerConfig from "../Models/ThemeConfig/LayerConfig"; import LayerConfig from "../Models/ThemeConfig/LayerConfig";
import State from "../State"; import State from "../State";
@ -61,6 +60,15 @@ export default class MetaTagging {
// All keys are already defined, we probably already ran this one // All keys are already defined, we probably already ran this one
continue continue
} }
if(metatag.isLazy){
somethingChanged = true;
metatag.applyMetaTagsOnFeature(feature, freshness)
}else{
const newValueAdded = metatag.applyMetaTagsOnFeature(feature, freshness) const newValueAdded = metatag.applyMetaTagsOnFeature(feature, freshness)
/* Note that the expression: /* Note that the expression:
* `somethingChanged = newValueAdded || metatag.applyMetaTagsOnFeature(feature, freshness)` * `somethingChanged = newValueAdded || metatag.applyMetaTagsOnFeature(feature, freshness)`
@ -70,6 +78,7 @@ export default class MetaTagging {
* thus not running an update! * thus not running an update!
*/ */
somethingChanged = newValueAdded || somethingChanged somethingChanged = newValueAdded || somethingChanged
}
} catch (e) { } catch (e) {
console.error("Could not calculate metatag for ", metatag.keys.join(","), ":", e, e.stack) console.error("Could not calculate metatag for ", metatag.keys.join(","), ":", e, e.stack)
} }
@ -91,14 +100,8 @@ export default class MetaTagging {
} }
private static createRetaggingFunc(layer: LayerConfig): private static createFunctionsForFeature(calculatedTags: [string, string][]): ((feature: any) => void)[] {
((params: ExtraFuncParams, feature: any) => void) { const functions: ((feature: any) => void)[] = [];
const calculatedTags: [string, string][] = layer.calculatedTags;
if (calculatedTags === undefined) {
return undefined;
}
const functions: ((params: ExtraFuncParams, feature: any) => void)[] = [];
for (const entry of calculatedTags) { for (const entry of calculatedTags) {
const key = entry[0] const key = entry[0]
const code = entry[1]; const code = entry[1];
@ -108,29 +111,27 @@ export default class MetaTagging {
const func = new Function("feat", "return " + code + ";"); const func = new Function("feat", "return " + code + ";");
try { const f = (feature: any) => {
const f = (featuresPerLayer, feature: any) => { delete feature.properties[key]
try {
let result = func(feature);
if (result instanceof UIEventSource) {
result.addCallbackAndRunD(d => {
if (typeof d !== "string") {
// Make sure it is a string!
d = JSON.stringify(d);
}
feature.properties[key] = d;
})
result = result.data
}
if (result === undefined || result === "") { Object.defineProperty(feature.properties, key, {
return; configurable: true,
enumerable: false, // By setting this as not enumerable, the localTileSaver will _not_ calculate this
get: function () {
try {
// Lazyness for the win!
let result = func(feature);
if (result === "") {
result === undefined
} }
if (typeof result !== "string") { if (result !== undefined && typeof result !== "string") {
// Make sure it is a string! // Make sure it is a string!
result = JSON.stringify(result); result = JSON.stringify(result);
} }
delete feature.properties[key]
feature.properties[key] = result; feature.properties[key] = result;
return result;
} catch (e) { } catch (e) {
if (MetaTagging.errorPrintCount < MetaTagging.stopErrorOutputAt) { if (MetaTagging.errorPrintCount < MetaTagging.stopErrorOutputAt) {
console.warn("Could not calculate a calculated tag defined by " + code + " due to " + e + ". This is code defined in the theme. Are you the theme creator? Doublecheck your code. Note that the metatags might not be stable on new features", e, e.stack) console.warn("Could not calculate a calculated tag defined by " + code + " due to " + e + ". This is code defined in the theme. Are you the theme creator? Doublecheck your code. Note that the metatags might not be stable on new features", e, e.stack)
@ -141,26 +142,41 @@ export default class MetaTagging {
} }
} }
}} )
} }
functions.push(f) functions.push(f)
} catch (e) {
console.error("Could not create a dynamic function: ", e)
} }
return functions;
} }
private static createRetaggingFunc(layer: LayerConfig):
((params: ExtraFuncParams, feature: any) => void) {
const calculatedTags: [string, string][] = layer.calculatedTags;
if (calculatedTags === undefined || calculatedTags.length === 0) {
return undefined;
}
return (params: ExtraFuncParams, feature) => { return (params: ExtraFuncParams, feature) => {
const tags = feature.properties const tags = feature.properties
if (tags === undefined) { if (tags === undefined) {
return; return;
} }
ExtraFunction.FullPatchFeature(params, feature);
try { try {
const functions = MetaTagging.createFunctionsForFeature(calculatedTags)
ExtraFunction.FullPatchFeature(params, feature);
for (const f of functions) { for (const f of functions) {
f(params, feature); f(feature);
} }
State.state?.allElements?.getEventSourceById(feature.properties.id)?.ping(); State.state?.allElements?.getEventSourceById(feature.properties.id)?.ping();
} catch (e) { } catch (e) {
console.error("While calculating a tag value: ", e) console.error("Invalid syntax in calculated tags or some other error: ", e)
} }
} }
} }

View file

@ -57,6 +57,19 @@ export abstract class OsmObject {
return src; return src;
} }
static async DownloadPropertiesOf(id: string): Promise<any> {
const splitted = id.split("/");
const type = splitted[0];
const idN = Number(splitted[1]);
if (idN < 0) {
return undefined;
}
const url = `${OsmObject.backendURL}api/0.6/${id}`;
const rawData = await Utils.downloadJson(url)
return rawData.elements[0].tags
}
static async DownloadObjectAsync(id: string): Promise<OsmObject> { static async DownloadObjectAsync(id: string): Promise<OsmObject> {
const splitted = id.split("/"); const splitted = id.split("/");
const type = splitted[0]; const type = splitted[0];
@ -65,7 +78,7 @@ export abstract class OsmObject {
return undefined; return undefined;
} }
const full = !id.startsWith("way") ? "" : "/full"; const full = (id.startsWith("way")) ? "/full" : "";
const url = `${OsmObject.backendURL}api/0.6/${id}${full}`; const url = `${OsmObject.backendURL}api/0.6/${id}${full}`;
const rawData = await Utils.downloadJson(url) const rawData = await Utils.downloadJson(url)
// A full query might contain more then just the requested object (e.g. nodes that are part of a way, where we only want the way) // A full query might contain more then just the requested object (e.g. nodes that are part of a way, where we only want the way)

View file

@ -65,13 +65,34 @@ export default class SimpleMetaTagger {
private static surfaceArea = new SimpleMetaTagger( private static surfaceArea = new SimpleMetaTagger(
{ {
keys: ["_surface", "_surface:ha"], keys: ["_surface", "_surface:ha"],
doc: "The surface area of the feature, in square meters and in hectare. Not set on points and ways" doc: "The surface area of the feature, in square meters and in hectare. Not set on points and ways",
isLazy: true
}, },
(feature => { (feature => {
Object.defineProperty(feature.properties, "_surface", {
enumerable: false,
configurable: true,
get: () => {
const sqMeters = ""+ GeoOperations.surfaceAreaInSqMeters(feature);
delete feature.properties["_surface"]
feature.properties["_surface"] = sqMeters;
return sqMeters
}
})
Object.defineProperty(feature.properties, "_surface:ha", {
enumerable: false,
configurable: true,
get: () => {
const sqMeters = GeoOperations.surfaceAreaInSqMeters(feature); const sqMeters = GeoOperations.surfaceAreaInSqMeters(feature);
feature.properties["_surface"] = "" + sqMeters; const sqMetersHa = "" + Math.floor(sqMeters / 1000) / 10;
feature.properties["_surface:ha"] = "" + Math.floor(sqMeters / 1000) / 10; delete feature.properties["_surface:ha"]
feature.area = sqMeters; feature.properties["_surface:ha"] = sqMetersHa;
return sqMetersHa
}
})
return true; return true;
}) })
); );
@ -173,7 +194,8 @@ export default class SimpleMetaTagger {
{ {
keys: ["_isOpen", "_isOpen:description"], keys: ["_isOpen", "_isOpen:description"],
doc: "If 'opening_hours' is present, it will add the current state of the feature (being 'yes' or 'no')", doc: "If 'opening_hours' is present, it will add the current state of the feature (being 'yes' or 'no')",
includesDates: true includesDates: true,
isLazy: true
}, },
(feature => { (feature => {
if (Utils.runningFromConsole) { if (Utils.runningFromConsole) {
@ -182,16 +204,22 @@ export default class SimpleMetaTagger {
return false return false
} }
Object.defineProperty(feature.properties, "_isOpen",{
enumerable: false,
configurable: true,
get: () => {
delete feature.properties._isOpen
feature.properties._isOpen = ""
const tagsSource = State.state.allElements.getEventSourceById(feature.properties.id); const tagsSource = State.state.allElements.getEventSourceById(feature.properties.id);
tagsSource.addCallbackAndRunD(tags => { tagsSource.addCallbackAndRunD(tags => {
if (tags.opening_hours === undefined || tags._country === undefined) { if (tags.opening_hours === undefined || tags._country === undefined) {
return; return;
} }
try { try {
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
const oh = new opening_hours(tags["opening_hours"], { const oh = new opening_hours(tags["opening_hours"], {
lat: tags._lat, lat: lat,
lon: tags._lon, lon: lon,
address: { address: {
country_code: tags._country.toLowerCase() country_code: tags._country.toLowerCase()
} }
@ -232,13 +260,17 @@ export default class SimpleMetaTagger {
} }
} }
updateTags(); updateTags();
return true; return true; // Our job is done, lets unregister!
} catch (e) { } catch (e) {
console.warn("Error while parsing opening hours of ", tags.id, e); console.warn("Error while parsing opening hours of ", tags.id, e);
tags["_isOpen"] = "parse_error"; tags["_isOpen"] = "parse_error";
} }
}) })
return feature.properties["_isOpen"]
}
})
}) })
) )
private static directionSimplified = new SimpleMetaTagger( private static directionSimplified = new SimpleMetaTagger(
@ -306,8 +338,12 @@ export default class SimpleMetaTagger {
SimpleMetaTagger.objectMetaInfo SimpleMetaTagger.objectMetaInfo
]; ];
public static readonly lazyTags: string[] = [].concat(...SimpleMetaTagger.metatags.filter(tagger => tagger.isLazy)
.map(tagger => tagger.keys));
public readonly keys: string[]; public readonly keys: string[];
public readonly doc: string; public readonly doc: string;
public readonly isLazy: boolean;
public readonly includesDates: boolean public readonly includesDates: boolean
public readonly applyMetaTagsOnFeature: (feature: any, freshness: Date) => boolean; public readonly applyMetaTagsOnFeature: (feature: any, freshness: Date) => boolean;
@ -316,10 +352,11 @@ export default class SimpleMetaTagger {
* @param docs: what does this extra data do? * @param docs: what does this extra data do?
* @param f: apply the changes. Returns true if something changed * @param f: apply the changes. Returns true if something changed
*/ */
constructor(docs: { keys: string[], doc: string, includesDates?: boolean }, constructor(docs: { keys: string[], doc: string, includesDates?: boolean, isLazy?: boolean },
f: ((feature: any, freshness: Date) => boolean)) { f: ((feature: any, freshness: Date) => boolean)) {
this.keys = docs.keys; this.keys = docs.keys;
this.doc = docs.doc; this.doc = docs.doc;
this.isLazy = docs.isLazy
this.applyMetaTagsOnFeature = f; this.applyMetaTagsOnFeature = f;
this.includesDates = docs.includesDates ?? false; this.includesDates = docs.includesDates ?? false;
for (const key of docs.keys) { for (const key of docs.keys) {
@ -345,7 +382,8 @@ export default class SimpleMetaTagger {
for (const metatag of SimpleMetaTagger.metatags) { for (const metatag of SimpleMetaTagger.metatags) {
subElements.push( subElements.push(
new Title(metatag.keys.join(", "), 3), new Title(metatag.keys.join(", "), 3),
metatag.doc metatag.doc,
metatag.isLazy ? "This is a lazy metatag and is only calculated when needed" : ""
) )
} }

View file

@ -47,6 +47,11 @@ export class RegexTag extends TagsFilter {
} }
matchesProperties(tags: any): boolean { matchesProperties(tags: any): boolean {
if(typeof this.key === "string"){
const value = tags[this.key] ?? ""
return RegexTag.doesMatch(value, this.value) != this.invert;
}
for (const key in tags) { for (const key in tags) {
if (key === undefined) { if (key === undefined) {
continue; continue;

View file

@ -23,26 +23,14 @@ export class Tag extends TagsFilter {
matchesProperties(properties: any): boolean { matchesProperties(properties: any): boolean {
for (const propertiesKey in properties) { const foundValue = properties[this.key]
if (!properties.hasOwnProperty(propertiesKey)) { if (foundValue === undefined && (this.value === "" || this.value === undefined)) {
continue
}
if (this.key === propertiesKey) {
const value = properties[propertiesKey];
if (value === undefined) {
continue
}
return value === this.value;
}
}
// The tag was not found // The tag was not found
if (this.value === "") {
// and it shouldn't be found! // and it shouldn't be found!
return true; return true;
} }
return false; return foundValue === this.value;
} }
asOverpass(): string[] { asOverpass(): string[] {

View file

@ -26,7 +26,8 @@ import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSou
import ShowDataMultiLayer from "./ShowDataLayer/ShowDataMultiLayer"; import ShowDataMultiLayer from "./ShowDataLayer/ShowDataMultiLayer";
import Minimap from "./Base/Minimap"; import Minimap from "./Base/Minimap";
import AllImageProviders from "../Logic/ImageProviders/AllImageProviders"; import AllImageProviders from "../Logic/ImageProviders/AllImageProviders";
import WikipediaBox from "./Wikipedia/WikipediaBox"; import WikipediaBox from "./WikipediaBox";
import SimpleMetaTagger from "../Logic/SimpleMetaTagger";
export interface SpecialVisualization { export interface SpecialVisualization {
funcName: string, funcName: string,
@ -45,14 +46,26 @@ export default class SpecialVisualizations {
docs: "Prints all key-value pairs of the object - used for debugging", docs: "Prints all key-value pairs of the object - used for debugging",
args: [], args: [],
constr: ((state: State, tags: UIEventSource<any>) => { constr: ((state: State, tags: UIEventSource<any>) => {
const calculatedTags = [].concat(
SimpleMetaTagger.lazyTags,
... state.layoutToUse.layers.map(l => l.calculatedTags?.map(c => c[0]) ?? []))
return new VariableUiElement(tags.map(tags => { return new VariableUiElement(tags.map(tags => {
const parts = []; const parts = [];
for (const key in tags) { for (const key in tags) {
if (!tags.hasOwnProperty(key)) { if (!tags.hasOwnProperty(key)) {
continue; continue
} }
parts.push([key, tags[key] ?? "<b>undefined</b>"]); parts.push([key, tags[key] ?? "<b>undefined</b>"]);
} }
for(const key of calculatedTags){
const value = tags[key]
if(value === undefined){
continue
}
parts.push([ "<i>"+key+"</i>", value ])
}
return new Table( return new Table(
["key", "value"], ["key", "value"],
parts parts

View file

@ -27,9 +27,6 @@
] ]
} }
}, },
"calculatedTags": [
"_comfort_score=feat.score('https://raw.githubusercontent.com/pietervdvn/AspectedRouting/master/Examples/bicycle/aspects/bicycle.comfort.json')"
],
"title": { "title": {
"render": { "render": {
"en": "Cycleways", "en": "Cycleways",

View file

@ -152,7 +152,7 @@
] ]
}, },
"geoJson": "https://pietervdvn.github.io/speelplekken_cache/speelplekken_{layer}_{z}_{x}_{y}.geojson", "geoJson": "https://pietervdvn.github.io/speelplekken_cache/speelplekken_{layer}_{z}_{x}_{y}.geojson",
"geoJsonZoomLevel": 11, "geoJsonZoomLevel": 14,
"isOsmCache": true "isOsmCache": true
}, },
"title": { "title": {

View file

@ -80,8 +80,7 @@
{ {
"id": "uk_addresses_import_button", "id": "uk_addresses_import_button",
"render": "{import_button(ref:inspireid=$inspireid, Add this address, ./assets/themes/uk_addresses/housenumber_add.svg)}" "render": "{import_button(ref:inspireid=$inspireid, Add this address, ./assets/themes/uk_addresses/housenumber_add.svg)}"
}, }
"all_tags"
], ],
"calculatedTags": [ "calculatedTags": [
"_embedding_object=feat.overlapWith('addresses')[0]?.feat?.properties ?? null", "_embedding_object=feat.overlapWith('addresses')[0]?.feat?.properties ?? null",
@ -122,16 +121,10 @@
} }
}, },
"calculatedTags": [ "calculatedTags": [
"_closest_3_street_names=feat.properties['addr:street'] === undefined ? feat.closestn('named_streets',3, 'name').map(f => ({name: f.feat.properties.name, distance: Math.round(1000*f.distance), id: f.id})) : []", "_closest_3_street_names=feat.closestn('named_streets',3, 'name').map(f => f.feat.properties.name)",
"_closest_street:0:name=JSON.parse(feat.properties._closest_3_street_names)[0]?.name", "_closest_street:0:name=JSON.parse(feat.properties._closest_3_street_names)[0]",
"_closest_street:1:name=JSON.parse(feat.properties._closest_3_street_names)[1]?.name", "_closest_street:1:name=JSON.parse(feat.properties._closest_3_street_names)[1]",
"_closest_street:2:name=JSON.parse(feat.properties._closest_3_street_names)[2]?.name", "_closest_street:2:name=JSON.parse(feat.properties._closest_3_street_names)[2]"
"_closest_street:0:distance=JSON.parse(feat.properties._closest_3_street_names)[0]?.distance",
"_closest_street:1:distance=JSON.parse(feat.properties._closest_3_street_names)[1]?.distance",
"_closest_street:2:distance=JSON.parse(feat.properties._closest_3_street_names)[2]?.distance",
"_closest_street:0:id=JSON.parse(feat.properties._closest_3_street_names)[0]?.id",
"_closest_street:1:id=JSON.parse(feat.properties._closest_3_street_names)[1]?.id",
"_closest_street:2:id=JSON.parse(feat.properties._closest_3_street_names)[2]?.id"
], ],
"title": { "title": {
"render": { "render": {
@ -157,7 +150,8 @@
"en": "What is the number of this house?" "en": "What is the number of this house?"
}, },
"freeform": { "freeform": {
"key": "addr:housenumber" "key": "addr:housenumber",
"addExtraTags": "nohousenumber="
}, },
"mappings": [ "mappings": [
{ {
@ -186,17 +180,17 @@
"mappings": [ "mappings": [
{ {
"if": "addr:street:={_closest_street:0:name}", "if": "addr:street:={_closest_street:0:name}",
"then": "Located in <b>{_closest_street:0:name}</b> (~{_closest_street:0:distance}m away)", "then": "Located in <b>{_closest_street:0:name}</b>",
"hideInAnswer": "_closest_street:0:name=" "hideInAnswer": "_closest_street:0:name="
}, },
{ {
"if": "addr:street:={_closest_street:1:name}", "if": "addr:street:={_closest_street:1:name}",
"then": "Located in <b>{_closest_street:1:name}</b> (~{_closest_street:1:distance}m away)", "then": "Located in <b>{_closest_street:1:name}</b>",
"hideInAnswer": "_closest_street:1:name=" "hideInAnswer": "_closest_street:1:name="
}, },
{ {
"if": "addr:street:={_closest_street:2:name}", "if": "addr:street:={_closest_street:2:name}",
"then": "Located in <b>{_closest_street:2:name}</b> (~{_closest_street:2:distance}m away)", "then": "Located in <b>{_closest_street:2:name}</b>",
"hideInAnswer": "_closest_street:2:name=" "hideInAnswer": "_closest_street:2:name="
} }
], ],
@ -254,7 +248,6 @@
}, },
{ {
"id": "named_streets", "id": "named_streets",
"name": "Named streets",
"minzoom": 18, "minzoom": 18,
"source": { "source": {
"osmTags": { "osmTags": {
@ -264,16 +257,11 @@
] ]
} }
}, },
"title": {
"render": {
"en": "{name}"
}
},
"color": { "color": {
"render": "#ccc" "render": "#ccc"
}, },
"width": { "width": {
"render": "3" "render": "0"
} }
} }
], ],

View file

@ -9,6 +9,7 @@ import {Tag} from "../Logic/Tags/Tag";
import {And} from "../Logic/Tags/And"; import {And} from "../Logic/Tags/And";
import {TagUtils} from "../Logic/Tags/TagUtils"; import {TagUtils} from "../Logic/Tags/TagUtils";
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"; import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig";
import {RegexTag} from "../Logic/Tags/RegexTag";
Utils.runningFromConsole = true; Utils.runningFromConsole = true;
@ -173,7 +174,6 @@ export default class TagSpec extends T {
equal(undefined, tr.GetRenderValue({"foo": "bar"})); equal(undefined, tr.GetRenderValue({"foo": "bar"}));
})], })],
[ [
"Empty match test", "Empty match test",
() => { () => {
@ -214,7 +214,8 @@ export default class TagSpec extends T {
const overpassOrInor = TagUtils.Tag(orInOr).asOverpass() const overpassOrInor = TagUtils.Tag(orInOr).asOverpass()
equal(3, overpassOrInor.length) equal(3, overpassOrInor.length)
} }
], [ ],
[
"Merge touching opening hours", "Merge touching opening hours",
() => { () => {
const oh1: OpeningHour = { const oh1: OpeningHour = {
@ -239,7 +240,8 @@ export default class TagSpec extends T {
equal(r.endHour, 12) equal(r.endHour, 12)
} }
], [ ],
[
"Merge overlapping opening hours", "Merge overlapping opening hours",
() => { () => {
const oh1: OpeningHour = { const oh1: OpeningHour = {
@ -394,7 +396,8 @@ export default class TagSpec extends T {
])); ]));
equal(rules, "Tu 23:00-00:00"); equal(rules, "Tu 23:00-00:00");
}], ["JOIN OH with overflowed hours", () => { }],
["JOIN OH with overflowed hours", () => {
const rules = OH.ToString( const rules = OH.ToString(
OH.MergeTimes([ OH.MergeTimes([
@ -483,7 +486,41 @@ export default class TagSpec extends T {
const tagRendering = new TagRenderingConfig(config, null, "test"); const tagRendering = new TagRenderingConfig(config, null, "test");
equal(true, tagRendering.IsKnown({bottle: "yes"})) equal(true, tagRendering.IsKnown({bottle: "yes"}))
equal(false, tagRendering.IsKnown({})) equal(false, tagRendering.IsKnown({}))
}]]); }],
[
"Tag matches a lazy property",
() => {
const properties = {}
const key = "_key"
Object.defineProperty(properties, key, {
configurable: true,
get: function () {
delete properties[key]
properties[key] = "yes"
return "yes"
}
})
const filter = new Tag("_key", "yes")
T.isTrue(filter.matchesProperties(properties), "Lazy value not matched")
}
],
[
"RegextTag matches a lazy property",
() => {
const properties = {}
const key = "_key"
Object.defineProperty(properties, key, {
configurable: true,
get: function () {
delete properties[key]
properties[key] = "yes"
return "yes"
}
})
const filter = TagUtils.Tag("_key~*")
T.isTrue(filter.matchesProperties(properties), "Lazy value not matched")
}
]]);
} }
} }