forked from MapComplete/MapComplete
		
	Merge develop
This commit is contained in:
		
						commit
						c8bd412476
					
				
					 49 changed files with 1342 additions and 977 deletions
				
			
		
							
								
								
									
										162
									
								
								Logic/FeatureSource/ChangeApplicator.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								Logic/FeatureSource/ChangeApplicator.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,162 @@ | |||
| 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"; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * 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; | ||||
| 
 | ||||
|     constructor(source: FeatureSource, changes: Changes, mode?: { | ||||
|         generateNewGeometries: boolean | ||||
|     }) { | ||||
| 
 | ||||
|         this.name = "ChangesApplied(" + source.name + ")" | ||||
|         this.features = source.features | ||||
|         const seenChanges = new Set<ChangeDescription>(); | ||||
|         const self = this; | ||||
|         let runningUpdate = false; | ||||
|         source.features.addCallbackAndRunD(features => { | ||||
|             if (runningUpdate) { | ||||
|                 return; // No need to ping again
 | ||||
|             } | ||||
|             ChangeApplicator.ApplyChanges(features, changes.pendingChanges.data, mode) | ||||
|             seenChanges.clear() | ||||
|         }) | ||||
| 
 | ||||
|         changes.pendingChanges.addCallbackAndRunD(changes => { | ||||
|             runningUpdate = true; | ||||
|             changes = changes.filter(ch => !seenChanges.has(ch)) | ||||
|             changes.forEach(c => seenChanges.add(c)) | ||||
|             ChangeApplicator.ApplyChanges(self.features.data, changes, mode) | ||||
|             source.features.ping() | ||||
|             runningUpdate = false; | ||||
|         }) | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * Returns true if the geometry is changed and the source should be pinged | ||||
|      */ | ||||
|     private static ApplyChanges(features: { feature: any; freshness: Date }[], cs: ChangeDescription[], mode: { generateNewGeometries: boolean }): boolean { | ||||
|         if (cs.length === 0 || features === undefined) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         console.log("Applying changes ", this.name, cs) | ||||
|         let geometryChanged = false; | ||||
|         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) { | ||||
|             feature.id = feature.properties.id | ||||
|             features.push({ | ||||
|                 feature: feature, | ||||
|                 freshness: now | ||||
|             }) | ||||
|             console.log("Added a new feature: ", feature) | ||||
|             geometryChanged = true; | ||||
|         } | ||||
| 
 | ||||
|         // First, create the new features - they have a negative ID
 | ||||
|         // We don't set the properties yet though
 | ||||
|         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) | ||||
|                         } | ||||
|                     }) | ||||
|             }) | ||||
|         } | ||||
| 
 | ||||
|         for (const feature of features) { | ||||
|             const f = feature.feature; | ||||
|             const id = f.properties.id; | ||||
|             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
 | ||||
|                     f.properties[kv.k] = kv.v; | ||||
|                 } | ||||
| 
 | ||||
|                 // Apply other changes to the object
 | ||||
|                 if (change.changes !== undefined) { | ||||
|                     geometryChanged = true; | ||||
|                     switch (change.type) { | ||||
|                         case "node": | ||||
|                             // @ts-ignore
 | ||||
|                             const coor: { lat, lon } = change.changes; | ||||
|                             f.geometry.coordinates = [coor.lon, coor.lat] | ||||
|                             break; | ||||
|                         case "way": | ||||
|                             f.geometry.coordinates = change.changes["locations"] | ||||
|                             break; | ||||
|                         case "relation": | ||||
|                             console.error("Changes to relations are not yet supported") | ||||
|                             break; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return geometryChanged | ||||
|     } | ||||
| } | ||||
|  | @ -6,7 +6,6 @@ import FeatureDuplicatorPerLayer from "../FeatureSource/FeatureDuplicatorPerLaye | |||
| import FeatureSource from "../FeatureSource/FeatureSource"; | ||||
| import {UIEventSource} from "../UIEventSource"; | ||||
| import LocalStorageSaver from "./LocalStorageSaver"; | ||||
| import LayerConfig from "../../Customizations/JSON/LayerConfig"; | ||||
| import LocalStorageSource from "./LocalStorageSource"; | ||||
| import LayoutConfig from "../../Customizations/JSON/LayoutConfig"; | ||||
| import Loc from "../../Models/Loc"; | ||||
|  | @ -14,6 +13,8 @@ import GeoJsonSource from "./GeoJsonSource"; | |||
| import MetaTaggingFeatureSource from "./MetaTaggingFeatureSource"; | ||||
| import RegisteringFeatureSource from "./RegisteringFeatureSource"; | ||||
| import FilteredLayer from "../../Models/FilteredLayer"; | ||||
| import {Changes} from "../Osm/Changes"; | ||||
| import ChangeApplicator from "./ChangeApplicator"; | ||||
| 
 | ||||
| export default class FeaturePipeline implements FeatureSource { | ||||
| 
 | ||||
|  | @ -22,10 +23,10 @@ export default class FeaturePipeline implements FeatureSource { | |||
|     public readonly name = "FeaturePipeline" | ||||
| 
 | ||||
|     constructor(flayers: UIEventSource<FilteredLayer[]>, | ||||
|                 changes: Changes, | ||||
|                 updater: FeatureSource, | ||||
|                 fromOsmApi: FeatureSource, | ||||
|                 layout: UIEventSource<LayoutConfig>, | ||||
|                 newPoints: FeatureSource, | ||||
|                 locationControl: UIEventSource<Loc>, | ||||
|                 selectedElement: UIEventSource<any>) { | ||||
| 
 | ||||
|  | @ -41,7 +42,9 @@ export default class FeaturePipeline implements FeatureSource { | |||
|                     new MetaTaggingFeatureSource(allLoadedFeatures, | ||||
|                         new FeatureDuplicatorPerLayer(flayers, | ||||
|                             new RegisteringFeatureSource( | ||||
|                                 updater) | ||||
|                                 new ChangeApplicator( | ||||
|                                     updater, changes | ||||
|                                 )) | ||||
|                         )), layout)); | ||||
| 
 | ||||
|         const geojsonSources: FeatureSource [] = GeoJsonSource | ||||
|  | @ -49,8 +52,7 @@ export default class FeaturePipeline implements FeatureSource { | |||
|             .map(geojsonSource => { | ||||
|                 let source = new RegisteringFeatureSource( | ||||
|                     new FeatureDuplicatorPerLayer(flayers, | ||||
|                             geojsonSource | ||||
|                     )); | ||||
|                         new ChangeApplicator(geojsonSource, changes))); | ||||
|                 if (!geojsonSource.isOsmCache) { | ||||
|                     source = new MetaTaggingFeatureSource(allLoadedFeatures, source, updater.features); | ||||
|                 } | ||||
|  | @ -58,25 +60,24 @@ export default class FeaturePipeline implements FeatureSource { | |||
|             }); | ||||
| 
 | ||||
|         const amendedLocalStorageSource = | ||||
|             new RememberingSource(new RegisteringFeatureSource(new FeatureDuplicatorPerLayer(flayers, new LocalStorageSource(layout)) | ||||
|             new RememberingSource(new RegisteringFeatureSource(new FeatureDuplicatorPerLayer(flayers, new ChangeApplicator(new LocalStorageSource(layout), changes)) | ||||
|             )); | ||||
| 
 | ||||
|         newPoints = new MetaTaggingFeatureSource(allLoadedFeatures, | ||||
|             new FeatureDuplicatorPerLayer(flayers, | ||||
|                 new RegisteringFeatureSource(newPoints))); | ||||
| 
 | ||||
|         const amendedOsmApiSource = new RememberingSource( | ||||
|             new MetaTaggingFeatureSource(allLoadedFeatures, | ||||
|                 new FeatureDuplicatorPerLayer(flayers, | ||||
| 
 | ||||
|                     new RegisteringFeatureSource(fromOsmApi)))); | ||||
|                     new RegisteringFeatureSource(new ChangeApplicator(fromOsmApi, changes, | ||||
|                         { | ||||
|                             // We lump in the new points here
 | ||||
|                             generateNewGeometries: true | ||||
|                         } | ||||
|                     ))))); | ||||
| 
 | ||||
|         const merged = | ||||
|             new FeatureSourceMerger([ | ||||
|                 amendedOverpassSource, | ||||
|                 amendedOsmApiSource, | ||||
|                 amendedLocalStorageSource, | ||||
|                 newPoints, | ||||
|                 ...geojsonSources | ||||
|             ]); | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,6 +1,10 @@ | |||
| import FeatureSource from "./FeatureSource"; | ||||
| import {UIEventSource} from "../UIEventSource"; | ||||
| 
 | ||||
| /** | ||||
|  * Merges features from different featureSources | ||||
|  * Uses the freshest feature available in the case multiple sources offer data with the same identifier | ||||
|  */ | ||||
| export default class FeatureSourceMerger implements FeatureSource { | ||||
| 
 | ||||
|     public features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<{ feature: any; freshness: Date }[]>([]); | ||||
|  |  | |||
|  | @ -94,16 +94,9 @@ export default class FilteringFeatureSource implements FeatureSource { | |||
|                     return false; | ||||
|                 } | ||||
| 
 | ||||
|                | ||||
| 
 | ||||
|                 return true; | ||||
|             }); | ||||
|             console.log( | ||||
|                 "Filtering layer source: input: ", | ||||
|                 upstream.features.data?.length, | ||||
|                 "output:", | ||||
|                 newFeatures.length | ||||
|             ); | ||||
| 
 | ||||
|             self.features.setData(newFeatures); | ||||
|             if (missingLayers.size > 0) { | ||||
|                 console.error( | ||||
|  |  | |||
|  | @ -15,15 +15,19 @@ export default class OsmApiFeatureSource implements FeatureSource { | |||
| 
 | ||||
| 
 | ||||
|     public load(id: string) { | ||||
|         if(id.indexOf("-") >= 0){ | ||||
|         if (id.indexOf("-") >= 0) { | ||||
|             // Newly added point - not yet in OSM
 | ||||
|             return; | ||||
|         } | ||||
|         console.debug("Downloading", id, "from the OSM-API") | ||||
|         OsmObject.DownloadObject(id).addCallbackAndRunD(element => { | ||||
|             const geojson = element.asGeoJson(); | ||||
|             geojson.id = geojson.properties.id; | ||||
|             this.features.setData([{feature: geojson, freshness: element.timestamp}]) | ||||
|             try { | ||||
|                 const geojson = element.asGeoJson(); | ||||
|                 geojson.id = geojson.properties.id; | ||||
|                 this.features.setData([{feature: geojson, freshness: element.timestamp}]) | ||||
|             } catch (e) { | ||||
|                 console.error(e) | ||||
|             } | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|  | @ -58,7 +62,7 @@ export default class OsmApiFeatureSource implements FeatureSource { | |||
|             const bounds = Utils.tile_bounds(z, x, y); | ||||
|             console.log("Loading OSM data tile", z, x, y, " with bounds", bounds) | ||||
|             OsmObject.LoadArea(bounds, objects => { | ||||
|                 const keptGeoJson: {feature:any, freshness: Date}[] = [] | ||||
|                 const keptGeoJson: { feature: any, freshness: Date }[] = [] | ||||
|                 // Which layer does the object match?
 | ||||
|                 for (const object of objects) { | ||||
| 
 | ||||
|  | @ -69,7 +73,7 @@ export default class OsmApiFeatureSource implements FeatureSource { | |||
|                         if (doesMatch) { | ||||
|                             const geoJson = object.asGeoJson(); | ||||
|                             geoJson._matching_layer_id = layer.id | ||||
|                             keptGeoJson.push({feature: geoJson, freshness:  object.timestamp}) | ||||
|                             keptGeoJson.push({feature: geoJson, freshness: object.timestamp}) | ||||
|                             break; | ||||
|                         } | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue