| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | import {OsmNode, OsmObject} from "./OsmObject"; | 
					
						
							| 
									
										
										
										
											2020-08-30 01:13:18 +02:00
										 |  |  | import {And, Tag, TagsFilter} from "../Tags"; | 
					
						
							| 
									
										
										
										
											2020-10-02 19:00:24 +02:00
										 |  |  | import State from "../../State"; | 
					
						
							| 
									
										
										
										
											2020-07-31 04:58:58 +02:00
										 |  |  | import {Utils} from "../../Utils"; | 
					
						
							| 
									
										
										
										
											2020-10-27 01:01:34 +01:00
										 |  |  | import {UIEventSource} from "../UIEventSource"; | 
					
						
							| 
									
										
										
										
											2021-01-02 19:09:49 +01:00
										 |  |  | import Constants from "../../Models/Constants"; | 
					
						
							| 
									
										
										
										
											2021-01-03 03:09:52 +01:00
										 |  |  | import FeatureSource from "../FeatureSource/FeatureSource"; | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-03 03:09:52 +01:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Handles all changes made to OSM. | 
					
						
							|  |  |  |  * Needs an authenticator via OsmConnection | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export class Changes implements FeatureSource{ | 
					
						
							| 
									
										
										
										
											2020-08-27 18:44:16 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-03 03:09:52 +01:00
										 |  |  |     public features = new UIEventSource<{feature: any, freshness: Date}[]>([]); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     private static _nextId = -1; // Newly assigned ID's are negative
 | 
					
						
							| 
									
										
										
										
											2020-08-27 18:44:16 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Adds a change to the pending changes | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2021-01-03 03:09:52 +01:00
										 |  |  |     private static checkChange(key: string, value: string): { k: string, v: string } { | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |         if (key === undefined || key === null) { | 
					
						
							|  |  |  |             console.log("Invalid key"); | 
					
						
							| 
									
										
										
										
											2020-08-27 18:44:16 +02:00
										 |  |  |             return undefined; | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |         if (value === undefined || value === null) { | 
					
						
							| 
									
										
										
										
											2020-08-27 18:44:16 +02:00
										 |  |  |             console.log("Invalid value for ", key); | 
					
						
							|  |  |  |             return undefined; | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-08-27 18:44:16 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if (key.startsWith(" ") || value.startsWith(" ") || value.endsWith(" ") || key.endsWith(" ")) { | 
					
						
							| 
									
										
										
										
											2020-08-06 19:42:10 +02:00
										 |  |  |             console.warn("Tag starts with or ends with a space - trimming anyway") | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-08-27 11:11:20 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-06 19:42:10 +02:00
										 |  |  |         key = key.trim(); | 
					
						
							|  |  |  |         value = value.trim(); | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-27 18:44:16 +02:00
										 |  |  |         return {k: key, v: value}; | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-03 03:09:52 +01:00
										 |  |  |     addTag(elementId: string, tagsFilter: TagsFilter, | 
					
						
							|  |  |  |            tags?: UIEventSource<any>) { | 
					
						
							|  |  |  |         const changes = this.tagToChange(tagsFilter); | 
					
						
							|  |  |  |         if (changes.length == 0) { | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         const eventSource = tags ?? State.state?.allElements.getEventSourceById(elementId); | 
					
						
							|  |  |  |         const elementTags = eventSource.data; | 
					
						
							|  |  |  |         const pending: { elementId: string, key: string, value: string }[] = []; | 
					
						
							|  |  |  |         for (const change of changes) { | 
					
						
							|  |  |  |             if (elementTags[change.k] !== change.v) { | 
					
						
							|  |  |  |                 elementTags[change.k] = change.v; | 
					
						
							|  |  |  |                 pending.push({elementId: elementTags.id, key: change.k, value: change.v}); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (pending.length === 0) { | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         console.log("Sending ping", eventSource) | 
					
						
							|  |  |  |         eventSource.ping(); | 
					
						
							|  |  |  |         this.uploadAll([], pending); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Create a new node element at the given lat/long. | 
					
						
							|  |  |  |      * An internal OsmObject is created to upload later on, a geojson represention is returned. | 
					
						
							|  |  |  |      * Note that the geojson version shares the tags (properties) by pointer, but has _no_ id in properties | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2021-01-03 03:09:52 +01:00
										 |  |  |     public createElement(basicTags: Tag[], lat: number, lon: number) { | 
					
						
							| 
									
										
										
										
											2020-08-06 21:06:50 +02:00
										 |  |  |         console.log("Creating a new element with ", basicTags) | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |         const osmNode = new OsmNode(Changes._nextId); | 
					
						
							|  |  |  |         Changes._nextId--; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const id = "node/" + osmNode.id; | 
					
						
							|  |  |  |         osmNode.lat = lat; | 
					
						
							|  |  |  |         osmNode.lon = lon; | 
					
						
							|  |  |  |         const properties = {id: id}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const geojson = { | 
					
						
							|  |  |  |             "type": "Feature", | 
					
						
							|  |  |  |             "properties": properties, | 
					
						
							|  |  |  |             "id": id, | 
					
						
							|  |  |  |             "geometry": { | 
					
						
							|  |  |  |                 "type": "Point", | 
					
						
							|  |  |  |                 "coordinates": [ | 
					
						
							|  |  |  |                     lon, | 
					
						
							|  |  |  |                     lat | 
					
						
							|  |  |  |                 ] | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // The basictags are COPIED, the id is included in the properties
 | 
					
						
							|  |  |  |         // The tags are not yet written into the OsmObject, but this is applied onto a 
 | 
					
						
							| 
									
										
										
										
											2020-08-27 11:11:20 +02:00
										 |  |  |         const changes = []; | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |         for (const kv of basicTags) { | 
					
						
							|  |  |  |             properties[kv.key] = kv.value; | 
					
						
							| 
									
										
										
										
											2020-08-27 11:11:20 +02:00
										 |  |  |             if (typeof kv.value !== "string") { | 
					
						
							|  |  |  |                 throw "Invalid value: don't use a regex in a preset" | 
					
						
							| 
									
										
										
										
											2020-08-23 01:49:19 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-08-27 11:11:20 +02:00
										 |  |  |             changes.push({elementId: id, key: kv.key, value: kv.value}) | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-02-14 19:45:02 +01:00
										 |  |  |         | 
					
						
							|  |  |  |         console.log("New feature added and pinged") | 
					
						
							| 
									
										
										
										
											2021-01-04 22:14:56 +01:00
										 |  |  |         this.features.data.push({feature:geojson, freshness: new Date()}); | 
					
						
							|  |  |  |         this.features.ping(); | 
					
						
							| 
									
										
										
										
											2021-02-14 19:45:02 +01:00
										 |  |  |          | 
					
						
							|  |  |  |         State.state.allElements.addOrGetElement(geojson).ping(); | 
					
						
							| 
									
										
										
										
											2021-01-04 22:14:56 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-27 11:11:20 +02:00
										 |  |  |         this.uploadAll([osmNode], changes); | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |         return geojson; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-03 03:09:52 +01:00
										 |  |  |     private tagToChange(tagsFilter: TagsFilter) { | 
					
						
							|  |  |  |         let changes: { k: string, v: string }[] = []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (tagsFilter instanceof Tag) { | 
					
						
							|  |  |  |             const tag = tagsFilter as Tag; | 
					
						
							|  |  |  |             if (typeof tag.value !== "string") { | 
					
						
							|  |  |  |                 throw "Invalid value" | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             return [Changes.checkChange(tag.key, tag.value)]; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (tagsFilter instanceof And) { | 
					
						
							|  |  |  |             const and = tagsFilter as And; | 
					
						
							|  |  |  |             for (const tag of and.and) { | 
					
						
							|  |  |  |                 changes = changes.concat(this.tagToChange(tag)); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             return changes; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         console.log("Unsupported tagsfilter element to addTag", tagsFilter); | 
					
						
							|  |  |  |         throw "Unsupported tagsFilter element"; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-27 18:44:16 +02:00
										 |  |  |     private uploadChangesWithLatestVersions( | 
					
						
							|  |  |  |         knownElements, newElements: OsmObject[], pending: { elementId: string; key: string; value: string }[]) { | 
					
						
							|  |  |  |         // Here, inside the continuation, we know that all 'neededIds' are loaded in 'knownElements', which maps the ids onto the elements
 | 
					
						
							|  |  |  |         // We apply the changes on them
 | 
					
						
							|  |  |  |         for (const change of pending) { | 
					
						
							|  |  |  |             if (parseInt(change.elementId.split("/")[1]) < 0) { | 
					
						
							|  |  |  |                 // This is a new element - we should apply this on one of the new elements
 | 
					
						
							|  |  |  |                 for (const newElement of newElements) { | 
					
						
							|  |  |  |                     if (newElement.type + "/" + newElement.id === change.elementId) { | 
					
						
							|  |  |  |                         newElement.addTag(change.key, change.value); | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-27 18:44:16 +02:00
										 |  |  |             } else { | 
					
						
							|  |  |  |                 knownElements[change.elementId].addTag(change.key, change.value); | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-27 18:44:16 +02:00
										 |  |  |         // Small sanity check for duplicate information
 | 
					
						
							|  |  |  |         let changedElements = []; | 
					
						
							|  |  |  |         for (const elementId in knownElements) { | 
					
						
							|  |  |  |             const element = knownElements[elementId]; | 
					
						
							|  |  |  |             if (element.changed) { | 
					
						
							|  |  |  |                 changedElements.push(element); | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-08-27 18:44:16 +02:00
										 |  |  |         if (changedElements.length == 0 && newElements.length == 0) { | 
					
						
							|  |  |  |             console.log("No changes in any object"); | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-27 18:44:16 +02:00
										 |  |  |         console.log("Beginning upload..."); | 
					
						
							|  |  |  |         // At last, we build the changeset and upload
 | 
					
						
							|  |  |  |         State.state.osmConnection.UploadChangeset( | 
					
						
							| 
									
										
										
										
											2020-08-30 01:13:18 +02:00
										 |  |  |             State.state.layoutToUse.data, | 
					
						
							|  |  |  |             State.state.allElements, | 
					
						
							| 
									
										
										
										
											2020-08-27 18:44:16 +02:00
										 |  |  |             function (csId) { | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-27 18:44:16 +02:00
										 |  |  |                 let modifications = ""; | 
					
						
							|  |  |  |                 for (const element of changedElements) { | 
					
						
							|  |  |  |                     if (!element.changed) { | 
					
						
							|  |  |  |                         continue; | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |                     } | 
					
						
							| 
									
										
										
										
											2020-08-27 18:44:16 +02:00
										 |  |  |                     modifications += element.ChangesetXML(csId) + "\n"; | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-27 18:44:16 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 let creations = ""; | 
					
						
							|  |  |  |                 for (const newElement of newElements) { | 
					
						
							|  |  |  |                     creations += newElement.ChangesetXML(csId); | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-02 19:09:49 +01:00
										 |  |  |                 let changes = `<osmChange version='0.6' generator='Mapcomplete ${Constants.vNumber}'>`; | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-27 18:44:16 +02:00
										 |  |  |                 if (creations.length > 0) { | 
					
						
							|  |  |  |                     changes += | 
					
						
							|  |  |  |                         "<create>" + | 
					
						
							|  |  |  |                         creations + | 
					
						
							|  |  |  |                         "</create>"; | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-27 18:44:16 +02:00
										 |  |  |                 if (modifications.length > 0) { | 
					
						
							|  |  |  |                     changes += | 
					
						
							| 
									
										
										
										
											2020-08-30 01:13:18 +02:00
										 |  |  |                         "<modify>\n" + | 
					
						
							| 
									
										
										
										
											2020-08-27 18:44:16 +02:00
										 |  |  |                         modifications + | 
					
						
							| 
									
										
										
										
											2020-08-30 01:13:18 +02:00
										 |  |  |                         "\n</modify>"; | 
					
						
							| 
									
										
										
										
											2020-08-27 18:44:16 +02:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-27 18:44:16 +02:00
										 |  |  |                 changes += "</osmChange>"; | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-27 18:44:16 +02:00
										 |  |  |                 return changes; | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private uploadAll( | 
					
						
							|  |  |  |         newElements: OsmObject[], | 
					
						
							|  |  |  |         pending: { elementId: string; key: string; value: string }[] | 
					
						
							|  |  |  |     ) { | 
					
						
							|  |  |  |         const self = this; | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-27 18:44:16 +02:00
										 |  |  |         let neededIds: string[] = []; | 
					
						
							|  |  |  |         for (const change of pending) { | 
					
						
							|  |  |  |             const id = change.elementId; | 
					
						
							|  |  |  |             if (parseFloat(id.split("/")[1]) < 0) { | 
					
						
							|  |  |  |                 // New element - we don't have to download this
 | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 neededIds.push(id); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-27 18:44:16 +02:00
										 |  |  |         neededIds = Utils.Dedup(neededIds); | 
					
						
							|  |  |  |         OsmObject.DownloadAll(neededIds, {}, (knownElements) => { | 
					
						
							|  |  |  |             self.uploadChangesWithLatestVersions(knownElements, newElements, pending) | 
					
						
							| 
									
										
										
										
											2020-07-31 04:58:58 +02:00
										 |  |  |         }); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-08-27 11:11:20 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | } |