forked from MapComplete/MapComplete
		
	More work on refactoring the changes handling
This commit is contained in:
		
							parent
							
								
									42391b4ff1
								
							
						
					
					
						commit
						b55f9a25c6
					
				
					 19 changed files with 181 additions and 105 deletions
				
			
		|  | @ -7,7 +7,7 @@ export default class ChangeToElementsActor { | ||||||
|             for (const change of changes) { |             for (const change of changes) { | ||||||
|                 const id = change.type + "/" + change.id; |                 const id = change.type + "/" + change.id; | ||||||
|                 if (!allElements.has(id)) { |                 if (!allElements.has(id)) { | ||||||
| continue; // Will be picked up later on
 |                     continue; // Ignored as the geometryFixer will introduce this
 | ||||||
|                 } |                 } | ||||||
|                 const src = allElements.getEventSourceById(id) |                 const src = allElements.getEventSourceById(id) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -13,7 +13,7 @@ export default class SelectedFeatureHandler { | ||||||
|     private readonly _hash: UIEventSource<string>; |     private readonly _hash: UIEventSource<string>; | ||||||
|     private readonly _selectedFeature: UIEventSource<any>; |     private readonly _selectedFeature: UIEventSource<any>; | ||||||
| 
 | 
 | ||||||
|     private static readonly _no_trigger_on = ["welcome","copyright","layers"] |     private static readonly _no_trigger_on = ["welcome","copyright","layers","new"] | ||||||
|     private readonly _osmApiSource: OsmApiFeatureSource; |     private readonly _osmApiSource: OsmApiFeatureSource; | ||||||
|      |      | ||||||
|     constructor(hash: UIEventSource<string>,  |     constructor(hash: UIEventSource<string>,  | ||||||
|  | @ -60,7 +60,9 @@ export default class SelectedFeatureHandler { | ||||||
|         if(hash === undefined || SelectedFeatureHandler._no_trigger_on.indexOf(hash) >= 0){ |         if(hash === undefined || SelectedFeatureHandler._no_trigger_on.indexOf(hash) >= 0){ | ||||||
|             return; // No valid feature selected
 |             return; // No valid feature selected
 | ||||||
|         } |         } | ||||||
|         // We should have a valid osm-ID and zoom to it
 |         // We should have a valid osm-ID and zoom to it... But we wrap it in try-catch to be sure
 | ||||||
|  |         try{ | ||||||
|  |              | ||||||
|         OsmObject.DownloadObject(hash).addCallbackAndRunD(element => { |         OsmObject.DownloadObject(hash).addCallbackAndRunD(element => { | ||||||
|             const centerpoint = element.centerpoint(); |             const centerpoint = element.centerpoint(); | ||||||
|             console.log("Zooming to location for select point: ", centerpoint) |             console.log("Zooming to location for select point: ", centerpoint) | ||||||
|  | @ -68,6 +70,9 @@ export default class SelectedFeatureHandler { | ||||||
|             location.data.lon = centerpoint[1] |             location.data.lon = centerpoint[1] | ||||||
|             location.ping(); |             location.ping(); | ||||||
|         }) |         }) | ||||||
|  |         }catch(e){ | ||||||
|  |             console.error("Could not download OSM-object with id", hash, " - probably a weird hash") | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     private downloadFeature(hash:  string){ |     private downloadFeature(hash:  string){ | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ import {ChangeDescription} from "../Osm/Actions/ChangeDescription"; | ||||||
| import {Utils} from "../../Utils"; | import {Utils} from "../../Utils"; | ||||||
| import {OsmNode, OsmRelation, OsmWay} from "../Osm/OsmObject"; | import {OsmNode, OsmRelation, OsmWay} from "../Osm/OsmObject"; | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * Applies changes from 'Changes' onto a featureSource |  * Applies changes from 'Changes' onto a featureSource | ||||||
|  */ |  */ | ||||||
|  | @ -16,25 +17,41 @@ export default class ChangeApplicator implements FeatureSource { | ||||||
| 
 | 
 | ||||||
|         this.name = "ChangesApplied(" + source.name + ")" |         this.name = "ChangesApplied(" + source.name + ")" | ||||||
|         this.features = source.features |         this.features = source.features | ||||||
| 
 |         const seenChanges = new Set<ChangeDescription>(); | ||||||
|  |         const self = this; | ||||||
|  |         let runningUpdate = false; | ||||||
|         source.features.addCallbackAndRunD(features => { |         source.features.addCallbackAndRunD(features => { | ||||||
|  |             if(runningUpdate){ | ||||||
|  |                 return; // No need to ping again
 | ||||||
|  |             } | ||||||
|             ChangeApplicator.ApplyChanges(features, changes.pendingChanges.data) |             ChangeApplicator.ApplyChanges(features, changes.pendingChanges.data) | ||||||
|  |             seenChanges.clear() | ||||||
|         }) |         }) | ||||||
| 
 | 
 | ||||||
|         changes.pendingChanges.addCallbackAndRunD(changes => { |         changes.pendingChanges.addCallbackAndRunD(changes => { | ||||||
|             ChangeApplicator.ApplyChanges(source.features.data, changes) |             runningUpdate = true; | ||||||
|  |             changes = changes.filter(ch => !seenChanges.has(ch)) | ||||||
|  |             changes.forEach(c => seenChanges.add(c)) | ||||||
|  |             console.log("Called back", changes) | ||||||
|  |             ChangeApplicator.ApplyChanges(self.features.data, changes) | ||||||
|             source.features.ping() |             source.features.ping() | ||||||
|  |             runningUpdate = false; | ||||||
|         }) |         }) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     private static ApplyChanges(features: { feature: any, freshness: Date }[], cs: ChangeDescription[]) { |     /** | ||||||
|  |      * Returns true if the geometry is changed and the source should be pinged | ||||||
|  |      */ | ||||||
|  |     private static ApplyChanges(features: {feature: any, freshness: Date}[], cs: ChangeDescription[]): boolean { | ||||||
|         if (cs.length === 0 || features === undefined   ) { |         if (cs.length === 0 || features === undefined   ) { | ||||||
|             return features; |             return ; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         console.log("Applying changes ", this.name, cs) | ||||||
|  |         let geometryChanged = false; | ||||||
|         const changesPerId: Map<string, ChangeDescription[]> = new Map<string, ChangeDescription[]>() |         const changesPerId: Map<string, ChangeDescription[]> = new Map<string, ChangeDescription[]>() | ||||||
|         for (const c of cs) { |         for (const c of cs) { | ||||||
|             const id = c.type + "/" + c.id |             const id = c.type + "/" + c.id | ||||||
|  | @ -52,6 +69,8 @@ export default class ChangeApplicator implements FeatureSource { | ||||||
|                 feature: feature, |                 feature: feature, | ||||||
|                 freshness: now |                 freshness: now | ||||||
|             }) |             }) | ||||||
|  |             console.log("Added a new feature: ", feature) | ||||||
|  |             geometryChanged = true; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // First, create the new features - they have a negative ID
 |         // First, create the new features - they have a negative ID
 | ||||||
|  | @ -62,6 +81,10 @@ export default class ChangeApplicator implements FeatureSource { | ||||||
|                     return; // Nothing to do here, already created
 |                     return; // Nothing to do here, already created
 | ||||||
|                 } |                 } | ||||||
|                  |                  | ||||||
|  |                 if(change.changes === undefined){ | ||||||
|  |                     // An update to the object - not the actual created
 | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
| 
 | 
 | ||||||
|                 try { |                 try { | ||||||
| 
 | 
 | ||||||
|  | @ -93,8 +116,8 @@ export default class ChangeApplicator implements FeatureSource { | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         for (const feature of features) { |         for (const feature of features) { | ||||||
|             const id = feature.feature.properties.id; |  | ||||||
|             const f = feature.feature; |             const f = feature.feature; | ||||||
|  |             const id = f.properties.id; | ||||||
|             if (!changesPerId.has(id)) { |             if (!changesPerId.has(id)) { | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
|  | @ -118,11 +141,12 @@ export default class ChangeApplicator implements FeatureSource { | ||||||
| 
 | 
 | ||||||
|                 // Apply other changes to the object
 |                 // Apply other changes to the object
 | ||||||
|                 if (change.changes !== undefined) { |                 if (change.changes !== undefined) { | ||||||
|  |                     geometryChanged = true; | ||||||
|                     switch (change.type) { |                     switch (change.type) { | ||||||
|                         case "node": |                         case "node": | ||||||
|                             // @ts-ignore
 |                             // @ts-ignore
 | ||||||
|                             const coor: { lat, lon } = change.changes; |                             const coor: { lat, lon } = change.changes; | ||||||
|                             f.geometry.coordinates = [[coor.lon, coor.lat]] |                             f.geometry.coordinates = [coor.lon, coor.lat] | ||||||
|                             break; |                             break; | ||||||
|                         case "way": |                         case "way": | ||||||
|                             f.geometry.coordinates = change.changes["locations"] |                             f.geometry.coordinates = change.changes["locations"] | ||||||
|  | @ -134,5 +158,6 @@ export default class ChangeApplicator implements FeatureSource { | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |         return geometryChanged | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -21,9 +21,13 @@ export default class OsmApiFeatureSource implements FeatureSource { | ||||||
|         } |         } | ||||||
|         console.debug("Downloading", id, "from the OSM-API") |         console.debug("Downloading", id, "from the OSM-API") | ||||||
|         OsmObject.DownloadObject(id).addCallbackAndRunD(element => { |         OsmObject.DownloadObject(id).addCallbackAndRunD(element => { | ||||||
|  |             try { | ||||||
|                 const geojson = element.asGeoJson(); |                 const geojson = element.asGeoJson(); | ||||||
|                 geojson.id = geojson.properties.id; |                 geojson.id = geojson.properties.id; | ||||||
|                 this.features.setData([{feature: geojson, freshness: element.timestamp}]) |                 this.features.setData([{feature: geojson, freshness: element.timestamp}]) | ||||||
|  |             } catch (e) { | ||||||
|  |                 console.error(e) | ||||||
|  |             } | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -37,7 +37,7 @@ export default class ChangeTagAction extends OsmChangeAction { | ||||||
|         return {k: key.trim(), v: value.trim()}; |         return {k: key.trim(), v: value.trim()}; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     Perform(changes: Changes): ChangeDescription [] { |     CreateChangeDescriptions(changes: Changes): ChangeDescription [] { | ||||||
|         const changedTags: { k: string, v: string }[] = this._tagsFilter.asChange(this._currentTags).map(ChangeTagAction.checkChange) |         const changedTags: { k: string, v: string }[] = this._tagsFilter.asChange(this._currentTags).map(ChangeTagAction.checkChange) | ||||||
|         const typeId = this._elementId.split("/") |         const typeId = this._elementId.split("/") | ||||||
|         const type = typeId[0] |         const type = typeId[0] | ||||||
|  |  | ||||||
|  | @ -4,23 +4,27 @@ import {Changes} from "../Changes"; | ||||||
| import {ChangeDescription} from "./ChangeDescription"; | import {ChangeDescription} from "./ChangeDescription"; | ||||||
| import {And} from "../../Tags/And"; | import {And} from "../../Tags/And"; | ||||||
| 
 | 
 | ||||||
| export default class CreateNewNodeAction implements OsmChangeAction { | export default class CreateNewNodeAction extends OsmChangeAction { | ||||||
| 
 | 
 | ||||||
|     private readonly _basicTags: Tag[]; |     private readonly _basicTags: Tag[]; | ||||||
|     private readonly _lat: number; |     private readonly _lat: number; | ||||||
|     private readonly _lon: number; |     private readonly _lon: number; | ||||||
| 
 | 
 | ||||||
|  |     public newElementId : string = undefined | ||||||
|  |      | ||||||
|     constructor(basicTags: Tag[], lat: number, lon: number) { |     constructor(basicTags: Tag[], lat: number, lon: number) { | ||||||
|  |         super() | ||||||
|         this._basicTags = basicTags; |         this._basicTags = basicTags; | ||||||
|         this._lat = lat; |         this._lat = lat; | ||||||
|         this._lon = lon; |         this._lon = lon; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     Perform(changes: Changes): ChangeDescription[] { |     CreateChangeDescriptions(changes: Changes): ChangeDescription[] { | ||||||
|         const id = changes.getNewID() |         const id = changes.getNewID() | ||||||
|         const properties = { |         const properties = { | ||||||
|             id: "node/" + id |             id: "node/" + id | ||||||
|         } |         } | ||||||
|  |         this.newElementId = "node/"+id | ||||||
|         for (const kv of this._basicTags) { |         for (const kv of this._basicTags) { | ||||||
|             if (typeof kv.value !== "string") { |             if (typeof kv.value !== "string") { | ||||||
|                 throw "Invalid value: don't use a regex in a preset" |                 throw "Invalid value: don't use a regex in a preset" | ||||||
|  |  | ||||||
|  | @ -7,10 +7,17 @@ import {ChangeDescription} from "./ChangeDescription"; | ||||||
| 
 | 
 | ||||||
| export default abstract class OsmChangeAction { | export default abstract class OsmChangeAction { | ||||||
| 
 | 
 | ||||||
|  |     private isUsed = false | ||||||
| 
 | 
 | ||||||
|  |     public Perform(changes: Changes) { | ||||||
|  |         if (this.isUsed) { | ||||||
|  |             throw "This ChangeAction is already used: " + this.constructor.name | ||||||
|  |         } | ||||||
|  |         this.isUsed = true; | ||||||
|  |         return this.CreateChangeDescriptions(changes) | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     public abstract Perform(changes: Changes): ChangeDescription[] |     protected abstract CreateChangeDescriptions(changes: Changes): ChangeDescription[] | ||||||
|      |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | @ -12,7 +12,7 @@ export default class RelationSplitlHandler extends OsmChangeAction{ | ||||||
|         super() |         super() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     Perform(changes: Changes): ChangeDescription[] { |     CreateChangeDescriptions(changes: Changes): ChangeDescription[] { | ||||||
|         return []; |         return []; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -42,7 +42,7 @@ export default class SplitAction extends OsmChangeAction { | ||||||
|         return wayParts.filter(wp => wp.length > 0) |         return wayParts.filter(wp => wp.length > 0) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     Perform(changes: Changes): ChangeDescription[] { |     CreateChangeDescriptions(changes: Changes): ChangeDescription[] { | ||||||
|         const splitPoints = this._splitPoints |         const splitPoints = this._splitPoints | ||||||
|         // We mark the new split points with a new id
 |         // We mark the new split points with a new id
 | ||||||
|         console.log(splitPoints) |         console.log(splitPoints) | ||||||
|  | @ -72,7 +72,6 @@ export default class SplitAction extends OsmChangeAction { | ||||||
| 
 | 
 | ||||||
|         // Next up is creating actual parts from this
 |         // Next up is creating actual parts from this
 | ||||||
|         const wayParts: SplitInfo[][] = SplitAction.SegmentSplitInfo(splitInfo); |         const wayParts: SplitInfo[][] = SplitAction.SegmentSplitInfo(splitInfo); | ||||||
| console.log("WayParts", wayParts, "by", splitInfo) |  | ||||||
|         // Allright! At this point, we have our new ways!
 |         // Allright! At this point, we have our new ways!
 | ||||||
|         // Which one is the longest of them (and can keep the id)?
 |         // Which one is the longest of them (and can keep the id)?
 | ||||||
| 
 | 
 | ||||||
|  | @ -144,7 +143,7 @@ console.log("WayParts", wayParts, "by", splitInfo) | ||||||
| 
 | 
 | ||||||
|         // At last, we still have to check that we aren't part of a relation...
 |         // At last, we still have to check that we aren't part of a relation...
 | ||||||
|         // At least, the order of the ways is identical, so we can keep the same roles
 |         // At least, the order of the ways is identical, so we can keep the same roles
 | ||||||
|         changeDescription.push(...new RelationSplitlHandler(partOf, newWayIds, originalNodes).Perform(changes)) |         changeDescription.push(...new RelationSplitlHandler(partOf, newWayIds, originalNodes).CreateChangeDescriptions(changes)) | ||||||
| 
 | 
 | ||||||
|         // And we have our objects!
 |         // And we have our objects!
 | ||||||
|         // Time to upload
 |         // Time to upload
 | ||||||
|  |  | ||||||
|  | @ -4,7 +4,6 @@ import {UIEventSource} from "../UIEventSource"; | ||||||
| import Constants from "../../Models/Constants"; | import Constants from "../../Models/Constants"; | ||||||
| import OsmChangeAction from "./Actions/OsmChangeAction"; | import OsmChangeAction from "./Actions/OsmChangeAction"; | ||||||
| import {ChangeDescription} from "./Actions/ChangeDescription"; | import {ChangeDescription} from "./Actions/ChangeDescription"; | ||||||
| import {LocalStorageSource} from "../Web/LocalStorageSource"; |  | ||||||
| import {Utils} from "../../Utils"; | import {Utils} from "../../Utils"; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  | @ -24,24 +23,22 @@ export class Changes { | ||||||
|     public readonly pendingChanges = new UIEventSource<ChangeDescription[]>([]) //  LocalStorageSource.GetParsed<ChangeDescription[]>("pending-changes", [])
 |     public readonly pendingChanges = new UIEventSource<ChangeDescription[]>([]) //  LocalStorageSource.GetParsed<ChangeDescription[]>("pending-changes", [])
 | ||||||
|     private readonly isUploading = new UIEventSource(false); |     private readonly isUploading = new UIEventSource(false); | ||||||
|      |      | ||||||
|  |     private readonly previouslyCreated : OsmObject[] = [] | ||||||
|  | 
 | ||||||
|     constructor() { |     constructor() { | ||||||
|         this.isUploading.addCallbackAndRun(u => { |         | ||||||
|             if (u) { |  | ||||||
|                 console.trace("Uploading set!") |  | ||||||
|             } |  | ||||||
|         }) |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static createChangesetFor(csId: string, |     private static createChangesetFor(csId: string, | ||||||
|                                      allChanges: { |                                      allChanges: { | ||||||
|                                          modifiedObjects?: OsmObject[], |                                          modifiedObjects: OsmObject[], | ||||||
|                                          newElements?: OsmObject[], |                                          newObjects: OsmObject[], | ||||||
|                                          deletedElements?: OsmObject[] |                                          deletedObjects: OsmObject[] | ||||||
|                                      }): string { |                                      }): string { | ||||||
| 
 | 
 | ||||||
|         const changedElements = allChanges.modifiedObjects ?? [] |         const changedElements = allChanges.modifiedObjects ?? [] | ||||||
|         const newElements = allChanges.newElements ?? [] |         const newElements = allChanges.newObjects ?? [] | ||||||
|         const deletedElements = allChanges.deletedElements ?? [] |         const deletedElements = allChanges.deletedObjects ?? [] | ||||||
| 
 | 
 | ||||||
|         let changes = `<osmChange version='0.6' generator='Mapcomplete ${Constants.vNumber}'>`; |         let changes = `<osmChange version='0.6' generator='Mapcomplete ${Constants.vNumber}'>`; | ||||||
|         if (newElements.length > 0) { |         if (newElements.length > 0) { | ||||||
|  | @ -73,7 +70,7 @@ export class Changes { | ||||||
|             .map(c => c.type + "/" + c.id)) |             .map(c => c.type + "/" + c.id)) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static CreateChangesetObjects(changes: ChangeDescription[], downloadedOsmObjects: OsmObject[]): { |     private CreateChangesetObjects(changes: ChangeDescription[], downloadedOsmObjects: OsmObject[]): { | ||||||
|         newObjects: OsmObject[], |         newObjects: OsmObject[], | ||||||
|         modifiedObjects: OsmObject[] |         modifiedObjects: OsmObject[] | ||||||
|         deletedObjects: OsmObject[] |         deletedObjects: OsmObject[] | ||||||
|  | @ -87,12 +84,21 @@ export class Changes { | ||||||
|             states.set(o.type + "/" + o.id, "unchanged") |             states.set(o.type + "/" + o.id, "unchanged") | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         for (const o of this.previouslyCreated) { | ||||||
|  |             objects.set(o.type + "/" + o.id, o)  | ||||||
|  |             states.set(o.type + "/" + o.id, "unchanged") | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         let changed = false; |         let changed = false; | ||||||
|         for (const change of changes) { |         for (const change of changes) { | ||||||
|             const id = change.type + "/" + change.id |             const id = change.type + "/" + change.id | ||||||
|             if (!objects.has(id)) { |             if (!objects.has(id)) { | ||||||
|  |                 if(change.id >= 0){ | ||||||
|  |                     throw "Did not get an object that should be known: "+id | ||||||
|  |                 } | ||||||
|                 // This is a new object that should be created
 |                 // This is a new object that should be created
 | ||||||
|                 states.set(id, "created") |                 states.set(id, "created") | ||||||
|  |                 console.log("Creating object for changeDescription", change) | ||||||
|                 let osmObj: OsmObject = undefined; |                 let osmObj: OsmObject = undefined; | ||||||
|                 switch (change.type) { |                 switch (change.type) { | ||||||
|                     case "node": |                     case "node": | ||||||
|  | @ -116,6 +122,7 @@ export class Changes { | ||||||
|                     throw "Hmm? This is a bug" |                     throw "Hmm? This is a bug" | ||||||
|                 } |                 } | ||||||
|                 objects.set(id, osmObj) |                 objects.set(id, osmObj) | ||||||
|  |                 this.previouslyCreated.push(osmObj) | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             const state = states.get(id) |             const state = states.get(id) | ||||||
|  | @ -195,8 +202,8 @@ export class Changes { | ||||||
|             newObjects: [], |             newObjects: [], | ||||||
|             modifiedObjects: [], |             modifiedObjects: [], | ||||||
|             deletedObjects: [] |             deletedObjects: [] | ||||||
| 
 |  | ||||||
|         } |         } | ||||||
|  |          | ||||||
|         objects.forEach((v, id) => { |         objects.forEach((v, id) => { | ||||||
| 
 | 
 | ||||||
|             const state = states.get(id) |             const state = states.get(id) | ||||||
|  | @ -228,20 +235,18 @@ export class Changes { | ||||||
|      */ |      */ | ||||||
|     public flushChanges(flushreason: string = undefined) { |     public flushChanges(flushreason: string = undefined) { | ||||||
|         if (this.pendingChanges.data.length === 0) { |         if (this.pendingChanges.data.length === 0) { | ||||||
|             console.log("No pending changes") |  | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         if (flushreason !== undefined) { |  | ||||||
|             console.log(flushreason) |  | ||||||
|         } |  | ||||||
|          |          | ||||||
|         if (this.isUploading.data) { |         if (this.isUploading.data) { | ||||||
|             console.log("Is uploading... Abort") |             console.log("Is already uploading... Abort") | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |          | ||||||
|  |        | ||||||
|         this.isUploading.setData(true) |         this.isUploading.setData(true) | ||||||
|         |         | ||||||
|         console.log("Beginning upload..."); |         console.log("Beginning upload... "+flushreason ?? ""); | ||||||
|         // At last, we build the changeset and upload
 |         // At last, we build the changeset and upload
 | ||||||
|         const self = this; |         const self = this; | ||||||
|         const pending = self.pendingChanges.data; |         const pending = self.pendingChanges.data; | ||||||
|  | @ -249,8 +254,12 @@ export class Changes { | ||||||
|         console.log("Needed ids", neededIds) |         console.log("Needed ids", neededIds) | ||||||
|         OsmObject.DownloadAll(neededIds, true).addCallbackAndRunD(osmObjects => { |         OsmObject.DownloadAll(neededIds, true).addCallbackAndRunD(osmObjects => { | ||||||
|             console.log("Got the fresh objects!", osmObjects, "pending: ", pending) |             console.log("Got the fresh objects!", osmObjects, "pending: ", pending) | ||||||
|             const changes = Changes.CreateChangesetObjects(pending, osmObjects) |             const changes: { | ||||||
|             console.log("Changes", changes) |                 newObjects: OsmObject[], | ||||||
|  |                 modifiedObjects: OsmObject[] | ||||||
|  |                 deletedObjects: OsmObject[] | ||||||
|  | 
 | ||||||
|  |             }  = self.CreateChangesetObjects(pending, osmObjects) | ||||||
|             if (changes.newObjects.length + changes.deletedObjects.length + changes.modifiedObjects.length === 0) { |             if (changes.newObjects.length + changes.deletedObjects.length + changes.modifiedObjects.length === 0) { | ||||||
|                 console.log("No changes to be made") |                 console.log("No changes to be made") | ||||||
|                 this.pendingChanges.setData([]) |                 this.pendingChanges.setData([]) | ||||||
|  | @ -262,11 +271,8 @@ export class Changes { | ||||||
|             State.state.osmConnection.UploadChangeset( |             State.state.osmConnection.UploadChangeset( | ||||||
|                 State.state.layoutToUse.data, |                 State.state.layoutToUse.data, | ||||||
|                 State.state.allElements, |                 State.state.allElements, | ||||||
|                 (csId) => { |                 (csId) => Changes.createChangesetFor(csId, changes), | ||||||
|                     return Changes.createChangesetFor(csId, changes); |  | ||||||
|                 }, |  | ||||||
|                 () => { |                 () => { | ||||||
|                     // When done
 |  | ||||||
|                     console.log("Upload successfull!") |                     console.log("Upload successfull!") | ||||||
|                     self.pendingChanges.setData([]); |                     self.pendingChanges.setData([]); | ||||||
|                     self.isUploading.setData(false) |                     self.isUploading.setData(false) | ||||||
|  |  | ||||||
|  | @ -23,7 +23,7 @@ export abstract class OsmObject { | ||||||
|         this.id = id; |         this.id = id; | ||||||
|         this.type = type; |         this.type = type; | ||||||
|         this.tags = { |         this.tags = { | ||||||
|             id: id |             id: `${this.type}/${id}` | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -52,6 +52,9 @@ export abstract class OsmObject { | ||||||
|         const splitted = id.split("/"); |         const splitted = id.split("/"); | ||||||
|         const type = splitted[0]; |         const type = splitted[0]; | ||||||
|         const idN = Number(splitted[1]); |         const idN = Number(splitted[1]); | ||||||
|  |         if(idN <0){ | ||||||
|  |             return; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         OsmObject.objectCache.set(id, src); |         OsmObject.objectCache.set(id, src); | ||||||
|         const newContinuation = (element: OsmObject) => { |         const newContinuation = (element: OsmObject) => { | ||||||
|  | @ -69,7 +72,7 @@ export abstract class OsmObject { | ||||||
|                 new OsmRelation(idN).Download(newContinuation); |                 new OsmRelation(idN).Download(newContinuation); | ||||||
|                 break; |                 break; | ||||||
|             default: |             default: | ||||||
|                 throw "Invalid road type:" + type; |                 throw "Invalid object type:" + type + id; | ||||||
| 
 | 
 | ||||||
|         } |         } | ||||||
|         return src; |         return src; | ||||||
|  | @ -105,7 +108,7 @@ export abstract class OsmObject { | ||||||
|         if (OsmObject.referencingRelationsCache.has(id)) { |         if (OsmObject.referencingRelationsCache.has(id)) { | ||||||
|             return OsmObject.referencingRelationsCache.get(id); |             return OsmObject.referencingRelationsCache.get(id); | ||||||
|         } |         } | ||||||
|         const relsSrc = new UIEventSource<OsmRelation[]>([]) |         const relsSrc = new UIEventSource<OsmRelation[]>(undefined) | ||||||
|         OsmObject.referencingRelationsCache.set(id, relsSrc); |         OsmObject.referencingRelationsCache.set(id, relsSrc); | ||||||
|         Utils.downloadJson(`${OsmObject.backendURL}api/0.6/${id}/relations`) |         Utils.downloadJson(`${OsmObject.backendURL}api/0.6/${id}/relations`) | ||||||
|             .then(data => { |             .then(data => { | ||||||
|  |  | ||||||
|  | @ -6,7 +6,7 @@ export class UIEventSource<T> { | ||||||
|     public data: T; |     public data: T; | ||||||
|     public trace: boolean; |     public trace: boolean; | ||||||
|     private readonly tag: string; |     private readonly tag: string; | ||||||
|     private _callbacks = []; |     private _callbacks: ((t: T) => (boolean | void | any)) [] = []; | ||||||
| 
 | 
 | ||||||
|     constructor(data: T, tag: string = "") { |     constructor(data: T, tag: string = "") { | ||||||
|         this.tag = tag; |         this.tag = tag; | ||||||
|  | @ -63,7 +63,13 @@ export class UIEventSource<T> { | ||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public addCallback(callback: ((latestData: T) => void)): UIEventSource<T> { |     /** | ||||||
|  |      * Adds a callback | ||||||
|  |      * | ||||||
|  |      * If the result of the callback is 'true', the callback is considered finished and will be removed again | ||||||
|  |      * @param callback | ||||||
|  |      */ | ||||||
|  |     public addCallback(callback: ((latestData: T) => (boolean | void | any))): UIEventSource<T> { | ||||||
|         if (callback === console.log) { |         if (callback === console.log) { | ||||||
|             // This ^^^ actually works!
 |             // This ^^^ actually works!
 | ||||||
|             throw "Don't add console.log directly as a callback - you'll won't be able to find it afterwards. Wrap it in a lambda instead." |             throw "Don't add console.log directly as a callback - you'll won't be able to find it afterwards. Wrap it in a lambda instead." | ||||||
|  | @ -90,8 +96,21 @@ export class UIEventSource<T> { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public ping(): void { |     public ping(): void { | ||||||
|  |         let toDelete = undefined | ||||||
|         for (const callback of this._callbacks) { |         for (const callback of this._callbacks) { | ||||||
|             callback(this.data); |             if (callback(this.data) === true) { | ||||||
|  |                 // This callback wants to be deleted
 | ||||||
|  |                 if (toDelete === undefined) { | ||||||
|  |                     toDelete = [callback] | ||||||
|  |                 } else { | ||||||
|  |                     toDelete.push(callback) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         if (toDelete !== undefined) { | ||||||
|  |             for (const toDeleteElement of toDelete) { | ||||||
|  |                 this._callbacks.splice(this._callbacks.indexOf(toDeleteElement), 1) | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										18
									
								
								State.ts
									
										
									
									
									
								
							
							
						
						
									
										18
									
								
								State.ts
									
										
									
									
									
								
							|  | @ -20,7 +20,7 @@ import PendingChangesUploader from "./Logic/Actors/PendingChangesUploader"; | ||||||
| import {Relation} from "./Logic/Osm/ExtractRelations"; | import {Relation} from "./Logic/Osm/ExtractRelations"; | ||||||
| import OsmApiFeatureSource from "./Logic/FeatureSource/OsmApiFeatureSource"; | import OsmApiFeatureSource from "./Logic/FeatureSource/OsmApiFeatureSource"; | ||||||
| import ChangeToElementsActor from "./Logic/Actors/ChangeToElementsActor"; | import ChangeToElementsActor from "./Logic/Actors/ChangeToElementsActor"; | ||||||
| import FeaturePipeline from "./Logic/FeatureSource/FeaturePipeline"; | import FeatureSource from "./Logic/FeatureSource/FeatureSource"; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Contains the global state: a bunch of UI-event sources |  * Contains the global state: a bunch of UI-event sources | ||||||
|  | @ -32,7 +32,7 @@ export default class State { | ||||||
|     public static state: State; |     public static state: State; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     public readonly layoutToUse = new UIEventSource<LayoutConfig>(undefined); |     public readonly layoutToUse = new UIEventSource<LayoutConfig>(undefined, "layoutToUse"); | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      The mapping from id -> UIEventSource<properties> |      The mapping from id -> UIEventSource<properties> | ||||||
|  | @ -45,7 +45,7 @@ export default class State { | ||||||
|     /** |     /** | ||||||
|      The leaflet instance of the big basemap |      The leaflet instance of the big basemap | ||||||
|      */ |      */ | ||||||
|     public leafletMap = new UIEventSource<L.Map>(undefined); |     public leafletMap = new UIEventSource<L.Map>(undefined, "leafletmap"); | ||||||
|     /** |     /** | ||||||
|      * Background layer id |      * Background layer id | ||||||
|      */ |      */ | ||||||
|  | @ -70,7 +70,7 @@ export default class State { | ||||||
|     }[]> = new UIEventSource<{ |     }[]> = new UIEventSource<{ | ||||||
|         readonly isDisplayed: UIEventSource<boolean>, |         readonly isDisplayed: UIEventSource<boolean>, | ||||||
|         readonly  layerDef: LayerConfig; |         readonly  layerDef: LayerConfig; | ||||||
|     }[]>([]) |     }[]>([], "filteredLayers") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | @ -101,13 +101,13 @@ export default class State { | ||||||
|     public readonly featureSwitchFakeUser: UIEventSource<boolean>; |     public readonly featureSwitchFakeUser: UIEventSource<boolean>; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     public readonly featurePipeline: FeaturePipeline; |     public featurePipeline: FeatureSource; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * The map location: currently centered lat, lon and zoom |      * The map location: currently centered lat, lon and zoom | ||||||
|      */ |      */ | ||||||
|     public readonly locationControl = new UIEventSource<Loc>(undefined); |     public readonly locationControl = new UIEventSource<Loc>(undefined, "locationControl"); | ||||||
|     public backgroundLayer; |     public backgroundLayer; | ||||||
|     public readonly backgroundLayerId: UIEventSource<string>; |     public readonly backgroundLayerId: UIEventSource<string>; | ||||||
| 
 | 
 | ||||||
|  | @ -149,11 +149,13 @@ export default class State { | ||||||
|                 .syncWith(LocalStorageSource.Get("lon"))); |                 .syncWith(LocalStorageSource.Get("lon"))); | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|             this.locationControl = new UIEventSource<Loc>({ |             this.locationControl.setData({ | ||||||
|                 zoom: Utils.asFloat(zoom.data), |                 zoom: Utils.asFloat(zoom.data), | ||||||
|                 lat: Utils.asFloat(lat.data), |                 lat: Utils.asFloat(lat.data), | ||||||
|                 lon: Utils.asFloat(lon.data), |                 lon: Utils.asFloat(lon.data), | ||||||
|             }).addCallback((latlonz) => { |             }) | ||||||
|  |             this.locationControl.addCallback((latlonz) => { | ||||||
|  |                 // Sync th location controls
 | ||||||
|                 zoom.setData(latlonz.zoom); |                 zoom.setData(latlonz.zoom); | ||||||
|                 lat.setData(latlonz.lat); |                 lat.setData(latlonz.lat); | ||||||
|                 lon.setData(latlonz.lon); |                 lon.setData(latlonz.lon); | ||||||
|  |  | ||||||
|  | @ -21,6 +21,7 @@ import {InputElement} from "../Input/InputElement"; | ||||||
| import Loc from "../../Models/Loc"; | import Loc from "../../Models/Loc"; | ||||||
| import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"; | import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"; | ||||||
| import CreateNewNodeAction from "../../Logic/Osm/Actions/CreateNewNodeAction"; | import CreateNewNodeAction from "../../Logic/Osm/Actions/CreateNewNodeAction"; | ||||||
|  | import Hash from "../../Logic/Web/Hash"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
| * The SimpleAddUI is a single panel, which can have multiple states: | * The SimpleAddUI is a single panel, which can have multiple states: | ||||||
|  | @ -71,10 +72,11 @@ export default class SimpleAddUI extends Toggle { | ||||||
|                     } |                     } | ||||||
|                     return SimpleAddUI.CreateConfirmButton(preset, |                     return SimpleAddUI.CreateConfirmButton(preset, | ||||||
|                         (tags, location) => { |                         (tags, location) => { | ||||||
|                             let changes = |                         const newElementAction = new CreateNewNodeAction(tags, location.lat, location.lon) | ||||||
|                                 State.state.changes.applyAction(new CreateNewNodeAction(tags, location.lat, location.lon)) |                             State.state.changes.applyAction(newElementAction) | ||||||
|                             State.state.selectedElement.setData(changes.newFeatures[0]); |  | ||||||
|                             selectedPreset.setData(undefined) |                             selectedPreset.setData(undefined) | ||||||
|  |                             isShown.setData(false) | ||||||
|  |                             Hash.hash.setData(newElementAction.newElementId) | ||||||
|                         }, () => { |                         }, () => { | ||||||
|                             selectedPreset.setData(undefined) |                             selectedPreset.setData(undefined) | ||||||
|                         }) |                         }) | ||||||
|  |  | ||||||
|  | @ -92,18 +92,24 @@ export default class SplitRoadWizard extends Toggle { | ||||||
|         // Save button
 |         // Save button
 | ||||||
|         const saveButton = new Button(t.split.Clone(), () => { |         const saveButton = new Button(t.split.Clone(), () => { | ||||||
|             hasBeenSplit.setData(true) |             hasBeenSplit.setData(true) | ||||||
|             OsmObject.DownloadObject(id).addCallbackAndRunD(way => { |             const way = OsmObject.DownloadObject(id) | ||||||
|                     OsmObject.DownloadReferencingRelations(id).addCallbackAndRunD( |             const partOfSrc = OsmObject.DownloadReferencingRelations(id); | ||||||
|                         partOf => { |             let hasRun = false | ||||||
|  |             way.map(way => { | ||||||
|  |                 const partOf = partOfSrc.data | ||||||
|  |                 if(way === undefined || partOf === undefined){ | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |                 if(hasRun){ | ||||||
|  |                     return | ||||||
|  |                 } | ||||||
|  |                 hasRun = true | ||||||
|                 const splitAction = new SplitAction( |                 const splitAction = new SplitAction( | ||||||
|                     <OsmWay>way, way.asGeoJson(), partOf, splitPoints.data.map(ff => ff.feature) |                     <OsmWay>way, way.asGeoJson(), partOf, splitPoints.data.map(ff => ff.feature) | ||||||
|                 ) |                 ) | ||||||
|                 State.state.changes.applyAction(splitAction) |                 State.state.changes.applyAction(splitAction) | ||||||
|                         } |  | ||||||
|                     ) |  | ||||||
|                  |                  | ||||||
|                 } |             }, [partOfSrc]) | ||||||
|             ) |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         }); |         }); | ||||||
|  | @ -123,7 +129,7 @@ export default class SplitRoadWizard extends Toggle { | ||||||
| 
 | 
 | ||||||
|         const splitTitle = new Title(t.splitTitle); |         const splitTitle = new Title(t.splitTitle); | ||||||
| 
 | 
 | ||||||
|         const mapView = new Combine([splitTitle, miniMap, new Combine([cancelButton, saveToggle])]); |         const mapView = new Combine([splitTitle, miniMap, new Combine([cancelButton, saveToggle]).SetClass("flex flex-row")]); | ||||||
|         mapView.SetClass("question") |         mapView.SetClass("question") | ||||||
|         const confirm = new Toggle(mapView, splitToggle, splitClicked); |         const confirm = new Toggle(mapView, splitToggle, splitClicked); | ||||||
|         super(t.hasBeenSplit.Clone(), confirm, hasBeenSplit) |         super(t.hasBeenSplit.Clone(), confirm, hasBeenSplit) | ||||||
|  |  | ||||||
|  | @ -61,7 +61,6 @@ export default class ShowDataLayer { | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             const allFeats = features.data.map(ff => ff.feature); |             const allFeats = features.data.map(ff => ff.feature); | ||||||
|             console.log("Rendering ",allFeats, "features at layer ", name) |  | ||||||
|             geoLayer = self.CreateGeojsonLayer(); |             geoLayer = self.CreateGeojsonLayer(); | ||||||
|             for (const feat of allFeats) { |             for (const feat of allFeats) { | ||||||
|                 if (feat === undefined) { |                 if (feat === undefined) { | ||||||
|  | @ -87,6 +86,7 @@ export default class ShowDataLayer { | ||||||
|                     console.error(e) |                     console.error(e) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |             State.state.selectedElement.ping() | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         features.addCallback(() => update()); |         features.addCallback(() => update()); | ||||||
|  |  | ||||||
|  | @ -19,7 +19,6 @@ export class SubstitutedTranslation extends VariableUiElement { | ||||||
|         const extraMappings: SpecialVisualization[] = []; |         const extraMappings: SpecialVisualization[] = []; | ||||||
| 
 | 
 | ||||||
|         mapping?.forEach((value, key) => { |         mapping?.forEach((value, key) => { | ||||||
|             console.log("KV:", key, value) |  | ||||||
|             extraMappings.push( |             extraMappings.push( | ||||||
|                 { |                 { | ||||||
|                     funcName: key, |                     funcName: key, | ||||||
|  | @ -73,11 +72,6 @@ export class SubstitutedTranslation extends VariableUiElement { | ||||||
|         } |         } | ||||||
|     }[] { |     }[] { | ||||||
| 
 | 
 | ||||||
|         if (extraMappings.length > 0) { |  | ||||||
| 
 |  | ||||||
|             console.log("Extra mappings are", extraMappings) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         for (const knownSpecial of SpecialVisualizations.specialVisualizations.concat(extraMappings)) { |         for (const knownSpecial of SpecialVisualizations.specialVisualizations.concat(extraMappings)) { | ||||||
| 
 | 
 | ||||||
|             // Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way'
 |             // Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way'
 | ||||||
|  |  | ||||||
|  | @ -109,9 +109,9 @@ export class Translation extends BaseUIElement { | ||||||
|                     // @ts-ignore
 |                     // @ts-ignore
 | ||||||
|                     const date: Date = el; |                     const date: Date = el; | ||||||
|                     rtext = date.toLocaleString(); |                     rtext = date.toLocaleString(); | ||||||
|                 } else if (el.ConstructElement() === undefined) { |                 } else if (el.ConstructElement === undefined) { | ||||||
|                     console.error("InnerREnder is not defined", el); |                     console.error("ConstructElement is not defined", el); | ||||||
|                     throw "Hmmm, el.InnerRender is not defined?" |                     throw "ConstructElement is not defined, you are working with a "+(typeof el)+":"+(el.constructor.name) | ||||||
|                 } else { |                 } else { | ||||||
|                     Translation.forcedLanguage = lang; // This is a very dirty hack - it'll bite me one day
 |                     Translation.forcedLanguage = lang; // This is a very dirty hack - it'll bite me one day
 | ||||||
|                     rtext = el.ConstructElement().innerHTML; |                     rtext = el.ConstructElement().innerHTML; | ||||||
|  |  | ||||||
|  | @ -33,7 +33,7 @@ | ||||||
|   "startZoom": 14, |   "startZoom": 14, | ||||||
|   "startLon": 3.2228, |   "startLon": 3.2228, | ||||||
|   "maintainer": "MapComplete", |   "maintainer": "MapComplete", | ||||||
|   "widenfactor": 0.05, |   "widenfactor": 0.01, | ||||||
|   "roamingRenderings": [ |   "roamingRenderings": [ | ||||||
|     { |     { | ||||||
|       "question": { |       "question": { | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue