import { Changes } from "../../Osm/Changes" import { OsmNode, OsmRelation, OsmWay } from "../../Osm/OsmObject" import { IndexedFeatureSource, WritableFeatureSource } from "../FeatureSource" import { UIEventSource } from "../../UIEventSource" import { ChangeDescription } from "../../Osm/Actions/ChangeDescription" import { OsmId, OsmTags } from "../../../Models/OsmFeature" import { Feature } from "geojson" import OsmObjectDownloader from "../../Osm/OsmObjectDownloader" export class NewGeometryFromChangesFeatureSource implements WritableFeatureSource { // 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 = new UIEventSource([]) constructor(changes: Changes, allElementStorage: IndexedFeatureSource, backendUrl: string) { const seenChanges = new Set() const features = this.features.data const self = this const backend = changes.backend changes.pendingChanges.addCallbackAndRunD((changes) => { if (changes.length === 0) { return } let somethingChanged = false function add(feature) { feature.id = feature.properties.id features.push(feature) 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 } console.log("Handling pending change") 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.featuresById.data.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 new OsmObjectDownloader(backend) .DownloadObjectAsync(change.type + "/" + change.id) .then((feat) => { console.log("Got the reused point:", feat) if (feat === "deleted") { throw "Panic: snapping to a point, but this point has been deleted in the meantime" } for (const kv of change.tags) { feat.tags[kv.k] = kv.v } const geojson = feat.asGeoJson() self.features.data.push(geojson) self.features.ping() }) continue } else if (change.changes === undefined) { // The geometry is not described - not a new point or geometry change, but probably a tagchange to a newly created point // Not something that should be handled here continue } try { const tags: OsmTags & { id: OsmId & string } = { id: (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() } }) } }