mapcomplete/Logic/FeatureSource/Sources/ChangeGeometryApplicator.ts

90 lines
3.4 KiB
TypeScript

/**
* Applies geometry changes from 'Changes' onto every feature of a featureSource
*/
import { Changes } from "../../Osm/Changes"
import { UIEventSource } from "../../UIEventSource"
import { FeatureSource, IndexedFeatureSource } from "../FeatureSource"
import { ChangeDescription, ChangeDescriptionTools } from "../../Osm/Actions/ChangeDescription"
import { Feature } from "geojson"
import { Utils } from "../../../Utils"
export default class ChangeGeometryApplicator implements FeatureSource {
public readonly features: UIEventSource<Feature[]> = new UIEventSource<Feature[]>([])
private readonly source: IndexedFeatureSource
private readonly changes: Changes
constructor(source: IndexedFeatureSource, changes: Changes) {
this.source = source
this.changes = changes
this.features = new UIEventSource<Feature[]>(undefined)
const self = this
source.features.addCallbackAndRunD((_) => self.update())
changes.allChanges.addCallbackAndRunD((_) => self.update())
}
private update() {
const upstreamFeatures = this.source.features.data
const upstreamIds = this.source.featuresById.data
const changesToApply = this.changes.allChanges.data?.filter(
(ch) =>
// Does upstream have this element? If not, we skip
upstreamIds.has(ch.type + "/" + ch.id) &&
// Are any (geometry) changes defined?
ch.changes !== undefined &&
// Ignore new elements, they are handled by the NewGeometryFromChangesFeatureSource
ch.id > 0
)
if (changesToApply === undefined || changesToApply.length === 0) {
// No changes to apply!
// Pass the original feature and lets continue our day
this.features.setData(upstreamFeatures)
return
}
const changesPerId = new Map<string, ChangeDescription[]>()
for (const ch of changesToApply) {
const key = ch.type + "/" + ch.id
if (changesPerId.has(key)) {
changesPerId.get(key).push(ch)
} else {
changesPerId.set(key, [ch])
}
}
const newFeatures: Feature[] = []
for (const feature of upstreamFeatures) {
const changesForFeature = changesPerId.get(feature.properties.id)
if (changesForFeature === undefined) {
// No changes for this element - simply pass it along to downstream
newFeatures.push(feature)
continue
}
// Allright! We have a feature to rewrite!
const copy = {
...feature,
}
// We only apply the last change as that one'll have the latest geometry
const change = changesForFeature[changesForFeature.length - 1]
copy.geometry = ChangeDescriptionTools.getGeojsonGeometry(change)
if (Utils.SameObject(copy.geometry, feature.geometry)) {
// No actual changes: pass along the original
newFeatures.push(feature)
continue
}
console.log(
"Applying a geometry change onto:",
feature,
"The change is:",
change,
"which becomes:",
copy
)
newFeatures.push(copy)
}
this.features.setData(newFeatures)
}
}