SplitAction logic, not yet pushing changes to osm, pieter will take over
This commit is contained in:
parent
159e4d3350
commit
f77c1efdf5
6 changed files with 262 additions and 29 deletions
|
@ -280,7 +280,7 @@ export class GeoOperations {
|
|||
* @param point Point defined as [lon, lat]
|
||||
*/
|
||||
public static nearestPoint(way, point: [number, number]){
|
||||
return turf.nearestPointOnLine(way, point);
|
||||
return turf.nearestPointOnLine(way, point, {units: "kilometers"});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {OsmNode, OsmObject} from "./OsmObject";
|
||||
import {OsmNode, OsmObject, OsmWay} from "./OsmObject";
|
||||
import State from "../../State";
|
||||
import {Utils} from "../../Utils";
|
||||
import {UIEventSource} from "../UIEventSource";
|
||||
|
@ -86,6 +86,14 @@ export class Changes implements FeatureSource{
|
|||
this.uploadAll([], this.pending.data);
|
||||
this.pending.setData([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new ID and updates the value for the next ID
|
||||
*/
|
||||
public getNewID(){
|
||||
return Changes._nextId--;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new node element at the given lat/long.
|
||||
* An internal OsmObject is created to upload later on, a geojson represention is returned.
|
||||
|
@ -93,8 +101,7 @@ export class Changes implements FeatureSource{
|
|||
*/
|
||||
public createElement(basicTags: Tag[], lat: number, lon: number) {
|
||||
console.log("Creating a new element with ", basicTags)
|
||||
const osmNode = new OsmNode(Changes._nextId);
|
||||
Changes._nextId--;
|
||||
const osmNode = new OsmNode(this.getNewID());
|
||||
|
||||
const id = "node/" + osmNode.id;
|
||||
osmNode.lat = lat;
|
||||
|
@ -114,16 +121,7 @@ export class Changes implements FeatureSource{
|
|||
}
|
||||
}
|
||||
|
||||
// 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 = [];
|
||||
for (const kv of basicTags) {
|
||||
properties[kv.key] = kv.value;
|
||||
if (typeof kv.value !== "string") {
|
||||
throw "Invalid value: don't use a regex in a preset"
|
||||
}
|
||||
changes.push({elementId: id, key: kv.key, value: kv.value})
|
||||
}
|
||||
const changes = this.createTagChangeList(basicTags, properties, id);
|
||||
|
||||
console.log("New feature added and pinged")
|
||||
this.features.data.push({feature:geojson, freshness: new Date()});
|
||||
|
@ -135,6 +133,57 @@ 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) {
|
||||
// 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 = [];
|
||||
for (const kv of basicTags) {
|
||||
properties[kv.key] = kv.value;
|
||||
if (typeof kv.value !== "string") {
|
||||
throw "Invalid value: don't use a regex in a preset"
|
||||
}
|
||||
changes.push({elementId: id, key: kv.key, value: kv.value})
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
|
||||
private uploadChangesWithLatestVersions(
|
||||
knownElements: OsmObject[], newElements: OsmObject[], pending: { elementId: string; key: string; value: string }[]) {
|
||||
const knownById = new Map<string, OsmObject>();
|
||||
|
@ -244,4 +293,13 @@ 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
|
||||
}
|
||||
}
|
|
@ -444,7 +444,7 @@ export class OsmWay extends OsmObject {
|
|||
this.nodes = element.nodes;
|
||||
}
|
||||
|
||||
asGeoJson() {
|
||||
public asGeoJson() {
|
||||
return {
|
||||
"type": "Feature",
|
||||
"properties": this.tags,
|
||||
|
|
162
Logic/Osm/SplitAction.ts
Normal file
162
Logic/Osm/SplitAction.ts
Normal file
|
@ -0,0 +1,162 @@
|
|||
import {UIEventSource} from "../UIEventSource";
|
||||
import {OsmNode, OsmObject, OsmWay} from "./OsmObject";
|
||||
import State from "../../State";
|
||||
import {distance} from "@turf/turf";
|
||||
import {GeoOperations} from "../GeoOperations";
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
// 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
|
|
@ -11,6 +11,7 @@ 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";
|
||||
|
||||
export default class SplitRoadWizard extends Toggle {
|
||||
/**
|
||||
|
@ -87,7 +88,7 @@ export default class SplitRoadWizard extends Toggle {
|
|||
State.state.osmConnection.isLoggedIn)
|
||||
|
||||
// Save button
|
||||
const saveButton = new Button("Split here", () => window.alert("Splitting..."));
|
||||
const saveButton = new Button("Split here", () => splitRoad(id, splitPositions.data));
|
||||
saveButton.SetClass("block btn btn-primary");
|
||||
const disabledSaveButton = new Button("Split here", undefined);
|
||||
disabledSaveButton.SetClass("block btn btn-disabled");
|
||||
|
@ -98,6 +99,7 @@ export default class SplitRoadWizard extends Toggle {
|
|||
splitClicked.setData(false);
|
||||
|
||||
splitPositions.setData([]);
|
||||
// Only keep showing the road, the cutpoints must be removed from the map
|
||||
roadEventSource.setData([roadEventSource.data[0]])
|
||||
});
|
||||
|
||||
|
|
37
test.ts
37
test.ts
|
@ -5,32 +5,43 @@ import {AllKnownLayouts} from "./Customizations/AllKnownLayouts";
|
|||
const way = {
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"id": "way/1234",
|
||||
"highway": "residential",
|
||||
"cyclestreet": "yes"
|
||||
"maxweight": "3.5",
|
||||
"maxweight:conditional": "none @ delivery",
|
||||
"name": "Silsstraat",
|
||||
"_last_edit:contributor": "Jorisbo",
|
||||
"_last_edit:contributor:uid": 1983103,
|
||||
"_last_edit:changeset": 70963524,
|
||||
"_last_edit:timestamp": "2019-06-05T18:20:44Z",
|
||||
"_version_number": 9,
|
||||
"id": "way/23583625"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
4.488961100578308,
|
||||
51.204971024401374
|
||||
4.4889691,
|
||||
51.2049831
|
||||
],
|
||||
[
|
||||
4.4896745681762695,
|
||||
51.204712226516435
|
||||
4.4895496,
|
||||
51.2047718
|
||||
],
|
||||
[
|
||||
4.489814043045044,
|
||||
51.20459459063348
|
||||
4.48966,
|
||||
51.2047147
|
||||
],
|
||||
[
|
||||
4.48991060256958,
|
||||
51.204439983016115
|
||||
4.4897439,
|
||||
51.2046548
|
||||
],
|
||||
[
|
||||
4.490291476249695,
|
||||
51.203845074952376
|
||||
4.4898162,
|
||||
51.2045921
|
||||
],
|
||||
[
|
||||
4.4902997,
|
||||
51.2038418
|
||||
]
|
||||
]
|
||||
}
|
||||
|
@ -39,4 +50,4 @@ const way = {
|
|||
State.state = new State(AllKnownLayouts.allKnownLayouts.get("fietsstraten"));
|
||||
// add road to state
|
||||
State.state.allElements.addOrGetElement(way);
|
||||
new SplitRoadWizard("way/1234").AttachTo("maindiv")
|
||||
new SplitRoadWizard("way/23583625").AttachTo("maindiv")
|
Loading…
Reference in a new issue