Merge develop

This commit is contained in:
pietervdvn 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 => {
const id = s.properties?.id
OsmObject.DownloadObjectAsync(id).then(obj => {
SelectedElementTagsUpdater.applyUpdate(state, obj, id)
let id = s.properties?.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 => {
console.error("Could not update tags of ", id, "due to", e)
})
@ -50,14 +61,12 @@ export default class SelectedElementTagsUpdater {
allElements: ElementStorage,
changes: Changes,
osmConnection: OsmConnection
}, obj: OsmObject, id: string
}, latestTags: any, id: string
) {
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);
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) {
const tagChanges = pendingChange.tags;
for (const tagChange of tagChanges) {
@ -84,7 +93,7 @@ export default class SelectedElementTagsUpdater {
const localValue = currentTags[key]
if (localValue !== osmValue) {
console.log("Local value:", localValue, "upstream", osmValue)
console.log("Local value for ", key ,":", localValue, "upstream", osmValue)
somethingChanged = true;
currentTags[key] = osmValue
}
@ -92,6 +101,8 @@ export default class SelectedElementTagsUpdater {
if (somethingChanged) {
console.log("Detected upstream changes to the object when opening it, updating...")
currentTagsSource.ping()
}else{
console.debug("Fetched latest tags for ", id, "but detected no changes")
}
}

View file

@ -135,10 +135,10 @@ export class ExtraFunction {
args: ["list of features or layer name", "amount of features", "unique tag key (optional)", "maxDistanceInMeters (optional)"]
},
(params, feature) => {
return (features, amount, uniqueTag, maxDistanceInMeters) => {
let distance : number = Number(maxDistanceInMeters)
if(isNaN(distance)){
let distance: number = Number(maxDistanceInMeters)
if (isNaN(distance)) {
distance = undefined
}
return ExtraFunction.GetClosestNFeatures(params, feature, features, {
@ -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[] = [
ExtraFunction.DistanceToFunc,
ExtraFunction.OverlapFunc,
ExtraFunction.ClosestObjectFunc,
ExtraFunction.ClosestNObjectFunc,
ExtraFunction.Memberships,
ExtraFunction.AspectedRouting
ExtraFunction.Memberships
];
private readonly _name: string;
private readonly _args: string[];
@ -243,33 +224,30 @@ export class ExtraFunction {
const maxFeatures = options?.maxFeatures ?? 1
const maxDistance = options?.maxDistance ?? 500
const uniqueTag: string | undefined = options?.uniqueTag
console.log("Requested closestN")
if (typeof features === "string") {
const name = features
const bbox = GeoOperations.bbox(GeoOperations.buffer(GeoOperations.bbox(feature), maxDistance))
features = params.getFeaturesWithin(name, new BBox(bbox.geometry.coordinates))
}else{
} else {
features = [features]
}
if (features === undefined) {
return;
}
const selfCenter = GeoOperations.centerpointCoordinates(feature)
let closestFeatures: { feat: any, distance: number }[] = [];
for(const featureList of features) {
for (const featureList of features) {
for (const otherFeature of featureList) {
if (otherFeature === feature || otherFeature.id === feature.id) {
continue; // We ignore self
}
let distance = undefined;
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),
[feature._lon, feature._lat]
)
}
if (distance === undefined || distance === null) {
const distance = GeoOperations.distanceBetween(
GeoOperations.centerpointCoordinates(otherFeature),
selfCenter
)
if (distance === undefined || distance === null || isNaN(distance)) {
console.error("Could not calculate the distance between", feature, "and", otherFeature)
throw "Undefined distance!"
}

View file

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

View file

@ -243,7 +243,7 @@ export default class FeaturePipeline {
this.runningQuery = updater.runningQuery.map(
overpass => {
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;
}, [osmFeatureSource.isRunning]
)
@ -361,7 +361,6 @@ export default class FeaturePipeline {
const self = this
window.setTimeout(
() => {
console.debug("Applying metatagging onto ", src.name)
const layerDef = src.layer.layerDef;
MetaTagging.addMetatags(
src.features.data,
@ -384,7 +383,6 @@ export default class FeaturePipeline {
private updateAllMetaTagging() {
const self = this;
console.log("Reupdating all metatagging")
this.perLayerHierarchy.forEach(hierarchy => {
hierarchy.loadedTiles.forEach(src => {
self.applyMetaTags(src)

View file

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

View file

@ -1,6 +1,5 @@
import SimpleMetaTagger from "./SimpleMetaTagger";
import {ExtraFuncParams, ExtraFunction} from "./ExtraFunction";
import {UIEventSource} from "./UIEventSource";
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
import State from "../State";
@ -33,34 +32,43 @@ export default class MetaTagging {
}
const metatagsToApply: SimpleMetaTagger [] = []
for (const metatag of SimpleMetaTagger.metatags) {
if (metatag.includesDates) {
if (options.includeDates ?? true) {
metatagsToApply.push(metatag)
}
} else {
if (options.includeNonDates ?? true) {
metatagsToApply.push(metatag)
}
const metatagsToApply: SimpleMetaTagger [] = []
for (const metatag of SimpleMetaTagger.metatags) {
if (metatag.includesDates) {
if (options.includeDates ?? true) {
metatagsToApply.push(metatag)
}
} else {
if (options.includeNonDates ?? true) {
metatagsToApply.push(metatag)
}
}
}
// The calculated functions - per layer - which add the new keys
const layerFuncs = this.createRetaggingFunc(layer)
for (let i = 0; i < features.length; i++) {
const ff = features[i];
const feature = ff.feature
const freshness = ff.freshness
let somethingChanged = false
for (const metatag of metatagsToApply) {
try {
if(!metatag.keys.some(key => feature.properties[key] === undefined)){
// All keys are already defined, we probably already ran this one
continue
}
const ff = features[i];
const feature = ff.feature
const freshness = ff.freshness
let somethingChanged = false
for (const metatag of metatagsToApply) {
try {
if (!metatag.keys.some(key => feature.properties[key] === undefined)) {
// All keys are already defined, we probably already ran this one
continue
}
if(metatag.isLazy){
somethingChanged = true;
metatag.applyMetaTagsOnFeature(feature, freshness)
}else{
const newValueAdded = metatag.applyMetaTagsOnFeature(feature, freshness)
/* Note that the expression:
* `somethingChanged = newValueAdded || metatag.applyMetaTagsOnFeature(feature, freshness)`
@ -70,35 +78,30 @@ export default class MetaTagging {
* thus not running an update!
*/
somethingChanged = newValueAdded || somethingChanged
} catch (e) {
console.error("Could not calculate metatag for ", metatag.keys.join(","), ":", e, e.stack)
}
}
if(layerFuncs !== undefined){
try {
layerFuncs(params, feature)
} catch (e) {
console.error(e)
}
somethingChanged = true
}
if(somethingChanged){
State.state?.allElements?.getEventSourceById(feature.properties.id)?.ping()
} catch (e) {
console.error("Could not calculate metatag for ", metatag.keys.join(","), ":", e, e.stack)
}
}
if (layerFuncs !== undefined) {
try {
layerFuncs(params, feature)
} catch (e) {
console.error(e)
}
somethingChanged = true
}
if (somethingChanged) {
State.state?.allElements?.getEventSourceById(feature.properties.id)?.ping()
}
}
}
private static createRetaggingFunc(layer: LayerConfig):
((params: ExtraFuncParams, feature: any) => void) {
const calculatedTags: [string, string][] = layer.calculatedTags;
if (calculatedTags === undefined) {
return undefined;
}
const functions: ((params: ExtraFuncParams, feature: any) => void)[] = [];
private static createFunctionsForFeature(calculatedTags: [string, string][]): ((feature: any) => void)[] {
const functions: ((feature: any) => void)[] = [];
for (const entry of calculatedTags) {
const key = entry[0]
const code = entry[1];
@ -108,59 +111,72 @@ export default class MetaTagging {
const func = new Function("feat", "return " + code + ";");
try {
const f = (featuresPerLayer, feature: any) => {
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
}
const f = (feature: any) => {
delete feature.properties[key]
if (result === undefined || result === "") {
return;
}
if (typeof result !== "string") {
// Make sure it is a string!
result = JSON.stringify(result);
}
feature.properties[key] = result;
} catch (e) {
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)
MetaTagging.errorPrintCount++;
if (MetaTagging.errorPrintCount == MetaTagging.stopErrorOutputAt) {
console.error("Got ", MetaTagging.stopErrorOutputAt, " errors calculating this metatagging - stopping output now")
Object.defineProperty(feature.properties, key, {
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 (result !== undefined && typeof result !== "string") {
// Make sure it is a string!
result = JSON.stringify(result);
}
delete feature.properties[key]
feature.properties[key] = result;
return result;
} catch (e) {
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)
MetaTagging.errorPrintCount++;
if (MetaTagging.errorPrintCount == MetaTagging.stopErrorOutputAt) {
console.error("Got ", MetaTagging.stopErrorOutputAt, " errors calculating this metatagging - stopping output now")
}
}
}
}
}} )
}
functions.push(f)
} catch (e) {
console.error("Could not create a dynamic function: ", e)
}
functions.push(f)
}
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) => {
const tags = feature.properties
if (tags === undefined) {
return;
}
ExtraFunction.FullPatchFeature(params, feature);
try {
const functions = MetaTagging.createFunctionsForFeature(calculatedTags)
ExtraFunction.FullPatchFeature(params, feature);
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) {
console.error("While calculating a tag value: ", e)
console.error("Invalid syntax in calculated tags or some other error: ", e)
}
}
}

View file

@ -56,6 +56,19 @@ export abstract class OsmObject {
OsmObject.objectCache.set(id, 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> {
const splitted = id.split("/");
@ -65,7 +78,7 @@ export abstract class OsmObject {
return undefined;
}
const full = !id.startsWith("way") ? "" : "/full";
const full = (id.startsWith("way")) ? "/full" : "";
const url = `${OsmObject.backendURL}api/0.6/${id}${full}`;
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)

View file

@ -65,13 +65,34 @@ export default class SimpleMetaTagger {
private static surfaceArea = new SimpleMetaTagger(
{
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 => {
const sqMeters = GeoOperations.surfaceAreaInSqMeters(feature);
feature.properties["_surface"] = "" + sqMeters;
feature.properties["_surface:ha"] = "" + Math.floor(sqMeters / 1000) / 10;
feature.area = sqMeters;
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 sqMetersHa = "" + Math.floor(sqMeters / 1000) / 10;
delete feature.properties["_surface:ha"]
feature.properties["_surface:ha"] = sqMetersHa;
return sqMetersHa
}
})
return true;
})
);
@ -153,7 +174,7 @@ export default class SimpleMetaTagger {
let centerPoint: any = GeoOperations.centerpoint(feature);
const lat = centerPoint.geometry.coordinates[1];
const lon = centerPoint.geometry.coordinates[0];
SimpleMetaTagger.coder?.GetCountryCodeFor(lon, lat, (countries: string[]) => {
try {
const oldCountry = feature.properties["_country"];
@ -173,7 +194,8 @@ export default class SimpleMetaTagger {
{
keys: ["_isOpen", "_isOpen:description"],
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 => {
if (Utils.runningFromConsole) {
@ -181,64 +203,74 @@ export default class SimpleMetaTagger {
// isOpen is irrelevant
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);
tagsSource.addCallbackAndRunD(tags => {
if (tags.opening_hours === undefined || tags._country === undefined) {
return;
}
try {
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
const oh = new opening_hours(tags["opening_hours"], {
lat: lat,
lon: lon,
address: {
country_code: tags._country.toLowerCase()
}
}, {tag_key: "opening_hours"});
// AUtomatically triggered on the next change
const updateTags = () => {
const oldValueIsOpen = tags["_isOpen"];
const oldNextChange = tags["_isOpen:nextTrigger"] ?? 0;
const tagsSource = State.state.allElements.getEventSourceById(feature.properties.id);
tagsSource.addCallbackAndRunD(tags => {
if (tags.opening_hours === undefined || tags._country === undefined) {
return;
if (oldNextChange > (new Date()).getTime() &&
tags["_isOpen:oldvalue"] === tags["opening_hours"]) {
// Already calculated and should not yet be triggered
return false;
}
tags["_isOpen"] = oh.getState() ? "yes" : "no";
const comment = oh.getComment();
if (comment) {
tags["_isOpen:description"] = comment;
}
if (oldValueIsOpen !== tags._isOpen) {
tagsSource.ping();
}
const nextChange = oh.getNextChange();
if (nextChange !== undefined) {
const timeout = nextChange.getTime() - (new Date()).getTime();
tags["_isOpen:nextTrigger"] = nextChange.getTime();
tags["_isOpen:oldvalue"] = tags.opening_hours
window.setTimeout(
() => {
console.log("Updating the _isOpen tag for ", tags.id, ", it's timer expired after", timeout);
updateTags();
},
timeout
)
}
}
updateTags();
return true; // Our job is done, lets unregister!
} catch (e) {
console.warn("Error while parsing opening hours of ", tags.id, e);
tags["_isOpen"] = "parse_error";
}
})
return feature.properties["_isOpen"]
}
try {
const oh = new opening_hours(tags["opening_hours"], {
lat: tags._lat,
lon: tags._lon,
address: {
country_code: tags._country.toLowerCase()
}
}, {tag_key: "opening_hours"});
// AUtomatically triggered on the next change
const updateTags = () => {
const oldValueIsOpen = tags["_isOpen"];
const oldNextChange = tags["_isOpen:nextTrigger"] ?? 0;
if (oldNextChange > (new Date()).getTime() &&
tags["_isOpen:oldvalue"] === tags["opening_hours"]) {
// Already calculated and should not yet be triggered
return false;
}
tags["_isOpen"] = oh.getState() ? "yes" : "no";
const comment = oh.getComment();
if (comment) {
tags["_isOpen:description"] = comment;
}
if (oldValueIsOpen !== tags._isOpen) {
tagsSource.ping();
}
const nextChange = oh.getNextChange();
if (nextChange !== undefined) {
const timeout = nextChange.getTime() - (new Date()).getTime();
tags["_isOpen:nextTrigger"] = nextChange.getTime();
tags["_isOpen:oldvalue"] = tags.opening_hours
window.setTimeout(
() => {
console.log("Updating the _isOpen tag for ", tags.id, ", it's timer expired after", timeout);
updateTags();
},
timeout
)
}
}
updateTags();
return true;
} catch (e) {
console.warn("Error while parsing opening hours of ", tags.id, e);
tags["_isOpen"] = "parse_error";
}
})
})
)
private static directionSimplified = new SimpleMetaTagger(
@ -306,20 +338,25 @@ export default class SimpleMetaTagger {
SimpleMetaTagger.objectMetaInfo
];
public static readonly lazyTags: string[] = [].concat(...SimpleMetaTagger.metatags.filter(tagger => tagger.isLazy)
.map(tagger => tagger.keys));
public readonly keys: string[];
public readonly doc: string;
public readonly isLazy: boolean;
public readonly includesDates: boolean
public readonly applyMetaTagsOnFeature: (feature: any, freshness: Date) => boolean;
/***
* A function that adds some extra data to a feature
* @param docs: what does this extra data do?
* @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)) {
this.keys = docs.keys;
this.doc = docs.doc;
this.isLazy = docs.isLazy
this.applyMetaTagsOnFeature = f;
this.includesDates = docs.includesDates ?? false;
for (const key of docs.keys) {
@ -345,7 +382,8 @@ export default class SimpleMetaTagger {
for (const metatag of SimpleMetaTagger.metatags) {
subElements.push(
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 {
if(typeof this.key === "string"){
const value = tags[this.key] ?? ""
return RegexTag.doesMatch(value, this.value) != this.invert;
}
for (const key in tags) {
if (key === undefined) {
continue;

View file

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

View file

@ -26,7 +26,8 @@ import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSou
import ShowDataMultiLayer from "./ShowDataLayer/ShowDataMultiLayer";
import Minimap from "./Base/Minimap";
import AllImageProviders from "../Logic/ImageProviders/AllImageProviders";
import WikipediaBox from "./Wikipedia/WikipediaBox";
import WikipediaBox from "./WikipediaBox";
import SimpleMetaTagger from "../Logic/SimpleMetaTagger";
export interface SpecialVisualization {
funcName: string,
@ -45,14 +46,26 @@ export default class SpecialVisualizations {
docs: "Prints all key-value pairs of the object - used for debugging",
args: [],
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 => {
const parts = [];
for (const key in tags) {
if (!tags.hasOwnProperty(key)) {
continue;
continue
}
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(
["key", "value"],
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": {
"render": {
"en": "Cycleways",

View file

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

View file

@ -80,8 +80,7 @@
{
"id": "uk_addresses_import_button",
"render": "{import_button(ref:inspireid=$inspireid, Add this address, ./assets/themes/uk_addresses/housenumber_add.svg)}"
},
"all_tags"
}
],
"calculatedTags": [
"_embedding_object=feat.overlapWith('addresses')[0]?.feat?.properties ?? null",
@ -122,16 +121,10 @@
}
},
"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_street:0:name=JSON.parse(feat.properties._closest_3_street_names)[0]?.name",
"_closest_street:1:name=JSON.parse(feat.properties._closest_3_street_names)[1]?.name",
"_closest_street:2:name=JSON.parse(feat.properties._closest_3_street_names)[2]?.name",
"_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"
"_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]",
"_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]"
],
"title": {
"render": {
@ -157,7 +150,8 @@
"en": "What is the number of this house?"
},
"freeform": {
"key": "addr:housenumber"
"key": "addr:housenumber",
"addExtraTags": "nohousenumber="
},
"mappings": [
{
@ -186,17 +180,17 @@
"mappings": [
{
"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="
},
{
"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="
},
{
"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="
}
],
@ -254,7 +248,6 @@
},
{
"id": "named_streets",
"name": "Named streets",
"minzoom": 18,
"source": {
"osmTags": {
@ -264,16 +257,11 @@
]
}
},
"title": {
"render": {
"en": "{name}"
}
},
"color": {
"render": "#ccc"
},
"width": {
"render": "3"
"render": "0"
}
}
],

View file

@ -9,6 +9,7 @@ import {Tag} from "../Logic/Tags/Tag";
import {And} from "../Logic/Tags/And";
import {TagUtils} from "../Logic/Tags/TagUtils";
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig";
import {RegexTag} from "../Logic/Tags/RegexTag";
Utils.runningFromConsole = true;
@ -173,7 +174,6 @@ export default class TagSpec extends T {
equal(undefined, tr.GetRenderValue({"foo": "bar"}));
})],
[
"Empty match test",
() => {
@ -214,7 +214,8 @@ export default class TagSpec extends T {
const overpassOrInor = TagUtils.Tag(orInOr).asOverpass()
equal(3, overpassOrInor.length)
}
], [
],
[
"Merge touching opening hours",
() => {
const oh1: OpeningHour = {
@ -239,7 +240,8 @@ export default class TagSpec extends T {
equal(r.endHour, 12)
}
], [
],
[
"Merge overlapping opening hours",
() => {
const oh1: OpeningHour = {
@ -394,7 +396,8 @@ export default class TagSpec extends T {
]));
equal(rules, "Tu 23:00-00:00");
}], ["JOIN OH with overflowed hours", () => {
}],
["JOIN OH with overflowed hours", () => {
const rules = OH.ToString(
OH.MergeTimes([
@ -483,7 +486,41 @@ export default class TagSpec extends T {
const tagRendering = new TagRenderingConfig(config, null, "test");
equal(true, tagRendering.IsKnown({bottle: "yes"}))
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")
}
]]);
}
}