forked from MapComplete/MapComplete
Better handling of metatags, more robust error handling when calculating tags
This commit is contained in:
parent
69363fbf0f
commit
6ac8a5373c
10 changed files with 134 additions and 89 deletions
|
@ -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);
|
||||
|
||||
|
|
|
@ -32,7 +32,19 @@ export class ElementStorage {
|
|||
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)) {
|
||||
const eventSource = new UIEventSource<any>(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<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);
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<number, OsmNode> = new Map<number, OsmNode>()
|
||||
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;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue