fix(itinerary): make optimize nearest-neighbor context-aware of anchor positions

The optimize function always started nearest-neighbor from the first
array element, ignoring where the traveler actually is after preceding
anchors (flights, lodging). Now passes the preceding anchor's exit
coordinates (destination for transportation) so the algorithm picks
the spatially nearest item as the starting point.
This commit is contained in:
2026-03-08 19:41:19 +00:00
parent 73289725eb
commit 604b52bcc7

View File

@@ -1052,14 +1052,35 @@
} }
function optimizeNearestNeighborSegment( function optimizeNearestNeighborSegment(
items: ResolvedItineraryItem[] items: ResolvedItineraryItem[],
startCoords?: { latitude: number; longitude: number } | null
): ResolvedItineraryItem[] { ): ResolvedItineraryItem[] {
if (items.length < 2) return [...items]; if (items.length < 2) return [...items];
const remaining = [...items]; const remaining = [...items];
const sorted: ResolvedItineraryItem[] = []; const sorted: ResolvedItineraryItem[] = [];
const firstItem = remaining.shift(); // When we have context coordinates from a preceding anchor (e.g. the
// destination of a flight), pick the nearest item to that position as
// the starting point instead of blindly using the first array element.
let firstItem: ResolvedItineraryItem | undefined;
if (startCoords) {
let nearestIndex = 0;
let nearestDistance = Number.POSITIVE_INFINITY;
for (let i = 0; i < remaining.length; i++) {
const coords = getCoordinatesFromItineraryItem(remaining[i]);
if (!coords) continue;
const distance = haversineDistanceBetweenCoordinates(startCoords, coords);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestIndex = i;
}
}
firstItem = remaining.splice(nearestIndex, 1)[0];
} else {
firstItem = remaining.shift();
}
if (!firstItem) return items; if (!firstItem) return items;
sorted.push(firstItem); sorted.push(firstItem);
@@ -1193,7 +1214,24 @@
const optimizedPath: ResolvedItineraryItem[] = []; const optimizedPath: ResolvedItineraryItem[] = [];
for (let segmentIndex = 0; segmentIndex < movableSegments.length; segmentIndex += 1) { for (let segmentIndex = 0; segmentIndex < movableSegments.length; segmentIndex += 1) {
const optimizedSegment = optimizeNearestNeighborSegment(movableSegments[segmentIndex]); // Determine where the traveler will be coming from so the
// nearest-neighbor search starts from a spatially sensible point.
let startCoords: { latitude: number; longitude: number } | null = null;
if (segmentIndex > 0) {
const precedingAnchor = chronologicalAnchors[segmentIndex - 1];
const anchorType = precedingAnchor?.item?.type || '';
// For transportation anchors use the *destination* (where the
// traveler arrives), for everything else use the default coords.
startCoords = getCoordinatesFromItineraryItem(
precedingAnchor,
anchorType === 'transportation' ? 'destination' : 'origin'
);
}
const optimizedSegment = optimizeNearestNeighborSegment(
movableSegments[segmentIndex],
startCoords
);
optimizedPath.push(...optimizedSegment); optimizedPath.push(...optimizedSegment);
if (segmentIndex < chronologicalAnchors.length) { if (segmentIndex < chronologicalAnchors.length) {