mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
6e3eb591b5
Paddle tours are planned on the water by TREK's own router: a water network per paddle region, built from the same Geofabrik extract as the obstacles and searched in a worker thread of the server (no sidecar). Rivers, canals and lakes (direct or along the shore), portages round weirs, locks by policy, whitewater by boat, gaps marked. Found portages and locks become points of their own; an unplannable leg says why and offers a way out. - Shaping points: a click on the line sets a point that steers the route without being a stop (hikes and paddle tours); recompute a single leg. - Lake style direct/shore, suggested from the wind forecast. - Regions load by themselves where a paddler plans (admin setting: automatic, on request, off; size cap and a daily count), with requests for the admins, a notification, and the load followed on the leg. - Portage from the last point, portages follow footways via the walking router (or stay straight), "put in here" on slipways and put-ins. - MCP plan_paddle_route with the REST route's rules.
90 lines
3.7 KiB
TypeScript
90 lines
3.7 KiB
TypeScript
import {
|
|
recommendedPaddleSpeedKmh,
|
|
type PaddleBoatType,
|
|
type PaddleLakeStyle,
|
|
type PaddleWaterContext,
|
|
type TourListItem,
|
|
} from '@trek/shared';
|
|
import { useCallback, useState } from 'react';
|
|
|
|
export interface PaddleFieldValues {
|
|
boatType: PaddleBoatType | null;
|
|
waterContext: PaddleWaterContext | null;
|
|
/** The speed the paddler chose, km/h; null while the boat's suggested speed applies. */
|
|
speedKmh: number | null;
|
|
/** PAD-07: the carrying speed the paddler chose, km/h; null while the starting value applies. Absent in an older draft. */
|
|
portageSpeedKmh?: number | null;
|
|
/** PAD-11: how lakes are routed; null (or absent, in an older draft) is straight over. */
|
|
lakeStyle?: PaddleLakeStyle | null;
|
|
}
|
|
|
|
export const NO_PADDLE_FIELDS: PaddleFieldValues = {
|
|
boatType: null,
|
|
waterContext: null,
|
|
speedKmh: null,
|
|
portageSpeedKmh: null,
|
|
lakeStyle: null,
|
|
};
|
|
|
|
/** The paddle fields of a saved tour, or none for a hike or a list cached before they existed. */
|
|
export function paddleFieldsOf(
|
|
tour: Pick<
|
|
TourListItem,
|
|
'boat_type' | 'water_context' | 'paddle_speed_kmh' | 'portage_speed_kmh' | 'paddle_lake_style'
|
|
>
|
|
): PaddleFieldValues {
|
|
return {
|
|
boatType: tour.boat_type ?? null,
|
|
waterContext: tour.water_context ?? null,
|
|
speedKmh: tour.paddle_speed_kmh ?? null,
|
|
portageSpeedKmh: tour.portage_speed_kmh ?? null,
|
|
lakeStyle: tour.paddle_lake_style ?? null,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* PAD-01: the boat, the water and the planning speed of a paddle tour. A
|
|
* speed the paddler typed stays when the boat changes; the boat's suggestion
|
|
* is offered beside it and only taken on purpose. `onEdit` tells the planner
|
|
* the draft changed, like any other field.
|
|
*/
|
|
export function usePaddleFields(initial: PaddleFieldValues, onEdit: () => boolean) {
|
|
const [values, setValues] = useState<PaddleFieldValues>(initial);
|
|
|
|
// Only a real change marks the draft edited: choosing the boat already chosen is no edit.
|
|
const edit = useCallback(
|
|
(next: Partial<PaddleFieldValues>) => {
|
|
const merged = { ...values, ...next };
|
|
const same =
|
|
merged.boatType === values.boatType &&
|
|
merged.waterContext === values.waterContext &&
|
|
Object.is(merged.speedKmh, values.speedKmh) &&
|
|
Object.is(merged.portageSpeedKmh ?? null, values.portageSpeedKmh ?? null) &&
|
|
(merged.lakeStyle ?? null) === (values.lakeStyle ?? null);
|
|
if (!same && onEdit()) setValues(merged);
|
|
},
|
|
[onEdit, values]
|
|
);
|
|
|
|
const recommended = recommendedPaddleSpeedKmh(values.boatType);
|
|
return {
|
|
...values,
|
|
portageSpeedKmh: values.portageSpeedKmh ?? null,
|
|
lakeStyle: values.lakeStyle ?? 'direct',
|
|
recommendedSpeedKmh: recommended,
|
|
/** The speed the time is worked out with: the chosen one, else the boat's suggestion. */
|
|
effectiveSpeedKmh: values.speedKmh ?? recommended,
|
|
setBoatType: useCallback((boatType: PaddleBoatType) => edit({ boatType }), [edit]),
|
|
setWaterContext: useCallback((waterContext: PaddleWaterContext) => edit({ waterContext }), [edit]),
|
|
setSpeedKmh: useCallback((speedKmh: number | null) => edit({ speedKmh }), [edit]),
|
|
/** PAD-07: the carrying speed, or back to the starting value with null. */
|
|
setPortageSpeedKmh: useCallback((portageSpeedKmh: number | null) => edit({ portageSpeedKmh }), [edit]),
|
|
/** PAD-11: straight over the lakes, or along their shores. */
|
|
setLakeStyle: useCallback((lakeStyle: PaddleLakeStyle) => edit({ lakeStyle }), [edit]),
|
|
/** Back to the boat's suggestion, by choice. */
|
|
adoptRecommendedSpeed: useCallback(() => edit({ speedKmh: null }), [edit]),
|
|
/** Loaded from a tour or a draft, or cleared; not an edit. */
|
|
load: useCallback((next: PaddleFieldValues) => setValues(next), []),
|
|
};
|
|
}
|