UNPKG

@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

128 lines (127 loc) 7.21 kB
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 { PreserveOrderBaseHelper } from "./preserve-order-base-helper"; import { InsertPositionResolver } from "../utils/insert-position-resolver"; import { RouteEditorHelper } from "../utils/route-editor-helper"; import { InsertionCostCalculator } from "../utils/insertion-cost-calculator"; const LOCATION_EPSILON = 1e-6; export class PreserveOrderJobHelper extends PreserveOrderBaseHelper { static determineInsertPosition(context, agentIndex, firstJobIndex, options) { var _a; return __awaiter(this, void 0, void 0, function* () { const job = RouteEditorHelper.getJobByIndex(context, firstJobIndex); const jobLocation = RouteEditorHelper.resolveJobLocation(context, job); const waypoints = context.getAgentFeature(agentIndex).properties.waypoints || []; // append: true (no position) → Append if (InsertPositionResolver.shouldAppend(options)) { const position = yield this.getEndPosition(context, agentIndex); return { position, createWaypoint: true }; } // afterId/afterWaypointIndex + append: true → Insert at specified position if (InsertPositionResolver.hasExplicitInsertPosition(options)) { const position = InsertPositionResolver.resolveInsertPosition(options); return { position, createWaypoint: true }; } // afterId/afterWaypointIndex + append: false → Optimize after position if (options.afterWaypointIndex !== undefined && !options.append) { const minPosition = (_a = options.afterWaypointIndex) !== null && _a !== void 0 ? _a : 0; const reusableWaypointIndex = this.findExistingWaypointByLocation(waypoints, jobLocation, minPosition + 1); if (reusableWaypointIndex !== -1) { return { position: reusableWaypointIndex, createWaypoint: false }; } const position = yield this.findOptimalInsertPositionAfter(context, agentIndex, firstJobIndex, minPosition); return { position, createWaypoint: true }; } // No position params → Use Route Matrix API to find optimal position anywhere const reusableWaypointIndex = this.findExistingWaypointByLocation(waypoints, jobLocation, 0); if (reusableWaypointIndex !== -1) { return { position: reusableWaypointIndex, createWaypoint: false }; } const position = yield this.findOptimalInsertPosition(context, agentIndex, firstJobIndex); return { position, createWaypoint: true }; }); } static findOptimalInsertPosition(context, agentIndex, jobIndex) { return __awaiter(this, void 0, void 0, function* () { const job = RouteEditorHelper.getJobByIndex(context, jobIndex); const jobLocation = RouteEditorHelper.resolveJobLocation(context, job); const agentFeature = context.getAgentFeature(agentIndex); if (!agentFeature) { return 1; } const routeLocations = InsertPositionResolver.extractRouteLocations(agentFeature); if (routeLocations.length === 0) { return 1; } const travelTimes = yield this.calculateTravelTimes(context, routeLocations, jobLocation); const optimalIndex = yield InsertionCostCalculator.findOptimalInsertionPoint(context, agentIndex, routeLocations, jobLocation, { canInsertBeforeFirst: !this.hasAgentStartLocation(context, agentIndex), canInsertAfterLast: !this.hasAgentEndLocation(context, agentIndex), travelTimes }); return optimalIndex; }); } static findOptimalInsertPositionAfter(context, agentIndex, jobIndex, minPosition) { return __awaiter(this, void 0, void 0, function* () { const job = RouteEditorHelper.getJobByIndex(context, jobIndex); const jobLocation = RouteEditorHelper.resolveJobLocation(context, job); const agentFeature = context.getAgentFeature(agentIndex); const allRouteLocations = InsertPositionResolver.extractRouteLocations(agentFeature); const routeStartIndex = Math.max(0, minPosition); const routeLocationsAfter = allRouteLocations.slice(routeStartIndex); if (routeLocationsAfter.length === 0) { return routeStartIndex; } const travelTimes = yield this.calculateTravelTimes(context, routeLocationsAfter, jobLocation); const optimalIndex = yield InsertionCostCalculator.findOptimalInsertionPoint(context, agentIndex, routeLocationsAfter, jobLocation, { canInsertBeforeFirst: false, canInsertAfterLast: !this.hasAgentEndLocation(context, agentIndex), travelTimes }); return routeStartIndex + optimalIndex; }); } static calculateTravelTimes(context, route, newLocation) { return __awaiter(this, void 0, void 0, function* () { const matrixHelper = context.getMatrixHelper(); const [timesToNew, timesFromNew] = yield Promise.all([ matrixHelper.calculateTimesToLocation(route, newLocation), matrixHelper.calculateTimesFromLocation(newLocation, route) ]); const travelTimes = []; for (let i = 0; i < route.length; i++) { travelTimes.push({ locationFrom: route[i], locationTo: newLocation, time: timesToNew[i] }); travelTimes.push({ locationFrom: newLocation, locationTo: route[i], time: timesFromNew[i] }); } return travelTimes; }); } static findExistingWaypointByLocation(waypoints, targetLocation, minWaypointIndex) { for (let i = Math.max(0, minWaypointIndex); i < waypoints.length; i++) { const waypointLocation = waypoints[i].original_location || waypoints[i].location; if (this.sameLocation(waypointLocation, targetLocation)) { return i; } } return -1; } static sameLocation(a, b) { return Math.abs(a[0] - b[0]) <= LOCATION_EPSILON && Math.abs(a[1] - b[1]) <= LOCATION_EPSILON; } }