2021-09-22 05:02:09 +02:00
|
|
|
import { Changes } from "../../Osm/Changes"
|
2023-04-20 03:58:31 +02:00
|
|
|
import { OsmNode, OsmRelation, OsmWay } from "../../Osm/OsmObject"
|
2023-04-20 01:52:23 +02:00
|
|
|
import { IndexedFeatureSource, WritableFeatureSource } from "../FeatureSource"
|
2021-09-22 05:02:09 +02:00
|
|
|
import { UIEventSource } from "../../UIEventSource"
|
|
|
|
import { ChangeDescription } from "../../Osm/Actions/ChangeDescription"
|
2022-09-02 13:43:14 +02:00
|
|
|
import { OsmId, OsmTags } from "../../../Models/OsmFeature"
|
2023-09-21 14:53:52 +02:00
|
|
|
import { Feature, Point } from "geojson"
|
|
|
|
import { TagUtils } from "../../Tags/TagUtils"
|
|
|
|
import FeaturePropertiesStore from "../Actors/FeaturePropertiesStore"
|
2021-09-22 05:02:09 +02:00
|
|
|
|
2023-04-20 01:52:23 +02:00
|
|
|
export class NewGeometryFromChangesFeatureSource implements WritableFeatureSource {
|
2021-09-22 05:02:09 +02:00
|
|
|
// This class name truly puts the 'Java' into 'Javascript'
|
2021-11-07 16:34:51 +01:00
|
|
|
|
2021-09-22 05:02:09 +02:00
|
|
|
/**
|
2022-06-13 01:35:50 +02:00
|
|
|
* 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
|
2023-09-21 14:53:52 +02:00
|
|
|
*
|
|
|
|
* Alternatively, an already existing point might suddenly match the layer, especially if a point in a wall is reused
|
|
|
|
*
|
|
|
|
* Note that the FeaturePropertiesStore will track a featuresource, such as this one
|
2021-09-22 05:02:09 +02:00
|
|
|
*/
|
2023-03-23 01:42:47 +01:00
|
|
|
public readonly features: UIEventSource<Feature[]> = new UIEventSource<Feature[]>([])
|
2023-09-21 14:53:52 +02:00
|
|
|
private readonly _seenChanges: Set<ChangeDescription>
|
|
|
|
private readonly _features: Feature[]
|
|
|
|
private readonly _backend: string
|
|
|
|
private readonly _allElementStorage: IndexedFeatureSource
|
|
|
|
private _featureProperties: FeaturePropertiesStore
|
2021-09-22 05:02:09 +02:00
|
|
|
|
2023-09-21 14:53:52 +02:00
|
|
|
constructor(
|
|
|
|
changes: Changes,
|
|
|
|
allElementStorage: IndexedFeatureSource,
|
|
|
|
featureProperties: FeaturePropertiesStore
|
|
|
|
) {
|
|
|
|
this._allElementStorage = allElementStorage
|
|
|
|
this._featureProperties = featureProperties
|
|
|
|
this._seenChanges = new Set<ChangeDescription>()
|
|
|
|
this._features = this.features.data
|
|
|
|
this._backend = changes.backend
|
2021-09-22 05:02:09 +02:00
|
|
|
const self = this
|
2023-09-21 14:53:52 +02:00
|
|
|
changes.pendingChanges.addCallbackAndRunD((changes) => self.handleChanges(changes))
|
|
|
|
}
|
2021-09-22 05:02:09 +02:00
|
|
|
|
2023-09-21 14:53:52 +02:00
|
|
|
private addNewFeature(feature: Feature) {
|
|
|
|
const features = this._features
|
|
|
|
feature.id = feature.properties.id
|
|
|
|
features.push(feature)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles a single pending change
|
|
|
|
* @returns true if something changed
|
|
|
|
* @param change
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
private handleChange(change: ChangeDescription): boolean {
|
2023-10-05 15:55:18 +02:00
|
|
|
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
|
|
|
|
return false
|
|
|
|
}
|
2021-09-22 05:02:09 +02:00
|
|
|
|
2023-10-05 15:55:18 +02:00
|
|
|
const allElementStorage = this._allElementStorage
|
|
|
|
console.log("Handling pending change", change)
|
2023-09-21 14:53:52 +02:00
|
|
|
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
|
|
|
|
return false
|
2022-01-21 03:57:49 +01:00
|
|
|
}
|
2023-09-21 14:53:52 +02:00
|
|
|
console.debug("Detected a reused point, for", change)
|
|
|
|
// The 'allElementsStore' does _not_ have this point yet, so we have to create it
|
|
|
|
// However, we already create a store for it
|
|
|
|
const { lon, lat } = <{ lon: number; lat: number }>change.changes
|
|
|
|
const feature = <Feature<Point, OsmTags>>{
|
|
|
|
type: "Feature",
|
|
|
|
properties: {
|
|
|
|
id: <OsmId>change.type + "/" + change.id,
|
|
|
|
...TagUtils.changeAsProperties(change.tags),
|
|
|
|
},
|
|
|
|
geometry: {
|
|
|
|
type: "Point",
|
|
|
|
coordinates: [lon, lat],
|
|
|
|
},
|
|
|
|
}
|
|
|
|
this._featureProperties.trackFeature(feature)
|
|
|
|
this.addNewFeature(feature)
|
|
|
|
return true
|
|
|
|
}
|
2022-01-21 03:57:49 +01:00
|
|
|
|
2023-09-21 14:53:52 +02:00
|
|
|
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
|
|
|
|
}
|
2022-01-21 03:57:49 +01:00
|
|
|
|
2023-09-21 14:53:52 +02:00
|
|
|
tags["_backend"] = this._backend
|
2021-09-22 05:02:09 +02:00
|
|
|
|
2023-09-21 14:53:52 +02:00
|
|
|
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()
|
|
|
|
this.addNewFeature(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])
|
|
|
|
this.addNewFeature(w.asGeoJson())
|
|
|
|
break
|
|
|
|
case "relation":
|
|
|
|
const r = new OsmRelation(change.id)
|
|
|
|
r.tags = tags
|
|
|
|
r.members = change.changes["members"]
|
|
|
|
this.addNewFeature(r.asGeoJson())
|
|
|
|
break
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
} catch (e) {
|
|
|
|
console.error("Could not generate a new geometry to render on screen for:", e)
|
|
|
|
}
|
|
|
|
}
|
2021-09-22 05:02:09 +02:00
|
|
|
|
2023-09-21 14:53:52 +02:00
|
|
|
private handleChanges(changes: ChangeDescription[]) {
|
|
|
|
const seenChanges = this._seenChanges
|
|
|
|
if (changes.length === 0) {
|
|
|
|
return
|
|
|
|
}
|
2022-01-21 03:57:49 +01:00
|
|
|
|
2023-09-21 14:53:52 +02:00
|
|
|
let somethingChanged = false
|
2022-01-21 03:57:49 +01:00
|
|
|
|
2023-09-21 14:53:52 +02:00
|
|
|
for (const change of changes) {
|
|
|
|
if (seenChanges.has(change)) {
|
|
|
|
// Already handled
|
|
|
|
continue
|
2022-01-21 03:57:49 +01:00
|
|
|
}
|
2023-09-21 14:53:52 +02:00
|
|
|
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
|
2022-01-21 03:57:49 +01:00
|
|
|
}
|
2023-09-21 14:53:52 +02:00
|
|
|
|
2023-10-05 15:55:18 +02:00
|
|
|
somethingChanged = this.handleChange(change) || somethingChanged // important: _first_ evaluate the method, to avoid shortcutting
|
2023-09-21 14:53:52 +02:00
|
|
|
}
|
|
|
|
if (somethingChanged) {
|
|
|
|
this.features.ping()
|
|
|
|
}
|
2021-09-22 05:02:09 +02:00
|
|
|
}
|
|
|
|
}
|