Better handling of metatags, more robust error handling when calculating tags

This commit is contained in:
Pieter Vander Vennet 2021-05-10 23:51:03 +02:00
parent 69363fbf0f
commit 6ac8a5373c
10 changed files with 134 additions and 89 deletions

View file

@ -7,6 +7,7 @@ import Bounds from "../../Models/Bounds";
import FeatureSource from "../FeatureSource/FeatureSource"; import FeatureSource from "../FeatureSource/FeatureSource";
import {Utils} from "../../Utils"; import {Utils} from "../../Utils";
import {TagsFilter} from "../Tags/TagsFilter"; import {TagsFilter} from "../Tags/TagsFilter";
import SimpleMetaTagger from "../SimpleMetaTagger";
export default class UpdateFromOverpass implements FeatureSource { export default class UpdateFromOverpass implements FeatureSource {
@ -158,14 +159,17 @@ export default class UpdateFromOverpass implements FeatureSource {
function (data, date) { function (data, date) {
self._previousBounds.get(z).push(queryBounds); self._previousBounds.get(z).push(queryBounds);
self.retries.setData(0); 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); self.runningQuery.setData(false);
}, },
function (reason) { function (reason) {
self.retries.data++; self.retries.data++;
self.ForceRefresh(); self.ForceRefresh();
self.timeout.setData(self.retries.data * 5); 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.retries.ping();
self.runningQuery.setData(false); self.runningQuery.setData(false);

View file

@ -32,7 +32,19 @@ export class ElementStorage {
return es; return es;
} }
addOrGetById(elementId: string, newProperties: any): UIEventSource<any> { getEventSourceById(elementId): UIEventSource<any> {
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<any> {
if (!this._elements.has(elementId)) { if (!this._elements.has(elementId)) {
const eventSource = new UIEventSource<any>(newProperties, "tags of " + elementId); const eventSource = new UIEventSource<any>(newProperties, "tags of " + elementId);
this._elements.set(elementId, eventSource); this._elements.set(elementId, eventSource);
@ -48,30 +60,33 @@ export class ElementStorage {
const keptKeys = es.data; const keptKeys = es.data;
// The element already exists // The element already exists
// We use the new feature to overwrite all the properties in the already existing eventsource // 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; let somethingChanged = false;
for (const k in newProperties) { for (const k in newProperties) {
const v = newProperties[k]; const v = newProperties[k];
if (keptKeys[k] !== v) { 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; somethingChanged = true;
} }
} }
if (somethingChanged) { if (somethingChanged) {
console.trace(`Merging multiple instances of ${elementId}: ` + debug_msg.join(", ")+" newProperties: ", newProperties)
es.ping(); es.ping();
} }
return es; return es;
} }
getEventSourceById(elementId): UIEventSource<any> {
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);
}
} }

View file

@ -3,16 +3,15 @@ import {UIEventSource} from "../UIEventSource";
import State from "../../State"; import State from "../../State";
export default class RegisteringFeatureSource implements FeatureSource { export default class RegisteringFeatureSource implements FeatureSource {
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>; public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>;
public readonly name; public readonly name;
constructor(source: FeatureSource) { constructor(source: FeatureSource) {
this.features = source.features; this.features = source.features;
this.name = "RegisteringSource of "+source.name; this.name = "RegisteringSource of " + source.name;
this.features.addCallbackAndRun(features => { this.features.addCallbackAndRun(features => {
for (const feature of 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)
}
} }
}) })
} }

View file

@ -62,7 +62,12 @@ export default class MetaTagging {
if (f === undefined) { if (f === undefined) {
continue; 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 + ";"); const func = new Function("feat", "return " + code + ";");
try{
const f = (featuresPerLayer, feature: any) => { 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) functions.push(f)
}catch(e){
console.error("Could not create a dynamic function: ", e)
}
} }
return (params: Params, feature) => { return (params: Params, feature) => {
const tags = feature.properties const tags = feature.properties

View file

@ -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 objects: OsmObject[] = [];
const allNodes: Map<number, OsmNode> = new Map<number, OsmNode>() const allNodes: Map<number, OsmNode> = new Map<number, OsmNode>()
for (const element of elements) { for (const element of elements) {
@ -96,44 +135,6 @@ export abstract class OsmObject {
} }
return objects; 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] // The centerpoint of the feature, as [lat, lon]
public abstract centerpoint(): [number, number]; public abstract centerpoint(): [number, number];
@ -149,10 +150,10 @@ export abstract class OsmObject {
TagsXML(): string { TagsXML(): string {
let tags = ""; let tags = "";
for (const key in this.tags) { for (const key in this.tags) {
if(key.startsWith("_")){ if (key.startsWith("_")) {
continue; continue;
} }
if(key === "id"){ if (key === "id") {
continue; continue;
} }
const v = this.tags[key]; const v = this.tags[key];
@ -168,24 +169,25 @@ export abstract class OsmObject {
const full = this.type !== "way" ? "" : "/full"; const full = this.type !== "way" ? "" : "/full";
const url = "https://www.openstreetmap.org/api/0.6/" + this.type + "/" + this.id + full; const url = "https://www.openstreetmap.org/api/0.6/" + this.type + "/" + this.id + full;
$.getJSON(url, function (data) { $.getJSON(url, function (data) {
const element = data.elements.pop(); const element = data.elements.pop();
let nodes = [] let nodes = []
if(data.elements.length > 2){ if (data.elements.length > 2) {
nodes = OsmObject.ParseObjects(data.elements) nodes = OsmObject.ParseObjects(data.elements)
} }
self.LoadData(element) self.LoadData(element)
self.SaveExtraData(element, nodes); self.SaveExtraData(element, nodes);
const meta = {
continuation(self, {
"_last_edit:contributor": element.user, "_last_edit:contributor": element.user,
"_last_edit:contributor:uid": element.uid, "_last_edit:contributor:uid": element.uid,
"_last_edit:changeset": element.changeset, "_last_edit:changeset": element.changeset,
"_last_edit:timestamp": new Date(element.timestamp), "_last_edit:timestamp": new Date(element.timestamp),
"_version_number": element.version "_version_number": element.version
}); }
continuation(self, meta);
} }
); );
return this; return this;
@ -220,7 +222,7 @@ export abstract class OsmObject {
this.version = element.version; this.version = element.version;
this.timestamp = element.timestamp; this.timestamp = element.timestamp;
const tgs = this.tags; const tgs = this.tags;
if(element.tags === undefined){ if (element.tags === undefined) {
// Simple node which is part of a way - not important // Simple node which is part of a way - not important
return; return;
} }
@ -230,8 +232,6 @@ export abstract class OsmObject {
tgs["_last_edit:timestamp"] = element.timestamp tgs["_last_edit:timestamp"] = element.timestamp
tgs["_version_number"] = element.version tgs["_version_number"] = element.version
tgs["id"] = this.type + "/" + this.id; tgs["id"] = this.type + "/" + this.id;
} }
} }

View file

@ -45,6 +45,7 @@ export class Overpass {
// @ts-ignore // @ts-ignore
const geojson = OsmToGeoJson.default(json); const geojson = OsmToGeoJson.default(json);
const osmTime = new Date(json.osm3s.timestamp_osm_base); const osmTime = new Date(json.osm3s.timestamp_osm_base);
continuation(geojson, osmTime); continuation(geojson, osmTime);
}).fail(onFail) }).fail(onFail)

View file

@ -31,17 +31,20 @@ export default class SimpleMetaTagger {
(feature) => {/*Note: also handled by 'UpdateTagsFromOsmAPI'*/ (feature) => {/*Note: also handled by 'UpdateTagsFromOsmAPI'*/
const tgs = feature.properties; const tgs = feature.properties;
tgs["_last_edit:contributor"] = tgs["user"]
tgs["_last_edit:contributor:uid"] = tgs["uid"] function move(src: string, target: string){
tgs["_last_edit:changeset"] = tgs["changeset"] if(tgs[src] === undefined){
tgs["_last_edit:timestamp"] = tgs["timestamp"] return;
tgs["_version_number"] = tgs["version"] }
tgs[target] = tgs[src]
delete tgs["timestamp"] delete tgs[src]
delete tgs["version"] }
delete tgs["changeset"]
delete tgs["user"] move("user","_last_edit:contributor")
delete tgs["uid"] 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({ private static latlon = new SimpleMetaTagger({

View file

@ -16,6 +16,9 @@ export class RegexTag extends TagsFilter {
} }
private static doesMatch(fromTag: string, possibleRegex: string | RegExp): boolean { private static doesMatch(fromTag: string, possibleRegex: string | RegExp): boolean {
if(fromTag === undefined){
return;
}
if (typeof possibleRegex === "string") { if (typeof possibleRegex === "string") {
return fromTag === possibleRegex; return fromTag === possibleRegex;
} }
@ -42,6 +45,9 @@ export class RegexTag extends TagsFilter {
matchesProperties(tags: any): boolean { matchesProperties(tags: any): boolean {
for (const key in tags) { for (const key in tags) {
if(key === undefined){
continue;
}
if (RegexTag.doesMatch(key, this.key)) { if (RegexTag.doesMatch(key, this.key)) {
const value = tags[key] const value = tags[key]
return RegexTag.doesMatch(value, this.value) != this.invert; return RegexTag.doesMatch(value, this.value) != this.invert;

View file

@ -53,7 +53,9 @@ export default class EditableTagRendering extends UIElement {
return this._question.Render(); return this._question.Render();
} }
if(!this._configuration.IsKnown(this._tags.data)){ 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, return new Combine([this._answer,

View file

@ -3,7 +3,6 @@ import {UIEventSource} from "../../Logic/UIEventSource";
import TagRenderingConfig from "../../Customizations/JSON/TagRenderingConfig"; import TagRenderingConfig from "../../Customizations/JSON/TagRenderingConfig";
import TagRenderingQuestion from "./TagRenderingQuestion"; import TagRenderingQuestion from "./TagRenderingQuestion";
import Translations from "../i18n/Translations"; import Translations from "../i18n/Translations";
import {TagUtils} from "../../Logic/TagUtils";
/** /**
@ -44,6 +43,7 @@ export default class QuestionBox extends UIElement {
.onClick(() => { .onClick(() => {
self._skippedQuestions.setData([]); self._skippedQuestions.setData([]);
}) })
this.SetClass("block")
} }
InnerRender(): string { InnerRender(): string {
@ -57,7 +57,6 @@ export default class QuestionBox extends UIElement {
if (this._skippedQuestions.data.indexOf(i) >= 0) { if (this._skippedQuestions.data.indexOf(i) >= 0) {
continue; continue;
} }
// this value is NOT known // this value is NOT known
return this._tagRenderingQuestions[i].Render(); return this._tagRenderingQuestions[i].Render();
} }