diff --git a/Logic/Actors/UpdateFromOverpass.ts b/Logic/Actors/UpdateFromOverpass.ts index 77c9ab7b3d..0abed35705 100644 --- a/Logic/Actors/UpdateFromOverpass.ts +++ b/Logic/Actors/UpdateFromOverpass.ts @@ -7,6 +7,7 @@ import Bounds from "../../Models/Bounds"; import FeatureSource from "../FeatureSource/FeatureSource"; import {Utils} from "../../Utils"; import {TagsFilter} from "../Tags/TagsFilter"; +import SimpleMetaTagger from "../SimpleMetaTagger"; export default class UpdateFromOverpass implements FeatureSource { @@ -158,14 +159,17 @@ export default class UpdateFromOverpass implements FeatureSource { function (data, date) { self._previousBounds.get(z).push(queryBounds); self.retries.setData(0); - self.features.setData(data.features.map(f => ({feature: f, freshness: date}))); + const features = data.features.map(f => ({feature: f, freshness: date})); + SimpleMetaTagger.objectMetaInfo.addMetaTags(features) + + self.features.setData(features); self.runningQuery.setData(false); }, function (reason) { self.retries.data++; self.ForceRefresh(); self.timeout.setData(self.retries.data * 5); - console.log(`QUERY FAILED (retrying in ${5 * self.retries.data} sec)`); + console.log(`QUERY FAILED (retrying in ${5 * self.retries.data} sec) due to ${reason}`); self.retries.ping(); self.runningQuery.setData(false); diff --git a/Logic/ElementStorage.ts b/Logic/ElementStorage.ts index 8d518e42e0..acb8ec3348 100644 --- a/Logic/ElementStorage.ts +++ b/Logic/ElementStorage.ts @@ -32,7 +32,19 @@ export class ElementStorage { return es; } - addOrGetById(elementId: string, newProperties: any): UIEventSource { + getEventSourceById(elementId): UIEventSource { + if (this._elements.has(elementId)) { + return this._elements.get(elementId); + } + console.error("Can not find eventsource with id ", elementId); + return undefined; + } + + has(id) { + return this._elements.has(id); + } + + private addOrGetById(elementId: string, newProperties: any): UIEventSource { if (!this._elements.has(elementId)) { const eventSource = new UIEventSource(newProperties, "tags of " + elementId); this._elements.set(elementId, eventSource); @@ -48,30 +60,33 @@ export class ElementStorage { const keptKeys = es.data; // The element already exists // We use the new feature to overwrite all the properties in the already existing eventsource - console.log("Merging multiple instances of ", elementId) + const debug_msg = [] let somethingChanged = false; for (const k in newProperties) { const v = newProperties[k]; + if (keptKeys[k] !== v) { - keptKeys[k] = v; + + if (v === undefined) { + // The new value is undefined; the tag might have been removed + // It might be a metatag as well + // In the latter case, we do keep the tag! + if (!k.startsWith("_")) { + delete keptKeys[k] + debug_msg.push(("Erased " + k)) + } + } else { + keptKeys[k] = v; + debug_msg.push(k + " --> " + v) + } + somethingChanged = true; } } if (somethingChanged) { + console.trace(`Merging multiple instances of ${elementId}: ` + debug_msg.join(", ")+" newProperties: ", newProperties) es.ping(); } return es; } - - getEventSourceById(elementId): UIEventSource { - if (this._elements.has(elementId)) { - return this._elements.get(elementId); - } - console.error("Can not find eventsource with id ", elementId); - return undefined; - } - - has(id) { - return this._elements.has(id); - } } \ No newline at end of file diff --git a/Logic/FeatureSource/RegisteringFeatureSource.ts b/Logic/FeatureSource/RegisteringFeatureSource.ts index 48fc315de7..7c246ee673 100644 --- a/Logic/FeatureSource/RegisteringFeatureSource.ts +++ b/Logic/FeatureSource/RegisteringFeatureSource.ts @@ -3,16 +3,15 @@ import {UIEventSource} from "../UIEventSource"; import State from "../../State"; export default class RegisteringFeatureSource implements FeatureSource { -public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>; -public readonly name; + public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>; + public readonly name; + constructor(source: FeatureSource) { this.features = source.features; - this.name = "RegisteringSource of "+source.name; + this.name = "RegisteringSource of " + source.name; this.features.addCallbackAndRun(features => { for (const feature of features ?? []) { - if (!State.state.allElements.has(feature.feature.properties.id)) { - State.state.allElements.addOrGetElement(feature.feature) - } + State.state.allElements.addOrGetElement(feature.feature) } }) } diff --git a/Logic/MetaTagging.ts b/Logic/MetaTagging.ts index ecce20409c..633e9ab053 100644 --- a/Logic/MetaTagging.ts +++ b/Logic/MetaTagging.ts @@ -62,7 +62,12 @@ export default class MetaTagging { if (f === undefined) { continue; } - f({featuresPerLayer: featuresPerLayer, memberships: relations}, feature.feature) + try{ + f({featuresPerLayer: featuresPerLayer, memberships: relations}, feature.feature) + }catch(e){ + console.error(e) + } + } } @@ -84,10 +89,21 @@ export default class MetaTagging { } const func = new Function("feat", "return " + code + ";"); + try{ + + const f = (featuresPerLayer, feature: any) => { - feature.properties[key] = func(feature); + try{ + feature.properties[key] = func(feature); + }catch(e){ + console.error("Could not calculate a metatag 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") + } + } functions.push(f) + }catch(e){ + console.error("Could not create a dynamic function: ", e) + } } return (params: Params, feature) => { const tags = feature.properties diff --git a/Logic/Osm/OsmObject.ts b/Logic/Osm/OsmObject.ts index c6c607cf73..a3d6c8535f 100644 --- a/Logic/Osm/OsmObject.ts +++ b/Logic/Osm/OsmObject.ts @@ -67,7 +67,46 @@ export abstract class OsmObject { }) } - private static ParseObjects(elements: any[]) : OsmObject[]{ + // bounds should be: [[maxlat, minlon], [minlat, maxlon]] (same as Utils.tile_bounds) + public static LoadArea(bounds: [[number, number], [number, number]], callback: (objects: OsmObject[]) => void) { + const minlon = bounds[0][1] + const maxlon = bounds[1][1] + const minlat = bounds[1][0] + const maxlat = bounds[0][0]; + const url = `https://www.openstreetmap.org/api/0.6/map.json?bbox=${minlon},${minlat},${maxlon},${maxlat}` + $.getJSON(url, data => { + const elements: any[] = data.elements; + const objects = OsmObject.ParseObjects(elements) + callback(objects); + + }) + } + + //Loads an area from the OSM-api. + + public static DownloadAll(neededIds, knownElements: any = {}, continuation: ((knownObjects: any) => void)) { + // local function which downloads all the objects one by one + // this is one big loop, running one download, then rerunning the entire function + if (neededIds.length == 0) { + continuation(knownElements); + return; + } + const neededId = neededIds.pop(); + + if (neededId in knownElements) { + OsmObject.DownloadAll(neededIds, knownElements, continuation); + return; + } + + OsmObject.DownloadObject(neededId, + function (element) { + knownElements[neededId] = element; // assign the element for later, continue downloading the next element + OsmObject.DownloadAll(neededIds, knownElements, continuation); + } + ); + } + + private static ParseObjects(elements: any[]): OsmObject[] { const objects: OsmObject[] = []; const allNodes: Map = new Map() for (const element of elements) { @@ -96,44 +135,6 @@ export abstract class OsmObject { } return objects; } - - //Loads an area from the OSM-api. - // bounds should be: [[maxlat, minlon], [minlat, maxlon]] (same as Utils.tile_bounds) - public static LoadArea(bounds: [[number, number], [number, number]], callback: (objects: OsmObject[]) => void) { - const minlon = bounds[0][1] - const maxlon = bounds[1][1] - const minlat = bounds[1][0] - const maxlat = bounds[0][0]; - const url = `https://www.openstreetmap.org/api/0.6/map.json?bbox=${minlon},${minlat},${maxlon},${maxlat}` - $.getJSON(url, data => { - const elements: any[] = data.elements; - const objects = OsmObject.ParseObjects(elements) - callback(objects); - - }) - } - - public static DownloadAll(neededIds, knownElements: any = {}, continuation: ((knownObjects: any) => void)) { - // local function which downloads all the objects one by one - // this is one big loop, running one download, then rerunning the entire function - if (neededIds.length == 0) { - continuation(knownElements); - return; - } - const neededId = neededIds.pop(); - - if (neededId in knownElements) { - OsmObject.DownloadAll(neededIds, knownElements, continuation); - return; - } - - OsmObject.DownloadObject(neededId, - function (element) { - knownElements[neededId] = element; // assign the element for later, continue downloading the next element - OsmObject.DownloadAll(neededIds, knownElements, continuation); - } - ); - } // The centerpoint of the feature, as [lat, lon] public abstract centerpoint(): [number, number]; @@ -149,10 +150,10 @@ export abstract class OsmObject { TagsXML(): string { let tags = ""; for (const key in this.tags) { - if(key.startsWith("_")){ + if (key.startsWith("_")) { continue; } - if(key === "id"){ + if (key === "id") { continue; } const v = this.tags[key]; @@ -168,24 +169,25 @@ export abstract class OsmObject { const full = this.type !== "way" ? "" : "/full"; const url = "https://www.openstreetmap.org/api/0.6/" + this.type + "/" + this.id + full; $.getJSON(url, function (data) { - + const element = data.elements.pop(); let nodes = [] - if(data.elements.length > 2){ + if (data.elements.length > 2) { nodes = OsmObject.ParseObjects(data.elements) } - + self.LoadData(element) self.SaveExtraData(element, nodes); - - continuation(self, { + const meta = { "_last_edit:contributor": element.user, "_last_edit:contributor:uid": element.uid, "_last_edit:changeset": element.changeset, "_last_edit:timestamp": new Date(element.timestamp), "_version_number": element.version - }); + } + + continuation(self, meta); } ); return this; @@ -220,7 +222,7 @@ export abstract class OsmObject { this.version = element.version; this.timestamp = element.timestamp; const tgs = this.tags; - if(element.tags === undefined){ + if (element.tags === undefined) { // Simple node which is part of a way - not important return; } @@ -230,8 +232,6 @@ export abstract class OsmObject { tgs["_last_edit:timestamp"] = element.timestamp tgs["_version_number"] = element.version tgs["id"] = this.type + "/" + this.id; - - } } diff --git a/Logic/Osm/Overpass.ts b/Logic/Osm/Overpass.ts index 788866155e..7a4cbfb79d 100644 --- a/Logic/Osm/Overpass.ts +++ b/Logic/Osm/Overpass.ts @@ -45,6 +45,7 @@ export class Overpass { // @ts-ignore const geojson = OsmToGeoJson.default(json); const osmTime = new Date(json.osm3s.timestamp_osm_base); + continuation(geojson, osmTime); }).fail(onFail) diff --git a/Logic/SimpleMetaTagger.ts b/Logic/SimpleMetaTagger.ts index c1f942550d..f350be44d2 100644 --- a/Logic/SimpleMetaTagger.ts +++ b/Logic/SimpleMetaTagger.ts @@ -31,17 +31,20 @@ export default class SimpleMetaTagger { (feature) => {/*Note: also handled by 'UpdateTagsFromOsmAPI'*/ const tgs = feature.properties; - tgs["_last_edit:contributor"] = tgs["user"] - tgs["_last_edit:contributor:uid"] = tgs["uid"] - tgs["_last_edit:changeset"] = tgs["changeset"] - tgs["_last_edit:timestamp"] = tgs["timestamp"] - tgs["_version_number"] = tgs["version"] - - delete tgs["timestamp"] - delete tgs["version"] - delete tgs["changeset"] - delete tgs["user"] - delete tgs["uid"] + + function move(src: string, target: string){ + if(tgs[src] === undefined){ + return; + } + tgs[target] = tgs[src] + delete tgs[src] + } + + move("user","_last_edit:contributor") + move("uid","_last_edit:contributor:uid") + move("changeset","_last_edit:changeset") + move("timestamp","_last_edit:timestamp") + move("version","_version_number") } ) private static latlon = new SimpleMetaTagger({ diff --git a/Logic/Tags/RegexTag.ts b/Logic/Tags/RegexTag.ts index 987fa36432..e236fa45c9 100644 --- a/Logic/Tags/RegexTag.ts +++ b/Logic/Tags/RegexTag.ts @@ -16,6 +16,9 @@ export class RegexTag extends TagsFilter { } private static doesMatch(fromTag: string, possibleRegex: string | RegExp): boolean { + if(fromTag === undefined){ + return; + } if (typeof possibleRegex === "string") { return fromTag === possibleRegex; } @@ -42,6 +45,9 @@ export class RegexTag extends TagsFilter { matchesProperties(tags: any): boolean { for (const key in tags) { + if(key === undefined){ + continue; + } if (RegexTag.doesMatch(key, this.key)) { const value = tags[key] return RegexTag.doesMatch(value, this.value) != this.invert; diff --git a/UI/Popup/EditableTagRendering.ts b/UI/Popup/EditableTagRendering.ts index c4733d8ea5..0019910a5a 100644 --- a/UI/Popup/EditableTagRendering.ts +++ b/UI/Popup/EditableTagRendering.ts @@ -53,7 +53,9 @@ export default class EditableTagRendering extends UIElement { return this._question.Render(); } if(!this._configuration.IsKnown(this._tags.data)){ - return "" + // Even though it is not known, we hide the question here + // It is the questionbox's task to show the question in edit mode + return ""; } return new Combine([this._answer, diff --git a/UI/Popup/QuestionBox.ts b/UI/Popup/QuestionBox.ts index b04196d985..dca20fcf92 100644 --- a/UI/Popup/QuestionBox.ts +++ b/UI/Popup/QuestionBox.ts @@ -3,7 +3,6 @@ import {UIEventSource} from "../../Logic/UIEventSource"; import TagRenderingConfig from "../../Customizations/JSON/TagRenderingConfig"; import TagRenderingQuestion from "./TagRenderingQuestion"; import Translations from "../i18n/Translations"; -import {TagUtils} from "../../Logic/TagUtils"; /** @@ -44,6 +43,7 @@ export default class QuestionBox extends UIElement { .onClick(() => { self._skippedQuestions.setData([]); }) + this.SetClass("block") } InnerRender(): string { @@ -57,7 +57,6 @@ export default class QuestionBox extends UIElement { if (this._skippedQuestions.data.indexOf(i) >= 0) { continue; } - // this value is NOT known return this._tagRenderingQuestions[i].Render(); }