Further work on the road splitting feature

This commit is contained in:
pietervdvn 2021-07-15 00:26:25 +02:00
parent 9348a019d6
commit 1da3f8a332
9 changed files with 351 additions and 274 deletions

View file

@ -1,6 +1,10 @@
import FeatureSource from "./FeatureSource";
import {UIEventSource} from "../UIEventSource";
/**
* Merges features from different featureSources
* Uses the freshest feature available in the case multiple sources offer data with the same identifier
*/
export default class FeatureSourceMerger implements FeatureSource {
public features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<{ feature: any; freshness: Date }[]>([]);

View file

@ -0,0 +1,7 @@
/**
* An action is a change to the OSM-database
* It will generate some new/modified/deleted objects, which are all bundled by the 'changes'-object
*/
export default interface Action {
}

View file

@ -1,4 +1,4 @@
import {OsmNode, OsmObject, OsmWay} from "./OsmObject";
import {OsmNode, OsmObject} from "./OsmObject";
import State from "../../State";
import {Utils} from "../../Utils";
import {UIEventSource} from "../UIEventSource";
@ -121,7 +121,7 @@ export class Changes implements FeatureSource{
}
}
const changes = this.createTagChangeList(basicTags, properties, id);
const changes = Changes.createTagChangeList(basicTags, properties, id);
console.log("New feature added and pinged")
this.features.data.push({feature:geojson, freshness: new Date()});
@ -133,44 +133,8 @@ export class Changes implements FeatureSource{
return geojson;
}
/**
* Creates a new road with given tags that consist of the points corresponding to given nodeIDs
* @param basicTags The tags to add to the road
* @param nodeIDs IDs of nodes of which the road consists. Those nodes must already exist in osm or already be added to the changeset.
* @param coordinates The coordinates correspoinding to the nodeID at the same index. Each coordinate is a [lon, lat] point
* @return geojson A geojson representation of the created road
*/
public createRoad(basicTags: Tag[], nodeIDs, coordinates) {
const osmWay = new OsmWay(this.getNewID());
const id = "way/" + osmWay.id;
osmWay.nodes = nodeIDs;
const properties = {id: id};
const geojson = {
"type": "Feature",
"properties": properties,
"id": id,
"geometry": {
"type": "LineString",
"coordinates": coordinates
}
}
const changes = this.createTagChangeList(basicTags, properties, id);
console.log("New feature added and pinged")
this.features.data.push({feature:geojson, freshness: new Date()});
this.features.ping();
State.state.allElements.addOrGetElement(geojson).ping();
this.uploadAll([osmWay], changes);
return geojson;
}
private createTagChangeList(basicTags: Tag[], properties: { id: string }, id: string) {
private static createTagChangeList(basicTags: Tag[], properties: { id: string }, id: string) {
// The basictags are COPIED, the id is included in the properties
// The tags are not yet written into the OsmObject, but this is applied onto a
const changes = [];
@ -229,46 +193,46 @@ export class Changes implements FeatureSource{
State.state.osmConnection.UploadChangeset(
State.state.layoutToUse.data,
State.state.allElements,
function (csId) {
let modifications = "";
for (const element of changedElements) {
if (!element.changed) {
continue;
}
modifications += element.ChangesetXML(csId) + "\n";
}
let creations = "";
for (const newElement of newElements) {
creations += newElement.ChangesetXML(csId);
}
let changes = `<osmChange version='0.6' generator='Mapcomplete ${Constants.vNumber}'>`;
if (creations.length > 0) {
changes +=
"<create>" +
creations +
"</create>";
}
if (modifications.length > 0) {
changes +=
"<modify>\n" +
modifications +
"\n</modify>";
}
changes += "</osmChange>";
return changes;
});
(csId) => Changes.createChangesetFor(csId,changedElements, newElements )
);
};
public static createChangesetFor(csId: string, changedElements: OsmObject[], newElements: OsmObject[]): string {
let modifications = "";
for (const element of changedElements) {
modifications += element.ChangesetXML(csId) + "\n";
}
let creations = "";
for (const newElement of newElements) {
creations += newElement.ChangesetXML(csId);
}
let changes = `<osmChange version='0.6' generator='Mapcomplete ${Constants.vNumber}'>`;
if (creations.length > 0) {
changes +=
"\n<create>\n" +
creations +
"</create>";
}
if (modifications.length > 0) {
changes +=
"\n<modify>\n" +
modifications +
"\n</modify>";
}
changes += "</osmChange>";
return changes;
}
private uploadAll(
newElements: OsmObject[],
pending: { elementId: string; key: string; value: string }[]
@ -293,13 +257,4 @@ export class Changes implements FeatureSource{
})
}
/**
* Changes the nodes of road with given id to the given nodes
* @param roadID The ID of the road to update
* @param newNodes The node id's the road consists of (should already be added to the changeset or in osm)
*/
public updateRoadCoordinates(roadID: string, newNodes: number[]) {
// TODO
}
}

View file

@ -0,0 +1,3 @@
export default class CreateNewNodeAction {
}

View file

@ -60,6 +60,8 @@ export abstract class OsmObject {
case("relation"):
new OsmRelation(idN).Download(newContinuation);
break;
default:
throw "Invalid road type:" + type;
}
return src;
@ -150,7 +152,7 @@ export abstract class OsmObject {
const minlat = bounds[1][0]
const maxlat = bounds[0][0];
const url = `${OsmObject.backendURL}api/0.6/map.json?bbox=${minlon},${minlat},${maxlon},${maxlat}`
Utils.downloadJson(url).then( data => {
Utils.downloadJson(url).then(data => {
const elements: any[] = data.elements;
const objects = OsmObject.ParseObjects(elements)
callback(objects);
@ -354,9 +356,9 @@ export class OsmNode extends OsmObject {
ChangesetXML(changesetId: string): string {
let tags = this.TagsXML();
return ' <node id="' + this.id + '" changeset="' + changesetId + '" ' + this.VersionXML() + ' lat="' + this.lat + '" lon="' + this.lon + '">\n' +
return ' <node id="' + this.id + '" changeset="' + changesetId + '" ' + this.VersionXML() + ' lat="' + this.lat + '" lon="' + this.lon + '">\n' +
tags +
' </node>\n';
' </node>\n';
}
SaveExtraData(element) {
@ -401,7 +403,6 @@ export class OsmWay extends OsmObject {
constructor(id) {
super("way", id);
}
centerpoint(): [number, number] {
@ -418,7 +419,7 @@ export class OsmWay extends OsmObject {
return ' <way id="' + this.id + '" changeset="' + changesetId + '" ' + this.VersionXML() + '>\n' +
nds +
tags +
' </way>\n';
' </way>\n';
}
SaveExtraData(element, allNodes: OsmNode[]) {

View file

@ -0,0 +1,11 @@
/**
* The logic to handle relations after a way within
*/
export default class RelationSplitlHandler {
constructor() {
}
}

View file

@ -1,162 +1,222 @@
import {UIEventSource} from "../UIEventSource";
import {OsmNode, OsmObject, OsmWay} from "./OsmObject";
import State from "../../State";
import {distance} from "@turf/turf";
import {OsmNode, OsmObject, OsmRelation, OsmWay} from "./OsmObject";
import {GeoOperations} from "../GeoOperations";
import State from "../../State";
import {UIEventSource} from "../UIEventSource";
import {Changes} from "./Changes";
/**
* Splits a road in different segments, each splitted at one of the given points (or a point on the road close to it)
* @param roadID The id of the road you want to split
* @param points The points on the road where you want the split to occur (geojson point list)
*/
export async function splitRoad(roadID, points) {
if (points.length != 1) {
// TODO: more than one point
console.log(points)
window.alert("Warning, currently only tested on one point, you selected " + points.length + " points")
}
let road = State.state.allElements.ContainingFeatures.get(roadID);
/**
* Compares two points based on the starting point of the road, can be used in sort function
* @param point1 [lon, lat] point
* @param point2 [lon, lat] point
*/
function comparePointDistance(point1, point2) {
let distFromStart1 = GeoOperations.nearestPoint(road, point1).properties.location;
let distFromStart2 = GeoOperations.nearestPoint(road, point2).properties.location;
return distFromStart1 - distFromStart2; // Sort requires a number to return instead of a bool
}
/**
* Eliminates split points close (<4m) to existing points on the road, so you can split on these points instead
* @param road The road geojson object
* @param points The points on the road where you want the split to occur (geojson point list)
* @return realSplitPoints List containing all new locations where you should split
*/
function getSplitPoints(road, points) {
// Copy the list
let roadPoints = [...road.geometry.coordinates];
// Get the coordinates of all geojson points
let splitPointsCoordinates = points.map((point) => point.geometry.coordinates);
roadPoints.push(...splitPointsCoordinates);
// Sort all points on the road based on the distance from the start
roadPoints.sort(comparePointDistance)
// Remove points close to existing points on road
let realSplitPoints = [...splitPointsCoordinates];
for (let index = roadPoints.length - 1; index > 0; index--) {
// Iterate backwards to prevent problems when removing elements
let dist = distance(roadPoints[index - 1], roadPoints[index], {units: "kilometers"});
// Remove all cutpoints closer than 4m to their previous point
if ((dist < 0.004) && (splitPointsCoordinates.includes(roadPoints[index]))) {
console.log("Removed a splitpoint, using a closer point to the road instead")
realSplitPoints.splice(index, 1)
realSplitPoints.push(roadPoints[index - 1])
}
}
return realSplitPoints;
}
let realSplitPoints = getSplitPoints(road, points);
// Create a sorted list containing all points
let allPoints = [...road.geometry.coordinates];
allPoints.push(...realSplitPoints);
allPoints.sort(comparePointDistance);
// The changeset that will contain the operations to split the road
let changes = new Changes();
// Download the data of the current road from Osm to get the ID's of the coordinates
let osmRoad: UIEventSource<OsmWay> = OsmObject.DownloadObject(roadID);
// TODO: Remove delay, use a callback on odmRoad instead and execute all code below in callback function
function delay(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
await delay(3000);
// Dict to quickly convert a coordinate to a nodeID
let coordToIDMap = {};
/**
* Converts a coordinate to a string, so it's hashable (e.g. for using it in a dict)
* @param coord [lon, lat] point
*/
function getCoordKey(coord: [number, number]) {
return coord[0] + "," + coord[1];
}
osmRoad.data.coordinates.forEach((coord, i) => coordToIDMap[getCoordKey([coord[1], coord[0]])] = osmRoad.data.nodes[i]);
let currentRoadPoints: number[] = [];
let currentRoadCoordinates: [number, number][] = []
/**
* Given a coordinate, check whether there is already a node in osm created (on the road or cutpoints) or create
* such point if it doesn't exist yet and return the id of this coordinate
* @param coord [lon, lat] point
* @return pointID The ID of the existing/created node on given coordinates
*/
function getOrCreateNodeID(coord) {
console.log(coordToIDMap)
let poinID = coordToIDMap[getCoordKey(coord)];
if (poinID == undefined) {
console.log(getCoordKey(coord) + " not in map")
// TODO: Check if lat, lon is correct
let newNode = changes.createElement([], coord[1], coord[0]);
coordToIDMap[coord] = newNode.id;
poinID = newNode.id;
console.log("New point created ");
}
return poinID;
}
/**
* Creates a new road in OSM, while copying the tags from osmRoad and using currentRoadPoints as points
* @param currentRoadPoints List of id's of nodes the road should exist of
* @param osmRoad The road to copy the tags from
*/
function createNewRoadSegment(currentRoadPoints, osmRoad) {
changes.createRoad(osmRoad.data.tags, currentRoadPoints, currentRoadCoordinates);
}
for (let coord of allPoints) {
console.log("Handling coord")
let pointID = getOrCreateNodeID(coord);
currentRoadPoints.push(pointID);
currentRoadCoordinates.push(coord);
if (realSplitPoints.includes(coord)) {
console.log("Handling split")
// Should split here
// currentRoadPoints contains a list containing all points for this road segment
createNewRoadSegment(currentRoadPoints, osmRoad);
// Cleanup for next split
currentRoadPoints = [pointID];
currentRoadCoordinates = [coord];
console.log("Splitting here...")
}
}
// Update the road to contain only the points of the last segment
// changes.updateRoadCoordinates(roadID, currentRoadPoints);
// push the applied changes
changes.flushChanges();
return;
interface SplitInfo {
originalIndex?: number, // or negative for new elements
lngLat: [number, number],
doSplit: boolean
}
export default class SplitAction {
private readonly roadObject: any;
// TODO: Vlakbij bestaand punt geklikt? Bestaand punt hergebruiken
// Nieuw wegobject aanmaken, en oude hergebruiken voor andere helft van de weg
// TODO: CHeck if relation exist to the road -> Delete them when splitted, because they might be outdated after the split
/***
*
* @param roadObject: the geojson of the road object. Properties.id must be the corresponding OSM-id
*/
constructor(roadObject: any) {
this.roadObject = roadObject;
}
private static SegmentSplitInfo(splitInfo: SplitInfo[]): SplitInfo[][] {
const wayParts = []
let currentPart = []
for (const splitInfoElement of splitInfo) {
currentPart.push(splitInfoElement)
if (splitInfoElement.doSplit) {
// We have to do a split!
// We add the current index to the currentParts, flush it and add it again
wayParts.push(currentPart)
currentPart = [splitInfoElement]
}
}
wayParts.push(currentPart)
return wayParts
}
public DoSplit(splitPoints: any[]) {
// We mark the new split points with a new id
for (const splitPoint of splitPoints) {
splitPoint.properties["_is_split_point"] = true
}
const self = this;
const id = this.roadObject.properties.id
const osmWay = <UIEventSource<OsmWay>>OsmObject.DownloadObject(id)
const partOf = OsmObject.DownloadReferencingRelations(id)
osmWay.map(originalElement => {
if(originalElement === undefined || partOf === undefined){
return;
}
const changes = State.state?.changes ?? new Changes();
// First, calculate splitpoints and remove points close to one another
const splitInfo = self.CalculateSplitCoordinates(splitPoints)
// Now we have a list with e.g.
// [ { originalIndex: 0}, {originalIndex: 1, doSplit: true}, {originalIndex: 2}, {originalIndex: undefined, doSplit: true}, {originalIndex: 3}]
// Lets change 'originalIndex' to the actual node id first:
for (const element of splitInfo) {
if (element.originalIndex >= 0) {
element.originalIndex = originalElement.nodes[element.originalIndex]
} else {
element.originalIndex = changes.getNewID();
}
}
// Next up is creating actual parts from this
const wayParts = SplitAction.SegmentSplitInfo(splitInfo);
// Allright! At this point, we have our new ways!
// Which one is the longest of them (and can keep the id)?
let longest = undefined;
for (const wayPart of wayParts) {
if (longest === undefined) {
longest = wayPart;
continue
}
if (wayPart.length > longest.length) {
longest = wayPart
}
}
const newOsmObjects: OsmObject[] = []
const modifiedObjects: OsmObject[] = []
// Let's create the new points as needed
for (const element of splitInfo) {
if (element.originalIndex >= 0) {
continue;
}
const node = new OsmNode(element.originalIndex)
node.lon = element.lngLat[0]
node.lat = element.lngLat[1]
newOsmObjects.push(node)
}
const newWayIds: number[] = []
// Lets create OsmWays based on them
for (const wayPart of wayParts) {
let isOriginal = wayPart === longest
if(isOriginal){
// We change the actual element!
originalElement.nodes = wayPart.map(p => p.originalIndex);
originalElement.changed = true;
modifiedObjects.push(originalElement)
}else{
let id = changes.getNewID();
const way = new OsmWay(id)
way.tags = originalElement.tags;
way.nodes = wayPart.map(p => p.originalIndex);
way.changed = true;
newOsmObjects.push(way)
newWayIds.push(way.id)
}
}
// At last, we still have to check that we aren't part of a relation...
// At least, the order of the ways is identical, so we can keep the same roles
modifiedObjects.push(...SplitAction.UpdateRelations(partOf.data, newWayIds, originalElement))
// And we have our objects!
// Time to upload
console.log(Changes.createChangesetFor("123", modifiedObjects, newOsmObjects))
}, [partOf])
}
private static UpdateRelations(data: OsmRelation[], newWayIds: number[], originalElement: OsmWay):OsmRelation[]{
// TODO
return []
}
/**
* Calculates the actual points to split
* If another point is closer then ~5m, we reuse that point
*/
private CalculateSplitCoordinates(
splitPoints: any[],
toleranceInM = 5): SplitInfo[] {
const allPoints = [...splitPoints];
// We have a bunch of coordinates here: [ [lat, lon], [lat, lon], ...] ...
const originalPoints: [number, number][] = this.roadObject.geometry.coordinates
// We project them onto the line (which should yield pretty much the same point
for (let i = 0; i < originalPoints.length; i++) {
let originalPoint = originalPoints[i];
let projected = GeoOperations.nearestPoint(this.roadObject, originalPoint)
projected.properties["_is_split_point"] = false
projected.properties["_original_index"] = i
allPoints.push(projected)
}
// At this point, we have a list of both the split point and the old points, with some properties to discriminate between them
// We sort this list so that the new points are at the same location
allPoints.sort((a, b) => a.properties.location - b.properties.location)
// When this is done, we check that no now point is too close to an already existing point and no very small segments get created
for (let i = allPoints.length - 1; i > 0; i--) {
const point = allPoints[i];
if (point.properties._original_index !== undefined) {
// This point is already in OSM - we have to keep it!
continue;
}
if (i != allPoints.length - 1) {
const prevPoint = allPoints[i + 1]
const diff = Math.abs(point.properties.location - prevPoint.properties.location) * 1000
if (diff <= toleranceInM) {
// To close to the previous point! We delete this point...
allPoints.splice(i, 1)
// ... and mark the previous point as a split point
prevPoint.properties._is_split_point = true
continue;
}
}
if (i > 0) {
const nextPoint = allPoints[i - 1]
const diff = Math.abs(point.properties.location - nextPoint.properties.location) * 1000
if (diff <= toleranceInM) {
// To close to the next point! We delete this point...
allPoints.splice(i, 1)
// ... and mark the next point as a split point
nextPoint.properties._is_split_point = true
// noinspection UnnecessaryContinueJS
continue;
}
}
// We don't have to remove this point...
}
const splitInfo: SplitInfo[] = []
let nextId = -1
for (const p of allPoints) {
let index = p.properties._original_index
if (index === undefined) {
index = nextId;
nextId--;
}
const splitInfoElement = {
originalIndex: index,
lngLat: p.geometry.coordinates,
doSplit: p.properties._is_split_point
}
splitInfo.push(splitInfoElement)
}
return splitInfo
}
}

View file

@ -7,13 +7,15 @@ import State from "../../State";
import ShowDataLayer from "../ShowDataLayer";
import {GeoOperations} from "../../Logic/GeoOperations";
import {LeafletMouseEvent} from "leaflet";
import LayerConfig from "../../Customizations/JSON/LayerConfig";
import Combine from "../Base/Combine";
import {Button} from "../Base/Button";
import Translations from "../i18n/Translations";
import {splitRoad} from "../../Logic/Osm/SplitAction";
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
import SplitAction from "../../Logic/Osm/SplitAction";
export default class SplitRoadWizard extends Toggle {
private static splitLayout = new UIEventSource(SplitRoadWizard.GetSplitLayout())
/**
* A UI Element used for splitting roads
*
@ -23,25 +25,25 @@ export default class SplitRoadWizard extends Toggle {
const t = Translations.t.split;
// Contains the points on the road that are selected to split on
const splitPositions = new UIEventSource([]);
// Contains the points on the road that are selected to split on - contains geojson points with extra properties such as 'location' with the distance along the linestring
const splitPoints = new UIEventSource<{feature: any, freshness: Date}[]>([]);
// Toggle variable between show split button and map
const splitClicked = new UIEventSource<boolean>(true); // todo: -> false
const splitClicked = new UIEventSource<boolean>(false);
// Minimap on which you can select the points to be splitted
const miniMap = new Minimap({background: State.state.backgroundLayer});
miniMap.SetStyle("width: 100%; height: 50rem;");
// Define how a cut is displayed on the map
const layoutConfigJson = {id: "splitpositions", source: {osmTags: "_cutposition=yes"}, icon: "./assets/svg/plus.svg"}
State.state.layoutToUse.data.layers.push(new LayerConfig(layoutConfigJson,undefined,"Split Road Wizard"))
// Load the road with given id on the minimap
const roadElement = State.state.allElements.ContainingFeatures.get(id)
const splitAction = new SplitAction(roadElement)
const roadEventSource = new UIEventSource([{feature: roadElement, freshness: new Date()}]);
// Datalayer displaying the road and the cut points (if any)
const dataLayer = new ShowDataLayer(roadEventSource, miniMap.leafletMap, State.state.layoutToUse, false, true);
new ShowDataLayer(roadEventSource, miniMap.leafletMap, State.state.layoutToUse, false, true);
new ShowDataLayer(splitPoints, miniMap.leafletMap, SplitRoadWizard.splitLayout, false, false)
/**
* Handles a click on the overleaf map.
@ -54,17 +56,14 @@ export default class SplitRoadWizard extends Toggle {
// Update point properties to let it match the layer
pointOnRoad.properties._cutposition = "yes";
pointOnRoad._matching_layer_id = "splitpositions";
// Add it to the list of all points and notify observers
splitPositions.data.push(pointOnRoad);
splitPositions.ping();
pointOnRoad["_matching_layer_id"] = "splitpositions";
// let the state remember the point, to be able to retrieve it later by id
State.state.allElements.addOrGetElement(pointOnRoad);
roadEventSource.data.push({feature: pointOnRoad, freshness: new Date()}); // show the point on the data layer
roadEventSource.ping(); // not updated using .setData, so manually ping observers
// Add it to the list of all points and notify observers
splitPoints.data.push({feature: pointOnRoad, freshness: new Date()}); // show the point on the data layer
splitPoints.ping(); // not updated using .setData, so manually ping observers
}
// When clicked, pass clicked location coordinates to onMapClick function
@ -88,19 +87,16 @@ export default class SplitRoadWizard extends Toggle {
State.state.osmConnection.isLoggedIn)
// Save button
const saveButton = new Button("Split here", () => splitRoad(id, splitPositions.data));
const saveButton = new Button("Split here", () => splitAction.DoSplit(splitPoints.data));
saveButton.SetClass("block btn btn-primary");
const disabledSaveButton = new Button("Split here", undefined);
disabledSaveButton.SetClass("block btn btn-disabled");
// Only show the save button if there are split points defined
const saveToggle = new Toggle(disabledSaveButton, saveButton, splitPositions.map((data) => data.length === 0))
const saveToggle = new Toggle(disabledSaveButton, saveButton, splitPoints.map((data) => data.length === 0))
const cancelButton = new Button("Cancel", () => {
splitClicked.setData(false);
splitPositions.setData([]);
// Only keep showing the road, the cutpoints must be removed from the map
roadEventSource.setData([roadEventSource.data[0]])
splitPoints.setData([]);
});
cancelButton.SetClass("block btn btn-secondary");
@ -111,4 +107,21 @@ export default class SplitRoadWizard extends Toggle {
super(mapView, splitToggle, splitClicked);
}
private static GetSplitLayout(): LayoutConfig {
return new LayoutConfig({
maintainer: "mapcomplete",
language: [],
startLon: 0,
startLat: 0,
description: undefined,
icon: "", startZoom: 0,
title: "Split locations",
version: "",
id: "splitpositions",
layers: [{id: "splitpositions", source: {osmTags: "_cutposition=yes"}, icon: "./assets/svg/plus.svg"}]
}, true, "split road wizard layout")
}
}

35
test.ts
View file

@ -1,6 +1,5 @@
import SplitRoadWizard from "./UI/Popup/SplitRoadWizard";
import State from "./State";
import {AllKnownLayouts} from "./Customizations/AllKnownLayouts";
import SplitAction from "./Logic/Osm/SplitAction";
import {GeoOperations} from "./Logic/GeoOperations";
const way = {
"type": "Feature",
@ -47,7 +46,31 @@ const way = {
}
}
State.state = new State(AllKnownLayouts.allKnownLayouts.get("fietsstraten"));
let splitPoint = {
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [
4.490211009979248,
51.2041509326002
]
}
}
let splitClose = {
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [
4.489563927054405,
51.2047546593862
]
}
}
// State.state = new State(AllKnownLayouts.allKnownLayouts.get("fietsstraten"));
// add road to state
State.state.allElements.addOrGetElement(way);
new SplitRoadWizard("way/23583625").AttachTo("maindiv")
// State.state.allElements.addOrGetElement(way);
new SplitAction(way).DoSplit([splitPoint, splitClose].map(p => GeoOperations.nearestPoint(way,<[number, number]> p.geometry.coordinates)))