forked from MapComplete/MapComplete
		
	
		
			
				
	
	
		
			88 lines
		
	
	
	
		
			3.1 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			88 lines
		
	
	
	
		
			3.1 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import { Store, UIEventSource } from "../../UIEventSource"
 | |
| import { FeatureSource, IndexedFeatureSource } from "../FeatureSource"
 | |
| import { Feature } from "geojson"
 | |
| import { Utils } from "../../../Utils"
 | |
| 
 | |
| /**
 | |
|  *
 | |
|  */
 | |
| export default class FeatureSourceMerger implements IndexedFeatureSource {
 | |
|     public features: UIEventSource<Feature[]> = new UIEventSource([])
 | |
|     public readonly featuresById: Store<Map<string, Feature>>
 | |
|     private readonly _featuresById: UIEventSource<Map<string, Feature>>
 | |
|     private readonly _sources: FeatureSource[] = []
 | |
|     /**
 | |
|      * Merges features from different featureSources.
 | |
|      * In case that multiple features have the same id, the latest `_version_number` will be used. Otherwise, we will take the last one
 | |
|      */
 | |
|     constructor(...sources: FeatureSource[]) {
 | |
|         this._featuresById = new UIEventSource<Map<string, Feature>>(undefined)
 | |
|         this.featuresById = this._featuresById
 | |
|         const self = this
 | |
|         sources = Utils.NoNull(sources)
 | |
|         for (let source of sources) {
 | |
|             source.features.addCallback(() => {
 | |
|                 self.addData(sources.map((s) => s.features.data))
 | |
|             })
 | |
|         }
 | |
|         this.addData(sources.map((s) => s.features.data))
 | |
|         this._sources = sources
 | |
|     }
 | |
| 
 | |
|     public addSource(source: FeatureSource) {
 | |
|         this._sources.push(source)
 | |
|         source.features.addCallbackAndRun(() => {
 | |
|             this.addData(this._sources.map((s) => s.features.data))
 | |
|         })
 | |
|     }
 | |
| 
 | |
|     protected addData(featuress: Feature[][]) {
 | |
|         featuress = Utils.NoNull(featuress)
 | |
|         let somethingChanged = false
 | |
|         const all: Map<string, Feature> = new Map()
 | |
|         const unseen = new Set<string>()
 | |
|         // We seed the dictionary with the previously loaded features
 | |
|         const oldValues = this.features.data ?? []
 | |
|         for (const oldValue of oldValues) {
 | |
|             all.set(oldValue.properties.id, oldValue)
 | |
|             unseen.add(oldValue.properties.id)
 | |
|         }
 | |
| 
 | |
|         for (const features of featuress) {
 | |
|             for (const f of features) {
 | |
|                 const id = f.properties.id
 | |
|                 unseen.delete(id)
 | |
|                 if (!all.has(id)) {
 | |
|                     // This is a new feature
 | |
|                     somethingChanged = true
 | |
|                     all.set(id, f)
 | |
|                     continue
 | |
|                 }
 | |
| 
 | |
|                 // This value has been seen already, either in a previous run or by a previous datasource
 | |
|                 // Let's figure out if something changed
 | |
|                 const oldV = all.get(id)
 | |
|                 if (oldV == f) {
 | |
|                     continue
 | |
|                 }
 | |
|                 all.set(id, f)
 | |
|                 somethingChanged = true
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         somethingChanged ||= unseen.size > 0
 | |
|         unseen.forEach((id) => all.delete(id))
 | |
| 
 | |
|         if (!somethingChanged) {
 | |
|             // We don't bother triggering an update
 | |
|             return
 | |
|         }
 | |
| 
 | |
|         const newList = []
 | |
|         all.forEach((value, key) => {
 | |
|             newList.push(value)
 | |
|         })
 | |
|         this.features.setData(newList)
 | |
|         this._featuresById.setData(all)
 | |
|     }
 | |
| }
 |