| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  | import FeatureSource from "./FeatureSource"; | 
					
						
							|  |  |  | import {UIEventSource} from "../UIEventSource"; | 
					
						
							|  |  |  | import {Changes} from "../Osm/Changes"; | 
					
						
							|  |  |  | import {ChangeDescription} from "../Osm/Actions/ChangeDescription"; | 
					
						
							|  |  |  | import {Utils} from "../../Utils"; | 
					
						
							|  |  |  | import {OsmNode, OsmRelation, OsmWay} from "../Osm/OsmObject"; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-18 14:52:09 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Applies changes from 'Changes' onto a featureSource | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export default class ChangeApplicator implements FeatureSource { | 
					
						
							|  |  |  |     public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>; | 
					
						
							|  |  |  |     public readonly name: string; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-18 17:50:35 +02:00
										 |  |  |     constructor(source: FeatureSource, changes: Changes, mode?: { | 
					
						
							|  |  |  |         generateNewGeometries: boolean | 
					
						
							|  |  |  |     }) { | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         this.name = "ChangesApplied(" + source.name + ")" | 
					
						
							|  |  |  |         this.features = source.features | 
					
						
							| 
									
										
										
										
											2021-07-18 14:52:09 +02:00
										 |  |  |         const seenChanges = new Set<ChangeDescription>(); | 
					
						
							|  |  |  |         const self = this; | 
					
						
							|  |  |  |         let runningUpdate = false; | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |         source.features.addCallbackAndRunD(features => { | 
					
						
							| 
									
										
										
										
											2021-07-18 17:50:35 +02:00
										 |  |  |             if (runningUpdate) { | 
					
						
							| 
									
										
										
										
											2021-07-18 14:52:09 +02:00
										 |  |  |                 return; // No need to ping again
 | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-07-18 17:50:35 +02:00
										 |  |  |             ChangeApplicator.ApplyChanges(features, changes.pendingChanges.data, mode) | 
					
						
							| 
									
										
										
										
											2021-07-18 14:52:09 +02:00
										 |  |  |             seenChanges.clear() | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         changes.pendingChanges.addCallbackAndRunD(changes => { | 
					
						
							| 
									
										
										
										
											2021-07-18 14:52:09 +02:00
										 |  |  |             runningUpdate = true; | 
					
						
							|  |  |  |             changes = changes.filter(ch => !seenChanges.has(ch)) | 
					
						
							|  |  |  |             changes.forEach(c => seenChanges.add(c)) | 
					
						
							| 
									
										
										
										
											2021-07-18 17:50:35 +02:00
										 |  |  |             ChangeApplicator.ApplyChanges(self.features.data, changes, mode) | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |             source.features.ping() | 
					
						
							| 
									
										
										
										
											2021-07-18 14:52:09 +02:00
										 |  |  |             runningUpdate = false; | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-18 14:52:09 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Returns true if the geometry is changed and the source should be pinged | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2021-07-18 17:50:35 +02:00
										 |  |  |     private static ApplyChanges(features: { feature: any; freshness: Date }[], cs: ChangeDescription[], mode: { generateNewGeometries: boolean }): boolean { | 
					
						
							|  |  |  |         if (cs.length === 0 || features === undefined) { | 
					
						
							|  |  |  |             return; | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-18 14:52:09 +02:00
										 |  |  |         console.log("Applying changes ", this.name, cs) | 
					
						
							|  |  |  |         let geometryChanged = false; | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |         const changesPerId: Map<string, ChangeDescription[]> = new Map<string, ChangeDescription[]>() | 
					
						
							|  |  |  |         for (const c of cs) { | 
					
						
							|  |  |  |             const id = c.type + "/" + c.id | 
					
						
							|  |  |  |             if (!changesPerId.has(id)) { | 
					
						
							|  |  |  |                 changesPerId.set(id, []) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             changesPerId.get(id).push(c) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const now = new Date() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         function add(feature) { | 
					
						
							| 
									
										
										
										
											2021-07-18 21:37:14 +02:00
										 |  |  |             feature.id = feature.properties.id | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |             features.push({ | 
					
						
							|  |  |  |                 feature: feature, | 
					
						
							|  |  |  |                 freshness: now | 
					
						
							|  |  |  |             }) | 
					
						
							| 
									
										
										
										
											2021-07-18 14:52:09 +02:00
										 |  |  |             console.log("Added a new feature: ", feature) | 
					
						
							|  |  |  |             geometryChanged = true; | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // First, create the new features - they have a negative ID
 | 
					
						
							|  |  |  |         // We don't set the properties yet though
 | 
					
						
							| 
									
										
										
										
											2021-07-18 17:50:35 +02:00
										 |  |  |         if (mode?.generateNewGeometries) { | 
					
						
							|  |  |  |             changesPerId.forEach(cs => { | 
					
						
							|  |  |  |                 cs | 
					
						
							|  |  |  |                     .forEach(change => { | 
					
						
							|  |  |  |                         if (change.id >= 0) { | 
					
						
							|  |  |  |                             return; // Nothing to do here, already created
 | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                         if (change.changes === undefined) { | 
					
						
							|  |  |  |                             // An update to the object - not the actual created
 | 
					
						
							|  |  |  |                             return; | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                         try { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                             switch (change.type) { | 
					
						
							|  |  |  |                                 case "node": | 
					
						
							|  |  |  |                                     const n = new OsmNode(change.id) | 
					
						
							|  |  |  |                                     n.lat = change.changes["lat"] | 
					
						
							|  |  |  |                                     n.lon = change.changes["lon"] | 
					
						
							|  |  |  |                                     const geojson = n.asGeoJson() | 
					
						
							|  |  |  |                                     add(geojson) | 
					
						
							|  |  |  |                                     break; | 
					
						
							|  |  |  |                                 case "way": | 
					
						
							|  |  |  |                                     const w = new OsmWay(change.id) | 
					
						
							|  |  |  |                                     w.nodes = change.changes["nodes"] | 
					
						
							|  |  |  |                                     add(w.asGeoJson()) | 
					
						
							|  |  |  |                                     break; | 
					
						
							|  |  |  |                                 case "relation": | 
					
						
							|  |  |  |                                     const r = new OsmRelation(change.id) | 
					
						
							|  |  |  |                                     r.members = change.changes["members"] | 
					
						
							|  |  |  |                                     add(r.asGeoJson()) | 
					
						
							|  |  |  |                                     break; | 
					
						
							|  |  |  |                             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                         } catch (e) { | 
					
						
							|  |  |  |                             console.error(e) | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                     }) | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |             }) | 
					
						
							| 
									
										
										
										
											2021-07-18 17:50:35 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         for (const feature of features) { | 
					
						
							|  |  |  |             const f = feature.feature; | 
					
						
							| 
									
										
										
										
											2021-07-18 14:52:09 +02:00
										 |  |  |             const id = f.properties.id; | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |             if (!changesPerId.has(id)) { | 
					
						
							|  |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const changed = {} | 
					
						
							|  |  |  |             // Copy all the properties
 | 
					
						
							|  |  |  |             Utils.Merge(f, changed) | 
					
						
							|  |  |  |             // play the changes onto the copied object
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             for (const change of changesPerId.get(id)) { | 
					
						
							|  |  |  |                 for (const kv of change.tags ?? []) { | 
					
						
							|  |  |  |                     // Apply tag changes and ping the consumers
 | 
					
						
							| 
									
										
										
										
											2021-07-18 21:37:14 +02:00
										 |  |  |                     f.properties[kv.k] = kv.v; | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 // Apply other changes to the object
 | 
					
						
							|  |  |  |                 if (change.changes !== undefined) { | 
					
						
							| 
									
										
										
										
											2021-07-18 14:52:09 +02:00
										 |  |  |                     geometryChanged = true; | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |                     switch (change.type) { | 
					
						
							|  |  |  |                         case "node": | 
					
						
							|  |  |  |                             // @ts-ignore
 | 
					
						
							|  |  |  |                             const coor: { lat, lon } = change.changes; | 
					
						
							| 
									
										
										
										
											2021-07-18 14:52:09 +02:00
										 |  |  |                             f.geometry.coordinates = [coor.lon, coor.lat] | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |                             break; | 
					
						
							|  |  |  |                         case "way": | 
					
						
							|  |  |  |                             f.geometry.coordinates = change.changes["locations"] | 
					
						
							|  |  |  |                             break; | 
					
						
							|  |  |  |                         case "relation": | 
					
						
							|  |  |  |                             console.error("Changes to relations are not yet supported") | 
					
						
							|  |  |  |                             break; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-07-18 14:52:09 +02:00
										 |  |  |         return geometryChanged | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | } |