import { ChangeDescription } from "./ChangeDescription" import { GeoOperations } from "../../GeoOperations" import { OsmWay } from "../OsmObject" export default class InsertPointIntoWayAction { private readonly _lat: number private readonly _lon: number private readonly _idToInsert: number private readonly _snapOnto: OsmWay private readonly _options: { allowReuseOfPreviouslyCreatedPoints?: boolean reusePointWithinMeters?: number } constructor( lat: number, lon: number, idToInsert: number, snapOnto: OsmWay, options: { allowReuseOfPreviouslyCreatedPoints?: boolean reusePointWithinMeters?: number } ) { this._lat = lat this._lon = lon this._idToInsert = idToInsert this._snapOnto = snapOnto this._options = options } /** * Tries to create the changedescription of the way where the point is inserted * Returns `undefined` if inserting failed */ public prepareChangeDescription(): Omit | undefined { // Project the point onto the way console.log("Snapping a node onto an existing way...") const geojson = this._snapOnto.asGeoJson() const projected = GeoOperations.nearestPoint(GeoOperations.outerRing(geojson), [ this._lon, this._lat, ]) const projectedCoor = <[number, number]>projected.geometry.coordinates const index = projected.properties.index console.log("Attempting to snap:", { geojson, projected, projectedCoor, index }) // We check that it isn't close to an already existing point let reusedPointId = undefined let reusedPointCoordinates: [number, number] = undefined let outerring: [number, number][] if (geojson.geometry.type === "LineString") { outerring = <[number, number][]>geojson.geometry.coordinates } else if (geojson.geometry.type === "Polygon") { outerring = <[number, number][]>geojson.geometry.coordinates[0] } const prev = outerring[index] if ( GeoOperations.distanceBetween(prev, projectedCoor) < this._options.reusePointWithinMeters ) { // We reuse this point instead! reusedPointId = this._snapOnto.nodes[index] reusedPointCoordinates = this._snapOnto.coordinates[index] } const next = outerring[index + 1] if ( GeoOperations.distanceBetween(next, projectedCoor) < this._options.reusePointWithinMeters ) { // We reuse this point instead! reusedPointId = this._snapOnto.nodes[index + 1] reusedPointCoordinates = this._snapOnto.coordinates[index + 1] } if (reusedPointId !== undefined) { return undefined } const locations = [ ...this._snapOnto.coordinates?.map(([lat, lon]) => <[number, number]>[lon, lat]), ] const ids = [...this._snapOnto.nodes] locations.splice(index + 1, 0, [this._lon, this._lat]) ids.splice(index + 1, 0, this._idToInsert) return { type: "way", id: this._snapOnto.id, changes: { coordinates: locations, nodes: ids, }, } } }