forked from MapComplete/MapComplete
Merge develop
This commit is contained in:
commit
b4b72a0995
15 changed files with 336 additions and 249 deletions
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,8 +137,8 @@ export class ExtraFunction {
|
||||||
(params, feature) => {
|
(params, feature) => {
|
||||||
|
|
||||||
return (features, amount, uniqueTag, maxDistanceInMeters) => {
|
return (features, amount, uniqueTag, maxDistanceInMeters) => {
|
||||||
let distance : number = Number(maxDistanceInMeters)
|
let distance: number = Number(maxDistanceInMeters)
|
||||||
if(isNaN(distance)){
|
if (isNaN(distance)) {
|
||||||
distance = undefined
|
distance = undefined
|
||||||
}
|
}
|
||||||
return ExtraFunction.GetClosestNFeatures(params, feature, features, {
|
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[] = [
|
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,33 +224,30 @@ 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))
|
||||||
features = params.getFeaturesWithin(name, new BBox(bbox.geometry.coordinates))
|
features = params.getFeaturesWithin(name, new BBox(bbox.geometry.coordinates))
|
||||||
}else{
|
} else {
|
||||||
features = [features]
|
features = [features]
|
||||||
}
|
}
|
||||||
if (features === undefined) {
|
if (features === undefined) {
|
||||||
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) {
|
GeoOperations.centerpointCoordinates(otherFeature),
|
||||||
distance = GeoOperations.distanceBetween([otherFeature._lon, otherFeature._lat], [feature._lon, feature._lat]);
|
selfCenter
|
||||||
} else {
|
)
|
||||||
distance = GeoOperations.distanceBetween(
|
if (distance === undefined || distance === null || isNaN(distance)) {
|
||||||
GeoOperations.centerpointCoordinates(otherFeature),
|
|
||||||
[feature._lon, feature._lat]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
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!"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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(_ => {
|
||||||
|
|
|
@ -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";
|
||||||
|
|
||||||
|
@ -33,34 +32,43 @@ export default class MetaTagging {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const metatagsToApply: SimpleMetaTagger [] = []
|
const metatagsToApply: SimpleMetaTagger [] = []
|
||||||
for (const metatag of SimpleMetaTagger.metatags) {
|
for (const metatag of SimpleMetaTagger.metatags) {
|
||||||
if (metatag.includesDates) {
|
if (metatag.includesDates) {
|
||||||
if (options.includeDates ?? true) {
|
if (options.includeDates ?? true) {
|
||||||
metatagsToApply.push(metatag)
|
metatagsToApply.push(metatag)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (options.includeNonDates ?? true) {
|
if (options.includeNonDates ?? true) {
|
||||||
metatagsToApply.push(metatag)
|
metatagsToApply.push(metatag)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The calculated functions - per layer - which add the new keys
|
// The calculated functions - per layer - which add the new keys
|
||||||
const layerFuncs = this.createRetaggingFunc(layer)
|
const layerFuncs = this.createRetaggingFunc(layer)
|
||||||
|
|
||||||
|
|
||||||
for (let i = 0; i < features.length; i++) {
|
for (let i = 0; i < features.length; i++) {
|
||||||
const ff = features[i];
|
const ff = features[i];
|
||||||
const feature = ff.feature
|
const feature = ff.feature
|
||||||
const freshness = ff.freshness
|
const freshness = ff.freshness
|
||||||
let somethingChanged = false
|
let somethingChanged = false
|
||||||
for (const metatag of metatagsToApply) {
|
for (const metatag of metatagsToApply) {
|
||||||
try {
|
try {
|
||||||
if(!metatag.keys.some(key => feature.properties[key] === undefined)){
|
if (!metatag.keys.some(key => feature.properties[key] === undefined)) {
|
||||||
// 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,35 +78,30 @@ export default class MetaTagging {
|
||||||
* thus not running an update!
|
* thus not running an update!
|
||||||
*/
|
*/
|
||||||
somethingChanged = newValueAdded || somethingChanged
|
somethingChanged = newValueAdded || somethingChanged
|
||||||
} catch (e) {
|
|
||||||
console.error("Could not calculate metatag for ", metatag.keys.join(","), ":", e, e.stack)
|
|
||||||
}
|
}
|
||||||
}
|
} 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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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):
|
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,59 +111,72 @@ 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
|
||||||
if (typeof result !== "string") {
|
get: function () {
|
||||||
// Make sure it is a string!
|
try {
|
||||||
result = JSON.stringify(result);
|
// Lazyness for the win!
|
||||||
}
|
let result = func(feature);
|
||||||
feature.properties[key] = result;
|
|
||||||
} catch (e) {
|
if (result === "") {
|
||||||
if (MetaTagging.errorPrintCount < MetaTagging.stopErrorOutputAt) {
|
result === undefined
|
||||||
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 (result !== undefined && typeof result !== "string") {
|
||||||
if (MetaTagging.errorPrintCount == MetaTagging.stopErrorOutputAt) {
|
// Make sure it is a string!
|
||||||
console.error("Got ", MetaTagging.stopErrorOutputAt, " errors calculating this metatagging - stopping output now")
|
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) => {
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 => {
|
||||||
const sqMeters = GeoOperations.surfaceAreaInSqMeters(feature);
|
|
||||||
feature.properties["_surface"] = "" + sqMeters;
|
Object.defineProperty(feature.properties, "_surface", {
|
||||||
feature.properties["_surface:ha"] = "" + Math.floor(sqMeters / 1000) / 10;
|
enumerable: false,
|
||||||
feature.area = sqMeters;
|
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;
|
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,63 +204,73 @@ export default class SimpleMetaTagger {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const tagsSource = State.state.allElements.getEventSourceById(feature.properties.id);
|
Object.defineProperty(feature.properties, "_isOpen",{
|
||||||
tagsSource.addCallbackAndRunD(tags => {
|
enumerable: false,
|
||||||
if (tags.opening_hours === undefined || tags._country === undefined) {
|
configurable: true,
|
||||||
return;
|
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;
|
||||||
|
|
||||||
|
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(
|
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" : ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
// The tag was not found
|
||||||
}
|
|
||||||
if (this.key === propertiesKey) {
|
|
||||||
const value = properties[propertiesKey];
|
|
||||||
if (value === undefined) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return value === this.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 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[] {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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": {
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
|
]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue