mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-19 13:21:46 +00:00
49b3af8b0d
Optimize day routes around the accommodation
When a day has an accommodation set, the route optimizer now treats it as
the day's home base: it optimizes a loop that leaves the hotel and returns
to it, so the stop nearest the hotel comes first. On a transfer day -
checking out of one hotel and into another - the route runs from the first
hotel to the second instead.
The optimizer also gained a 2-opt pass on top of the nearest-neighbor
ordering, which removes the crossings the greedy pass used to leave behind.
A new display setting ("optimize route from accommodation", on by default)
lets you turn the anchoring off.
Confirm before deleting notes
Deleting a plan note or a collab note now asks for confirmation first. On
phones and tablets the edit and delete icons sit close together and were
easy to mis-tap, which deleted notes with no way back.
50 lines
2.1 KiB
TypeScript
50 lines
2.1 KiB
TypeScript
import type { Day, Accommodation, RouteAnchors } from '../types'
|
|
|
|
export const getDayOrder = (day: Day, days: Day[]): number =>
|
|
day.day_number ?? days.indexOf(day)
|
|
|
|
// Derives route anchors from the accommodation(s) active on a day. A single hotel is the day's home
|
|
// base, so the route is a loop that starts and ends there. A transfer day — checking out of one hotel
|
|
// and into another — instead runs from the morning hotel to the evening one.
|
|
export const getAccommodationAnchors = (
|
|
day: Day,
|
|
days: Day[],
|
|
accommodations: Accommodation[],
|
|
): RouteAnchors => {
|
|
const located = accommodations.filter(a =>
|
|
a.place_lat != null && a.place_lng != null &&
|
|
isDayInAccommodationRange(day, a.start_day_id, a.end_day_id, days),
|
|
)
|
|
if (located.length === 0) return {}
|
|
|
|
const toAnchor = (a: Accommodation) => ({ lat: a.place_lat as number, lng: a.place_lng as number })
|
|
|
|
const checkOut = located.find(a => a.end_day_id === day.id) // the hotel you leave this morning
|
|
const checkIn = located.find(a => a.start_day_id === day.id) // the hotel you arrive at tonight
|
|
if (checkOut && checkIn && checkOut !== checkIn) {
|
|
return { start: toAnchor(checkOut), end: toAnchor(checkIn) }
|
|
}
|
|
|
|
const hotel = toAnchor(located[0])
|
|
return { start: hotel, end: hotel }
|
|
}
|
|
|
|
export const isDayInAccommodationRange = (
|
|
day: Day,
|
|
startDayId: number,
|
|
endDayId: number,
|
|
days: Day[],
|
|
): boolean => {
|
|
const startDay = days.find(d => d.id === startDayId)
|
|
const endDay = days.find(d => d.id === endDayId)
|
|
if (!startDay || !endDay) {
|
|
// Endpoint days not in the loaded array (e.g. sparse test data or partial load).
|
|
// Fall back to numeric ID range — acceptable since non-monotonic IDs only arise when
|
|
// both endpoints are present in a fully-loaded trip's days list.
|
|
return day.id >= Math.min(startDayId, endDayId) && day.id <= Math.max(startDayId, endDayId)
|
|
}
|
|
const lo = Math.min(getDayOrder(startDay, days), getDayOrder(endDay, days))
|
|
const hi = Math.max(getDayOrder(startDay, days), getDayOrder(endDay, days))
|
|
return getDayOrder(day, days) >= lo && getDayOrder(day, days) <= hi
|
|
}
|