@geoapify/route-planner-sdk
Version:
TypeScript SDK for the Geoapify Route Planner API. Supports route optimization, delivery planning, and timeline visualization in browser and Node.js
137 lines (136 loc) • 7.63 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { IndexConverter } from "../../../helpers/index-converter";
import { InvalidInsertionPosition } from "../../../models";
import { AgentPlanRecalculator, WaypointBuilder } from "../strategies";
import { RouteViolationValidator } from "../validations";
export class WaypointMoveHelper {
static execute(context, agentIdOrIndex, fromWaypointIndex, toWaypointIndex) {
return __awaiter(this, void 0, void 0, function* () {
const agentIndex = IndexConverter.convertAgentToIndex(context.getRawData(), agentIdOrIndex, true);
context.validateAgent(agentIndex);
const agentFeature = context.getAgentFeature(agentIndex);
const waypoints = agentFeature.properties.waypoints;
const actions = agentFeature.properties.actions;
if (waypoints.length === 0) {
return;
}
this.validateWaypointIndex(waypoints, fromWaypointIndex, agentIndex, 'from');
this.validateWaypointIndex(waypoints, toWaypointIndex, agentIndex, 'to');
this.validatePickupDeliveryOrder(waypoints, fromWaypointIndex, toWaypointIndex, agentIndex);
if (fromWaypointIndex === toWaypointIndex) {
return;
}
const existingLegs = agentFeature.properties.legs || [];
const legDataMap = WaypointBuilder.buildLegDataMap(waypoints, existingLegs);
const waypointToMove = waypoints[fromWaypointIndex];
waypoints.splice(fromWaypointIndex, 1);
waypoints.splice(toWaypointIndex, 0, waypointToMove);
this.mergeAdjacentDuplicateLocations(waypoints, toWaypointIndex);
agentFeature.properties.legs = WaypointBuilder.rebuildLegs(waypoints, legDataMap);
yield AgentPlanRecalculator.recalculate(context, agentIndex);
RouteViolationValidator.validate(context, agentIndex);
});
}
static validateWaypointIndex(waypoints, index, agentIndex, label) {
if (index < 0 || index >= waypoints.length) {
throw new InvalidInsertionPosition(`Waypoint ${label} index ${index} out of range (0-${waypoints.length - 1})`, agentIndex, index);
}
const waypoint = waypoints[index];
const isStartOrEnd = waypoint.actions.some(action => action.type === 'start' || action.type === 'end');
if (isStartOrEnd) {
throw new InvalidInsertionPosition(`Cannot move waypoint containing start or end action (index ${index})`, agentIndex, index);
}
}
static validatePickupDeliveryOrder(waypoints, fromWaypointIndex, toWaypointIndex, agentIndex) {
const fromWaypoint = waypoints[fromWaypointIndex];
const shipmentIndexes = new Set();
for (const action of fromWaypoint.actions) {
if (typeof action.shipment_index === "number") {
shipmentIndexes.add(action.shipment_index);
}
}
if (shipmentIndexes.size === 0) {
return;
}
for (const shipmentIndex of shipmentIndexes) {
const pickupPosition = this.findShipmentActionPosition(waypoints, shipmentIndex, "pickup");
const deliveryPosition = this.findShipmentActionPosition(waypoints, shipmentIndex, "delivery");
if (!pickupPosition || !deliveryPosition) {
continue;
}
const projectedPickupWaypointIndex = this.mapMovedWaypointIndex(pickupPosition.waypointIndex, fromWaypointIndex, toWaypointIndex);
const projectedDeliveryWaypointIndex = this.mapMovedWaypointIndex(deliveryPosition.waypointIndex, fromWaypointIndex, toWaypointIndex);
const validOrder = projectedPickupWaypointIndex < projectedDeliveryWaypointIndex ||
(projectedPickupWaypointIndex === projectedDeliveryWaypointIndex &&
pickupPosition.actionIndex < deliveryPosition.actionIndex);
if (!validOrder) {
throw new InvalidInsertionPosition(`Cannot move waypoint ${fromWaypointIndex} before pickup for shipment ${shipmentIndex}`, agentIndex, toWaypointIndex);
}
}
}
static findShipmentActionPosition(waypoints, shipmentIndex, actionType) {
for (let waypointIndex = 0; waypointIndex < waypoints.length; waypointIndex++) {
const waypoint = waypoints[waypointIndex];
for (let actionIndex = 0; actionIndex < waypoint.actions.length; actionIndex++) {
const action = waypoint.actions[actionIndex];
if (action.type === actionType && action.shipment_index === shipmentIndex) {
return { waypointIndex, actionIndex };
}
}
}
return undefined;
}
static mapMovedWaypointIndex(originalWaypointIndex, fromWaypointIndex, toWaypointIndex) {
if (originalWaypointIndex === fromWaypointIndex) {
return toWaypointIndex;
}
if (fromWaypointIndex < toWaypointIndex) {
if (originalWaypointIndex > fromWaypointIndex && originalWaypointIndex <= toWaypointIndex) {
return originalWaypointIndex - 1;
}
return originalWaypointIndex;
}
if (fromWaypointIndex > toWaypointIndex) {
if (originalWaypointIndex >= toWaypointIndex && originalWaypointIndex < fromWaypointIndex) {
return originalWaypointIndex + 1;
}
return originalWaypointIndex;
}
return originalWaypointIndex;
}
static mergeAdjacentDuplicateLocations(waypoints, movedIndex) {
const hasPreviousMatch = movedIndex > 0 &&
this.haveSameLocation(waypoints[movedIndex - 1], waypoints[movedIndex]);
const hasNextMatch = movedIndex < waypoints.length - 1 &&
this.haveSameLocation(waypoints[movedIndex], waypoints[movedIndex + 1]);
if (hasPreviousMatch) {
this.mergeWaypoints(waypoints, movedIndex - 1, movedIndex);
}
else if (hasNextMatch) {
this.mergeWaypoints(waypoints, movedIndex, movedIndex + 1);
}
}
static haveSameLocation(firstWaypoint, secondWaypoint) {
if (firstWaypoint.original_location_index !== undefined && secondWaypoint.original_location_index !== undefined) {
return firstWaypoint.original_location_index === secondWaypoint.original_location_index;
}
const [lat1, lon1] = firstWaypoint.original_location;
const [lat2, lon2] = secondWaypoint.original_location;
return lat1 === lat2 && lon1 === lon2;
}
static mergeWaypoints(waypoints, keepIndex, removeIndex) {
const keepWaypoint = waypoints[keepIndex];
const removeWaypoint = waypoints[removeIndex];
keepWaypoint.actions.push(...removeWaypoint.actions);
keepWaypoint.duration += removeWaypoint.duration;
waypoints.splice(removeIndex, 1);
}
}