@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
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 { 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;
}
}