forked from MapComplete/MapComplete
		
	
		
			
				
	
	
		
			131 lines
		
	
	
	
		
			5.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			131 lines
		
	
	
	
		
			5.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import { Changes } from "../../Osm/Changes"
 | 
						|
import { OsmNode, OsmObject, OsmRelation, OsmWay } from "../../Osm/OsmObject"
 | 
						|
import FeatureSource from "../FeatureSource"
 | 
						|
import { UIEventSource } from "../../UIEventSource"
 | 
						|
import { ChangeDescription } from "../../Osm/Actions/ChangeDescription"
 | 
						|
import { ElementStorage } from "../../ElementStorage"
 | 
						|
import { OsmId, OsmTags } from "../../../Models/OsmFeature"
 | 
						|
 | 
						|
export class NewGeometryFromChangesFeatureSource implements FeatureSource {
 | 
						|
    // This class name truly puts the 'Java' into 'Javascript'
 | 
						|
 | 
						|
    /**
 | 
						|
     * A feature source containing exclusively new elements.
 | 
						|
     *
 | 
						|
     * These elements are probably created by the 'SimpleAddUi' which generates a new point, but the import functionality might create a line or polygon too.
 | 
						|
     * Other sources of new points are e.g. imports from nodes
 | 
						|
     */
 | 
						|
    public readonly features: UIEventSource<{ feature: any; freshness: Date }[]> =
 | 
						|
        new UIEventSource<{ feature: any; freshness: Date }[]>([])
 | 
						|
    public readonly name: string = "newFeatures"
 | 
						|
 | 
						|
    constructor(changes: Changes, allElementStorage: ElementStorage, backendUrl: string) {
 | 
						|
        const seenChanges = new Set<ChangeDescription>()
 | 
						|
        const features = this.features.data
 | 
						|
        const self = this
 | 
						|
 | 
						|
        changes.pendingChanges.stabilized(100).addCallbackAndRunD((changes) => {
 | 
						|
            if (changes.length === 0) {
 | 
						|
                return
 | 
						|
            }
 | 
						|
 | 
						|
            const now = new Date()
 | 
						|
            let somethingChanged = false
 | 
						|
 | 
						|
            function add(feature) {
 | 
						|
                feature.id = feature.properties.id
 | 
						|
                features.push({
 | 
						|
                    feature: feature,
 | 
						|
                    freshness: now,
 | 
						|
                })
 | 
						|
                somethingChanged = true
 | 
						|
            }
 | 
						|
 | 
						|
            for (const change of changes) {
 | 
						|
                if (seenChanges.has(change)) {
 | 
						|
                    // Already handled
 | 
						|
                    continue
 | 
						|
                }
 | 
						|
                seenChanges.add(change)
 | 
						|
 | 
						|
                if (change.tags === undefined) {
 | 
						|
                    // If tags is undefined, this is probably a new point that is part of a split road
 | 
						|
                    continue
 | 
						|
                }
 | 
						|
 | 
						|
                if (change.id > 0) {
 | 
						|
                    // This is an already existing object
 | 
						|
                    // In _most_ of the cases, this means that this _isn't_ a new object
 | 
						|
                    // However, when a point is snapped to an already existing point, we have to create a representation for this point!
 | 
						|
                    // For this, we introspect the change
 | 
						|
                    if (allElementStorage.has(change.type + "/" + change.id)) {
 | 
						|
                        // The current point already exists, we don't have to do anything here
 | 
						|
                        continue
 | 
						|
                    }
 | 
						|
                    console.debug("Detected a reused point")
 | 
						|
                    // The 'allElementsStore' does _not_ have this point yet, so we have to create it
 | 
						|
                    OsmObject.DownloadObjectAsync(change.type + "/" + change.id).then((feat) => {
 | 
						|
                        console.log("Got the reused point:", feat)
 | 
						|
                        for (const kv of change.tags) {
 | 
						|
                            feat.tags[kv.k] = kv.v
 | 
						|
                        }
 | 
						|
                        const geojson = feat.asGeoJson()
 | 
						|
                        allElementStorage.addOrGetElement(geojson)
 | 
						|
                        self.features.data.push({ feature: geojson, freshness: new Date() })
 | 
						|
                        self.features.ping()
 | 
						|
                    })
 | 
						|
                    continue
 | 
						|
                } else if (change.id < 0 && change.changes === undefined) {
 | 
						|
                    // The geometry is not described - not a new point
 | 
						|
                    if (change.id < 0) {
 | 
						|
                        console.error("WARNING: got a new point without geometry!")
 | 
						|
                    }
 | 
						|
                    continue
 | 
						|
                }
 | 
						|
 | 
						|
                try {
 | 
						|
                    const tags: OsmTags & { id: OsmId & string } = {
 | 
						|
                        id: <OsmId & string>(change.type + "/" + change.id),
 | 
						|
                    }
 | 
						|
                    for (const kv of change.tags) {
 | 
						|
                        tags[kv.k] = kv.v
 | 
						|
                    }
 | 
						|
 | 
						|
                    tags["_backend"] = backendUrl
 | 
						|
 | 
						|
                    switch (change.type) {
 | 
						|
                        case "node":
 | 
						|
                            const n = new OsmNode(change.id)
 | 
						|
                            n.tags = tags
 | 
						|
                            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.tags = tags
 | 
						|
                            w.nodes = change.changes["nodes"]
 | 
						|
                            w.coordinates = change.changes["coordinates"].map(([lon, lat]) => [
 | 
						|
                                lat,
 | 
						|
                                lon,
 | 
						|
                            ])
 | 
						|
                            add(w.asGeoJson())
 | 
						|
                            break
 | 
						|
                        case "relation":
 | 
						|
                            const r = new OsmRelation(change.id)
 | 
						|
                            r.tags = tags
 | 
						|
                            r.members = change.changes["members"]
 | 
						|
                            add(r.asGeoJson())
 | 
						|
                            break
 | 
						|
                    }
 | 
						|
                } catch (e) {
 | 
						|
                    console.error("Could not generate a new geometry to render on screen for:", e)
 | 
						|
                }
 | 
						|
            }
 | 
						|
            if (somethingChanged) {
 | 
						|
                self.features.ping()
 | 
						|
            }
 | 
						|
        })
 | 
						|
    }
 | 
						|
}
 |