Skip to content

Commit

Permalink
Move route split logic to upstream TS package
Browse files Browse the repository at this point in the history
  • Loading branch information
dabreegster committed May 20, 2024
1 parent 47322d1 commit 0bac9b8
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 145 deletions.
15 changes: 9 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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"
Expand Down
8 changes: 5 additions & 3 deletions src/lib/draw/EditGeometryMode.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -40,14 +40,16 @@
name = interventionName(feature);
if (feature.geometry.type == "LineString") {
$routeTool?.editExistingRoute(feature as Feature<LineString, Props>);
$routeTool?.editExistingRoute(
feature as unknown as Feature<LineString, RouteProps>,
);
$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<Polygon, Props>);
$routeTool?.editExistingArea(feature as Feature<Polygon, AreaProps>);
$routeTool?.addEventListenerSuccess(onSuccess);
$routeTool?.addEventListenerUpdated(onUpdate);
$routeTool?.addEventListenerFailure(onFailure);
Expand Down
168 changes: 35 additions & 133 deletions src/lib/draw/route/SplitRouteMode.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@
// Note this component has to embedded underneath <Map> for the <GeoJSON>
// 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";
Expand Down Expand Up @@ -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<LineString>,
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<LineString>,
piece1 as OurFeature<LineString>,
piece2 as OurFeature<LineString>,
snappedCursor!,
);
// Replace the one LineString we snapped to with the two new pieces
gj.features.splice(
snappedIndex!,
1,
piece1 as OurFeature<LineString>,
piece2 as OurFeature<LineString>,
);
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<LineString>,
piece2 as unknown as OurFeature<LineString>,
);
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
Expand All @@ -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<LineString>,
piece1: OurFeature<LineString>,
piece2: OurFeature<LineString>,
splitPt: Feature<Point>,
) {
// 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<LineString>, point: Feature<Point>) {
// 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;
}
</script>

<svelte:window on:keydown={onKeyDown} />
Expand Down

0 comments on commit 0bac9b8

Please sign in to comment.