diff --git a/package-lock.json b/package-lock.json index 1d27ba96..6344d977 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,8 +15,6 @@ "@turf/boolean-point-in-polygon": "^6.5.0", "@turf/helpers": "^6.5.0", "@turf/length": "^6.5.0", - "@turf/line-slice": "^6.5.0", - "@turf/line-split": "^6.5.0", "@turf/mask": "^6.5.0", "@turf/nearest-point-on-line": "^6.5.0", "@turf/random": "^6.5.0", @@ -28,7 +26,7 @@ "maplibre-gl": "^4.0.2", "read-excel-file": "^5.7.1", "route-snapper": "^0.3.0", - "route-snapper-ts": "^0.0.3", + "route-snapper-ts": "^0.0.5", "svelte": "^4.2.10", "svelte-maplibre": "^0.9.2", "uuid": "^9.0.1" @@ -3076,10 +3074,15 @@ "integrity": "sha512-5a9fMq05zftlFCp8Smnw+5k6CP871sYgEaD30o9KX4VARUaACkJDWCCq76KZ3tmP/Vv/KupZOVHnPtxcDWSmpQ==" }, "node_modules/route-snapper-ts": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/route-snapper-ts/-/route-snapper-ts-0.0.3.tgz", - "integrity": "sha512-uUin96b1vrPWwmX0KkDdDZ+qDltL279ibLo1P3O04+mZIkomd6QAOTyBSXsoSWwqNNxVuLlJKTdtv4x+QYmX3Q==", + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/route-snapper-ts/-/route-snapper-ts-0.0.5.tgz", + "integrity": "sha512-xQyU81/gJdtKlWWeyEevKR4GLs/C1QtPQTD9JmuFO9ExShjmvD4TtT1EdchpT+omfe4quvIuA9YSN6qt4UNMMg==", "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/length": "^6.5.0", + "@turf/line-slice": "^6.5.0", + "@turf/line-split": "^6.5.0", + "@turf/nearest-point-on-line": "^6.5.0", "maplibre-gl": "^4.0.0", "route-snapper": "^0.4.0" } diff --git a/package.json b/package.json index bcf2a2ba..1ab2d099 100644 --- a/package.json +++ b/package.json @@ -41,8 +41,6 @@ "@turf/boolean-point-in-polygon": "^6.5.0", "@turf/helpers": "^6.5.0", "@turf/length": "^6.5.0", - "@turf/line-slice": "^6.5.0", - "@turf/line-split": "^6.5.0", "@turf/mask": "^6.5.0", "@turf/nearest-point-on-line": "^6.5.0", "@turf/random": "^6.5.0", @@ -54,7 +52,7 @@ "maplibre-gl": "^4.0.2", "read-excel-file": "^5.7.1", "route-snapper": "^0.3.0", - "route-snapper-ts": "^0.0.3", + "route-snapper-ts": "^0.0.5", "svelte": "^4.2.10", "svelte-maplibre": "^0.9.2", "uuid": "^9.0.1" diff --git a/src/lib/draw/EditGeometryMode.svelte b/src/lib/draw/EditGeometryMode.svelte index 5af8628b..ef3eae2b 100644 --- a/src/lib/draw/EditGeometryMode.svelte +++ b/src/lib/draw/EditGeometryMode.svelte @@ -17,7 +17,7 @@ import PolygonControls from "./polygon/PolygonControls.svelte"; import RouteControls from "./route/RouteControls.svelte"; import SnapPolygonControls from "./snap_polygon/SnapPolygonControls.svelte"; - import { type Props } from "route-snapper-ts"; + import type { AreaProps, RouteProps } from "route-snapper-ts"; export let id: number; @@ -40,14 +40,16 @@ name = interventionName(feature); if (feature.geometry.type == "LineString") { - $routeTool?.editExistingRoute(feature as Feature); + $routeTool?.editExistingRoute( + feature as unknown as Feature, + ); $routeTool?.addEventListenerSuccess(onSuccess); $routeTool?.addEventListenerUpdated(onUpdate); $routeTool?.addEventListenerFailure(onFailure); controls = "route"; } else if (feature.geometry.type == "Polygon") { if (feature.properties.waypoints) { - $routeTool?.editExistingArea(feature as Feature); + $routeTool?.editExistingArea(feature as Feature); $routeTool?.addEventListenerSuccess(onSuccess); $routeTool?.addEventListenerUpdated(onUpdate); $routeTool?.addEventListenerFailure(onFailure); diff --git a/src/lib/draw/route/SplitRouteMode.svelte b/src/lib/draw/route/SplitRouteMode.svelte index 1789f969..a86214d7 100644 --- a/src/lib/draw/route/SplitRouteMode.svelte +++ b/src/lib/draw/route/SplitRouteMode.svelte @@ -2,10 +2,8 @@ // Note this component has to embedded underneath for the // component to work. This is an exception to how LeftSidebar manages most // modes. - import { point } from "@turf/helpers"; - import length from "@turf/length"; - import lineSlice from "@turf/line-slice"; - import lineSplit from "@turf/line-split"; + + import { splitRoute, type RouteProps } from "route-snapper-ts"; import nearestPointOnLine from "@turf/nearest-point-on-line"; // Note we don't use our specialization of Feature here import type { Feature, LineString, Point, Position } from "geojson"; @@ -86,56 +84,41 @@ if (snappedIndex == null) { // We clicked the map, stop the tool mode.set({ mode: "list" }); - } else { - // TODO Can we avoid using ! everywhere here? - let result = lineSplit( - $gjSchemeCollection.features[snappedIndex!] as Feature, - snappedCursor!, - ); - if (result.features.length == 2) { - let piece1 = result.features[0]; - let piece2 = result.features[1]; - // lineSplit may introduce unnecessary coordinate precision - piece1.geometry.coordinates = - piece1.geometry.coordinates.map(setPrecision); - piece2.geometry.coordinates = - piece2.geometry.coordinates.map(setPrecision); - - gjSchemeCollection.update((gj) => { - // Keep the old ID for one, assign a new ID to the other - piece1.id = gj.features[snappedIndex!].id; - piece2.id = newFeatureId(gj); - - // The properties get lost. Copy everything to both - piece1.properties = JSON.parse( - JSON.stringify(gj.features[snappedIndex!].properties), - ); - // "Deep clone" - piece2.properties = JSON.parse(JSON.stringify(piece1.properties)); - - fixRouteProperties( - gj.features[snappedIndex!] as OurFeature, - piece1 as OurFeature, - piece2 as OurFeature, - snappedCursor!, - ); - - // Replace the one LineString we snapped to with the two new pieces - gj.features.splice( - snappedIndex!, - 1, - piece1 as OurFeature, - piece2 as OurFeature, - ); - - return gj; - }); - } + return; + } - // Stay in this mode, but reset state - snappedCursor = null; - snappedIndex = null; + let result = splitRoute( + $gjSchemeCollection.features[snappedIndex] as unknown as Feature< + LineString, + RouteProps + >, + snappedCursor!, + ); + + if (result != null) { + let [piece1, piece2] = result; + gjSchemeCollection.update((gj) => { + // Keep the old ID for one, assign a new ID to the other + piece1.id = gj.features[snappedIndex!].id; + piece2.id = newFeatureId(gj); + + // Replace the one LineString we snapped to with the two new pieces + gj.features.splice( + snappedIndex!, + 1, + // TODO Maybe splitRoute's types can be more precise. Existing + // properties get copied, so this is valid. + piece1 as unknown as OurFeature, + piece2 as unknown as OurFeature, + ); + + return gj; + }); } + + // Stay in this mode, but reset state + snappedCursor = null; + snappedIndex = null; } // The escape key isn't registered at all for keypress, so use keydown @@ -156,87 +139,6 @@ }, }; } - - // TODO Move this function to route-snapper, and remove some turf dependencies. - // The implementation there would likely be Rust, to avoid depending on turf in the NPM package... - function fixRouteProperties( - original: OurFeature, - piece1: OurFeature, - piece2: OurFeature, - splitPt: Feature, - ) { - // Fix length - piece1.properties.length_meters = - length(piece1, { units: "kilometers" }) * 1000.0; - piece2.properties.length_meters = - length(piece2, { units: "kilometers" }) * 1000.0; - - piece1.properties.waypoints = []; - piece2.properties.waypoints = []; - - let splitDist = distanceAlongLine(original, splitPt); - let firstPiece = true; - // TODO Can we iterate over an array's contents and get the index at the same time? - let i = 0; - for (let waypt of original.properties.waypoints!) { - let wayptDist = distanceAlongLine( - original, - point([waypt.lon, waypt.lat]), - ); - if (firstPiece) { - if (wayptDist < splitDist) { - piece1.properties.waypoints.push(waypt); - } else { - // We found where the split occurs. We'll insert a new waypoint - // representing the split at the end of piece1 and the beginning of - // piece2. Should that new waypoint be snapped or freehand? There are - // 4 cases for where the split (|) happens with regards to a - // (s)napped and (f)reehand point: - // - // 1) s | s - // 2) s | f - // 3) f | s - // 4) f | f - // - // Only in case 1 should the new waypoint introduced at (|) be - // snapped. - // TODO Problem: in case 1, what if we split in the middle of a road, - // far from an intersection? - - // Note i > 0; splitDist can't be before the first waypoint (distance 0) - // TODO Edge case: somebody manages to exactly click a waypoint - let snapped = - waypt.snapped && original.properties.waypoints![i - 1].snapped; - - piece1.properties.waypoints.push({ - lon: splitPt.geometry.coordinates[0], - lat: splitPt.geometry.coordinates[1], - snapped, - }); - - firstPiece = false; - piece2.properties.waypoints.push({ - lon: splitPt.geometry.coordinates[0], - lat: splitPt.geometry.coordinates[1], - snapped, - }); - piece2.properties.waypoints.push(waypt); - } - } else { - piece2.properties.waypoints.push(waypt); - } - i++; - } - } - - // Returns the distance of a point along a line-string from the start, in - // meters. The point should be roughly on the line. - function distanceAlongLine(line: Feature, point: Feature) { - // TODO Is there a cheaper way to do this? - let start = line.geometry.coordinates[0]; - let sliced = lineSlice(start, point, line); - return length(sliced, { units: "kilometers" }) * 1000.0; - }