mapcomplete/Logic/Osm/Actions/ChangeDescription.ts

202 lines
6.4 KiB
TypeScript

import { OsmNode, OsmRelation, OsmWay } from "../OsmObject"
/**
* Represents a single change to an object
*/
export interface ChangeDescription {
/**
* Metadata to be included in the changeset
*/
meta: {
/*
* The theme with which this changeset was made
*/
theme: string
/**
* The type of the change
*/
changeType: "answer" | "create" | "split" | "delete" | "move" | "import" | string | null
/**
* THe motivation for the change, e.g. 'deleted because does not exist anymore'
*/
specialMotivation?: string
/**
* Added by Changes.ts
*/
distanceToObject?: number
}
/**
* Identifier of the object
*/
type: "node" | "way" | "relation"
/**
* Identifier of the object
* Negative for new objects
*/
id: number
/**
* All changes to tags
* v = "" or v = undefined to erase this tag
*
* Note that this list will only contain the _changes_ to the tags, not the full set of tags
*/
tags?: { k: string; v: string }[]
/**
* A change to the geometry:
* 1) Change of node location
* 2) Change of way geometry
* 3) Change of relation members (untested)
*/
changes?:
| {
lat: number
lon: number
}
| {
/* Coordinates are only used for rendering. They should be LON, LAT
* */
coordinates: [number, number][]
nodes: number[]
}
| {
members: { type: "node" | "way" | "relation"; ref: number; role: string }[]
}
/*
Set to delete the object
*/
doDelete?: boolean
}
export class ChangeDescriptionTools {
/**
* Rewrites all the ids in a changeDescription
*
* // should rewrite the id of the changed object
* const change = <ChangeDescription> {
* id: -1234,
* type: "node",
* meta:{
* theme:"test",
* changeType: "answer"
* },
* tags:[
* {
* k: "key",
* v: "value"
* }
* ]
* }
* }
* const mapping = new Map<string, string>([["node/-1234", "node/42"]])
* const rewritten = ChangeDescriptionTools.rewriteIds(change, mapping)
* rewritten.id // => 42
*
* // should rewrite ids in nodes of a way
* const change = <ChangeDescription> {
* type: "way",
* id: 789,
* changes: {
* nodes: [-1, -2, -3, 68453],
* coordinates: []
* },
* meta:{
* theme:"test",
* changeType: "create"
* }
* }
* const mapping = new Map<string, string>([["node/-1", "node/42"],["node/-2", "node/43"],["node/-3", "node/44"]])
* const rewritten = ChangeDescriptionTools.rewriteIds(change, mapping)
* rewritten.id // => 789
* rewritten.changes["nodes"] // => [42,43,44, 68453]
*
* // should rewrite ids in relationship members
* const change = <ChangeDescription> {
* type: "way",
* id: 789,
* changes: {
* members: [{type: "way", ref: -1, role: "outer"},{type: "way", ref: 48, role: "outer"}],
* },
* meta:{
* theme:"test",
* changeType: "create"
* }
* }
* const mapping = new Map<string, string>([["way/-1", "way/42"],["node/-2", "node/43"],["node/-3", "node/44"]])
* const rewritten = ChangeDescriptionTools.rewriteIds(change, mapping)
* rewritten.id // => 789
* rewritten.changes["members"] // => [{type: "way", ref: 42, role: "outer"},{type: "way", ref: 48, role: "outer"}]
*
*/
public static rewriteIds(
change: ChangeDescription,
mappings: Map<string, string>
): ChangeDescription {
const key = change.type + "/" + change.id
const wayHasChangedNode = ((change.changes ?? {})["nodes"] ?? []).some((id) =>
mappings.has("node/" + id)
)
const relationHasChangedMembers = ((change.changes ?? {})["members"] ?? []).some(
(obj: { type: string; ref: number }) => mappings.has(obj.type + "/" + obj.ref)
)
const hasSomeChange = mappings.has(key) || wayHasChangedNode || relationHasChangedMembers
if (hasSomeChange) {
change = { ...change }
}
if (mappings.has(key)) {
const [_, newId] = mappings.get(key).split("/")
change.id = Number.parseInt(newId)
}
if (wayHasChangedNode) {
change.changes = { ...change.changes }
change.changes["nodes"] = change.changes["nodes"].map((id) => {
const key = "node/" + id
if (!mappings.has(key)) {
return id
}
const [_, newId] = mappings.get(key).split("/")
return Number.parseInt(newId)
})
}
if (relationHasChangedMembers) {
change.changes = { ...change.changes }
change.changes["members"] = change.changes["members"].map(
(obj: { type: string; ref: number }) => {
const key = obj.type + "/" + obj.ref
if (!mappings.has(key)) {
return obj
}
const [_, newId] = mappings.get(key).split("/")
return { ...obj, ref: Number.parseInt(newId) }
}
)
}
return change
}
public static getGeojsonGeometry(change: ChangeDescription): any {
switch (change.type) {
case "node":
const n = new OsmNode(change.id)
n.lat = change.changes["lat"]
n.lon = change.changes["lon"]
return n.asGeoJson().geometry
case "way":
const w = new OsmWay(change.id)
w.nodes = change.changes["nodes"]
w.coordinates = change.changes["coordinates"].map(([lon, lat]) => [lat, lon])
return w.asGeoJson().geometry
case "relation":
const r = new OsmRelation(change.id)
r.members = change.changes["members"]
return r.asGeoJson().geometry
}
}
}