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:
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user