mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-11-13 15:51:00 +00:00
3238fbc517
The PDF export routed the whole trip under an 8 s deadline while the router is asked one leg at a time, a second apart. A cold month-long trip got about seven legs routed per export; the rest printed as straight lines at "0 m" under a total that looked final, and every further export routed a few more days from the device cache. The export now waits for every leg, bounded by the request pacing, the rate-limit retries and a per-leg timeout. The export dialog and the phone sheet count the legs while they route and offer to print without the rest. Any leg left without a road is marked in the document: the day reads "not routed" or "at least ...", the total says it is a floor, and a note names how many legs are missing. The planner overview shares the same wording. The PDF also routes what the planner map routes now: the road trip's vias, the multi-day tour stages and the reader's distance unit. Closes #2876
73 lines
3.5 KiB
TypeScript
73 lines
3.5 KiB
TypeScript
import { useSettingsStore } from '../../store/settingsStore';
|
|
import type { DistanceUnit } from '../../types';
|
|
import type { ViasByDay } from '../../utils/dayRouteVias';
|
|
import { formatDistance } from '../../utils/units';
|
|
import type { TourStageStops } from '../Map/dayTourProjection';
|
|
import {
|
|
routeTrip,
|
|
type TripRouteInputs,
|
|
type TripRouteProgress,
|
|
type TripRouteSummary,
|
|
} from '../Map/tripRouteGeometry';
|
|
import { renderTripMapImage } from './tripMapImage';
|
|
import { buildTripMapSvg } from './tripMapSvg';
|
|
|
|
/** What the planner knows about the trip's route beyond the plan, and how it follows the routing. */
|
|
export interface TripPdfRouteOptions {
|
|
/** The roads picked in the road trip (#2580), so the print bends where the planner map does. */
|
|
viasByDay?: ViasByDay;
|
|
/** Where the stages of the trip's multi-day tours run. */
|
|
tourStageStops?: TourStageStops;
|
|
/** Stops routing early: the legs asked by then are printed, the rest stay straight and are marked. */
|
|
signal?: AbortSignal;
|
|
onProgress?: TripRouteProgress;
|
|
}
|
|
|
|
interface TripPdfRoute {
|
|
route: TripRouteSummary | null;
|
|
mapSvg: string | null;
|
|
}
|
|
|
|
const MAP_FRAME = { width: 720, height: 420 };
|
|
|
|
/**
|
|
* The trip's route as one map (#1736), from the builder the planner overview uses and
|
|
* fed what the overview is fed, so the document and the screen show the same roads.
|
|
*
|
|
* Every leg is waited for: a deadline here printed a month-long trip with most of its
|
|
* days as straight lines and a total that looked final (#2876). Fail-safe all the same:
|
|
* a routing failure means no map rather than no PDF.
|
|
*/
|
|
export async function drawTripRoute(
|
|
input: Omit<TripRouteInputs, 'viasByDay' | 'tourStageStops'>,
|
|
{ tripId, unit, options = {} }: { tripId: number; unit: DistanceUnit; options?: TripPdfRouteOptions }
|
|
): Promise<TripPdfRoute> {
|
|
const route = await routeTrip(
|
|
{ ...input, viasByDay: options.viasByDay, tourStageStops: options.tourStageStops },
|
|
{ profile: 'driving', tripId, unit, signal: options.signal, onProgress: options.onProgress }
|
|
).catch((err: unknown) => {
|
|
// Logged rather than swallowed: a map that is silently absent looks exactly like a
|
|
// trip that has no route, and the two need different answers from whoever is
|
|
// looking. The export itself carries on — the itinerary matters more than the map.
|
|
console.warn('[tripPdfMap] routing the trip failed; the export continues without a map', err);
|
|
return null;
|
|
});
|
|
if (!route) return { route: null, mapSvg: null };
|
|
// A real basemap first — at city scale the bundled outlines are a country-sized
|
|
// blank, and only streets carry context that small. The outline map is what is left
|
|
// when there is no WebGL, no network, or a style that will not load.
|
|
const frame = { ...MAP_FRAME, formatDistance: (km: number) => formatDistance(km, unit) };
|
|
const style = useSettingsStore.getState().settings.maplibre_style;
|
|
const mapSvg = (await renderTripMapImage(route.days, { ...frame, style })) ?? buildTripMapSvg(route.days, frame);
|
|
// The other way to end up mapless, and the one that is not a failure: nothing in the
|
|
// trip routed. Only worth saying when there were stops to route — a trip nobody has
|
|
// planned yet is meant to print without a map.
|
|
if (!mapSvg && Object.values(input.assignments).some((list) => list?.length)) {
|
|
console.warn(
|
|
`[tripPdfMap] no map drawn: ${route.days.length} of ${input.days.length} day(s) produced a route.` +
|
|
' A day needs two located stops, or one located stop with an accommodation either side of it.'
|
|
);
|
|
}
|
|
return { route, mapSvg };
|
|
}
|