Merge pull request #614 from trails-cool/fix/planner-clear-route-under-two

fix(planner): clear the route when waypoints drop below two
This commit is contained in:
Ullrich Schäfer 2026-07-16 23:50:21 +02:00 committed by GitHub
commit 1473e2f291
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 54 additions and 1 deletions

View file

@ -6,6 +6,8 @@ import {
PROFILE_KEY,
ROAD_METADATA_KEYS,
clearRouteData,
clearComputedRoute,
hasComputedRoute,
extractNoGoAreas,
getBaseLayer,
getColorMode,
@ -266,6 +268,28 @@ describe("clearRouteData", () => {
});
});
describe("clearComputedRoute / hasComputedRoute", () => {
it("detects computed geometry and clears it while keeping the profile", () => {
const { doc, routeData } = createDoc();
expect(hasComputedRoute(routeData)).toBe(false);
writeComputedRoute(doc, routeData, enrichedFixture());
setProfile(routeData, "trekking");
setColorMode(routeData, "surface");
expect(hasComputedRoute(routeData)).toBe(true);
clearComputedRoute(doc, routeData);
expect(hasComputedRoute(routeData)).toBe(false);
expect(getCoordinates(routeData)).toBeNull();
expect(readGeojson(routeData)).toBeUndefined();
expect(readRoadMetadata(routeData).surfaces).toEqual([]);
// profile (and other prefs) survive — unlike clearRouteData
expect(getProfile(routeData)).toBe("trekking");
expect(getColorMode(routeData)).toBe("surface");
});
});
describe("extractNoGoAreas", () => {
it("reads areas and drops degenerate ones", () => {
const doc = new Y.Doc();

View file

@ -189,7 +189,12 @@ export function setGeojson(routeData: Y.Map<unknown>, geojson: unknown): void {
}
/** Removes the computed route and profile (view preferences are kept). */
export function clearRouteData(doc: Y.Doc, routeData: Y.Map<unknown>): void {
/**
* Clear the computed route geometry + per-coordinate road metadata, but keep
* the routing profile. Used when the route can no longer exist (fewer than two
* waypoints) so the stale line stops rendering without resetting the profile.
*/
export function clearComputedRoute(doc: Y.Doc, routeData: Y.Map<unknown>): void {
doc.transact(() => {
routeData.delete("geojson");
routeData.delete("coordinates");
@ -198,6 +203,17 @@ export function clearRouteData(doc: Y.Doc, routeData: Y.Map<unknown>): void {
for (const key of ROAD_METADATA_KEYS) {
routeData.delete(key);
}
});
}
/** True if the route data currently holds computed geometry. */
export function hasComputedRoute(routeData: Y.Map<unknown>): boolean {
return routeData.get("coordinates") !== undefined || routeData.get("geojson") !== undefined;
}
export function clearRouteData(doc: Y.Doc, routeData: Y.Map<unknown>): void {
doc.transact(() => {
clearComputedRoute(doc, routeData);
routeData.delete(PROFILE_KEY);
});
}

View file

@ -15,6 +15,8 @@ import {
extractNoGoAreas,
getProfile,
writeComputedRoute,
clearComputedRoute,
hasComputedRoute,
} from "./route-data.ts";
import { extractWaypoints } from "./waypoint-ymap.ts";
@ -210,11 +212,22 @@ export function useRouting(yjs: YjsState | null, sessionId: string) {
triggerRecompute();
};
// When waypoints drop below two, no route can exist — drop the stale
// geometry so the line stops rendering. (Recompute for >=2 is triggered by
// the add/move/delete handlers themselves.)
const waypointsObserver = () => {
if (getWaypointsFromYjs(yjs.waypoints).length < 2 && hasComputedRoute(yjs.routeData)) {
clearComputedRoute(yjs.doc, yjs.routeData);
}
};
yjs.routeData.observe(profileObserver);
yjs.noGoAreas.observeDeep(noGoObserver);
yjs.waypoints.observeDeep(waypointsObserver);
return () => {
yjs.routeData.unobserve(profileObserver);
yjs.noGoAreas.unobserveDeep(noGoObserver);
yjs.waypoints.unobserveDeep(waypointsObserver);
};
}, [yjs, isHost, requestRoute]);