@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
313 lines (312 loc) • 14 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 { RoutePlannerResult } from "./models/entities/route-planner-result";
import { RouteResultJobEditor } from "./tools/route-editor/route-result-job-editor";
import { RouteResultShipmentEditor } from "./tools/route-editor/route-result-shipment-editor";
import { AgentReoptimizeHelper, AgentTimeOffsetHelper, WaypointMoveHelper } from "./tools/route-editor/helpers";
import { InsertPositionResolver } from "./tools/route-editor/strategies/preserve-order/utils/insert-position-resolver";
import { Utils } from "./tools/utils";
import { InvalidInsertionPosition, InvalidParameter } from "./models";
import { IndexConverter } from "./helpers/index-converter";
/**
* Editor for modifying route planner results.
*
* Provides methods to assign, remove, and add jobs/shipments to agent routes.
* Supports two strategies: reoptimize (default) and preserveOrder.
*
* @example
* ```typescript
* import { RoutePlannerResultEditor, PRESERVE_ORDER, REOPTIMIZE } from '@geoapify/route-planner-sdk';
*
* const editor = new RoutePlannerResultEditor(plannerResult);
*
* // Assign job to agent (with full reoptimization)
* await editor.assignJobs('agent-A', ['job-1']);
*
* // Find optimal insertion point without reordering (Route Matrix API)
* await editor.assignJobs('agent-A', ['job-2'], { strategy: PRESERVE_ORDER });
*
* // Append job to end of route (no API call)
* await editor.assignJobs('agent-A', ['job-2'], { strategy: PRESERVE_ORDER, append: true });
*
* // Remove job while keeping route order
* await editor.removeJobs(['job-3'], { strategy: PRESERVE_ORDER });
*
* // Get the modified result
* const modifiedResult = editor.getModifiedResult();
* ```
*/
export class RoutePlannerResultEditor {
/**
* Creates a new RoutePlannerResultEditor.
* Note: The editor works on a cloned copy of the result, not the original.
*
* @param result - The route planner result to edit
*/
constructor(result) {
this.rawData = Utils.cloneObject(result.getRaw());
this.callOptions = result.getCallOptions();
this.routingOptions = result.getRoutingOptions();
}
/**
* Assigns jobs to an agent. Removes the jobs if they're currently assigned to another agent.
*
* @param agentIdOrIndex - The ID or index of the agent
* @param jobIndexesOrIds - Array of job IDs or indexes to assign
* @param options - Assignment options
* @returns Promise resolving to true if successful
*
* @example
* ```typescript
* // Default: full reoptimization (Route Planner API)
* await editor.assignJobs('agent-A', ['job-1', 'job-2']);
*
* // Find optimal insertion point (Route Matrix API)
* await editor.assignJobs('agent-A', ['job-1'], { strategy: 'preserveOrder' });
*
* // Insert at specific position (no API call)
* await editor.assignJobs('agent-A', ['job-2'], {
* strategy: 'preserveOrder',
* afterId: 'job-1'
* });
*
* // Append to end of route (no API call)
* await editor.assignJobs('agent-A', ['job-1'], {
* strategy: 'preserveOrder',
* append: true
* });
* ```
*/
assignJobs(agentIdOrIndex, jobIndexesOrIds, options) {
return __awaiter(this, void 0, void 0, function* () {
this.assertArray(jobIndexesOrIds, "jobIndexesOrIds");
let agentIndex = IndexConverter.convertAgentToIndex(this.rawData, agentIdOrIndex, true);
const normalizedOptions = this.normalizeAddAssignOptions(agentIndex, options);
let jobIndexes = IndexConverter.convertJobsToIndexes(this.rawData, jobIndexesOrIds);
return this.getJobEditor().assignJobs(agentIndex, jobIndexes, normalizedOptions);
});
}
/**
* Assigns shipments to an agent. Removes the shipments if they're currently assigned to another agent.
*
* @param agentIdOrIndex - The ID or index of the agent
* @param shipmentIndexesOrIds - Array of shipment IDs or indexes to assign
* @param options - Assignment options
* @returns Promise resolving to true if successful
*
* @example
* ```typescript
* // Default: full reoptimization (Route Planner API)
* await editor.assignShipments('agent-A', ['shipment-1']);
*
* // Find optimal insertion point (Route Matrix API)
* await editor.assignShipments('agent-A', ['shipment-1'], { strategy: 'preserveOrder' });
*
* // Append pickup and delivery to end of route (no API call)
* await editor.assignShipments('agent-A', ['shipment-1'], {
* strategy: 'preserveOrder',
* append: true
* });
* ```
*/
assignShipments(agentIdOrIndex, shipmentIndexesOrIds, options) {
return __awaiter(this, void 0, void 0, function* () {
this.assertArray(shipmentIndexesOrIds, "shipmentIndexesOrIds");
let agentIndex = IndexConverter.convertAgentToIndex(this.rawData, agentIdOrIndex, true);
const normalizedOptions = this.normalizeAddAssignOptions(agentIndex, options);
let shipmentIndexes = IndexConverter.convertShipmentsToIndexes(this.rawData, shipmentIndexesOrIds);
return this.getShipmentEditor().assignShipments(agentIndex, shipmentIndexes, normalizedOptions);
});
}
/**
* Removes jobs from the plan, marking them as unassigned.
*
* @param jobIndexesOrIds - Array of job IDs or indexes to remove
* @param options - Removal options
* @returns Promise resolving to true if successful
*
* @example
* ```typescript
* // Default: reoptimize remaining route
* await editor.removeJobs(['job-1', 'job-2']);
*
* // Remove without reordering remaining jobs
* await editor.removeJobs(['job-1'], { strategy: 'preserveOrder' });
* ```
*/
removeJobs(jobIndexesOrIds, options) {
return __awaiter(this, void 0, void 0, function* () {
this.assertArray(jobIndexesOrIds, "jobIndexesOrIds");
let jobIndexes = IndexConverter.convertJobsToIndexes(this.rawData, jobIndexesOrIds);
return this.getJobEditor().removeJobs(jobIndexes, this.normalizeRemoveOptions(options));
});
}
/**
* Removes shipments from the plan, marking them as unassigned.
*
* @param shipmentIndexesOrIds - Array of shipment IDs or indexes to remove
* @param options - Removal options
* @returns Promise resolving to true if successful
*
* @example
* ```typescript
* // Default: reoptimize remaining route
* await editor.removeShipments(['shipment-1']);
*
* // Remove without reordering remaining shipments
* await editor.removeShipments(['shipment-1'], { strategy: 'preserveOrder' });
* ```
*/
removeShipments(shipmentIndexesOrIds, options) {
return __awaiter(this, void 0, void 0, function* () {
this.assertArray(shipmentIndexesOrIds, "shipmentIndexes");
let shipmentIndexes = IndexConverter.convertShipmentsToIndexes(this.rawData, shipmentIndexesOrIds);
return this.getShipmentEditor().removeShipments(shipmentIndexes, this.normalizeRemoveOptions(options));
});
}
/**
* Adds new jobs to an agent's schedule.
*
* @param agentIdOrIndex - The ID or index of the agent
* @param jobs - Array of Job objects to add
* @param options - Assignment options
* @returns Promise resolving to true if successful
*
* @example
* ```typescript
* const newJob = new Job()
* .setId('new-job')
* .setLocation(44.5, 40.2)
* .setDuration(300);
*
* // Default: reoptimize with new job (Route Planner API)
* await editor.addNewJobs('agent-A', [newJob]);
*
* // Find optimal insertion point (Route Matrix API)
* await editor.addNewJobs('agent-A', [newJob], { strategy: 'preserveOrder' });
*
* // Append to end of route (no API call)
* await editor.addNewJobs('agent-A', [newJob], { strategy: 'preserveOrder', append: true });
* ```
*/
addNewJobs(agentIdOrIndex, jobs, options) {
this.assertArray(jobs, "jobs");
let agentIndex = IndexConverter.convertAgentToIndex(this.rawData, agentIdOrIndex, true);
const normalizedOptions = this.normalizeAddAssignOptions(agentIndex, options);
return this.getJobEditor().addNewJobs(agentIndex, jobs, normalizedOptions);
}
/**
* Adds new shipments to an agent's schedule.
*
* @param agentIdOrIndex - The ID or index of the agent
* @param shipments - Array of Shipment objects to add
* @param options - Assignment options
* @returns Promise resolving to true if successful
*
* @example
* ```typescript
* const newShipment = new Shipment()
* .setId('new-shipment')
* .setPickup(new ShipmentStep().setLocation(44.5, 40.2).setDuration(120))
* .setDelivery(new ShipmentStep().setLocation(44.6, 40.3).setDuration(120));
*
* // Default: reoptimize with new shipment (Route Planner API)
* await editor.addNewShipments('agent-A', [newShipment]);
*
* // Find optimal insertion point (Route Matrix API)
* await editor.addNewShipments('agent-A', [newShipment], { strategy: 'preserveOrder' });
*
* // Append to end of route (no API call)
* await editor.addNewShipments('agent-A', [newShipment], {
* strategy: 'preserveOrder',
* append: true
* });
* ```
*/
addNewShipments(agentIdOrIndex, shipments, options) {
this.assertArray(shipments, "shipments");
let agentIndex = IndexConverter.convertAgentToIndex(this.rawData, agentIdOrIndex, true);
const normalizedOptions = this.normalizeAddAssignOptions(agentIndex, options);
/* ToDo No need to create an object here, use functions */
return this.getShipmentEditor().addNewShipments(agentIndex, shipments, normalizedOptions);
}
getModifiedResult() {
return new RoutePlannerResult(this.callOptions, Utils.cloneObject(this.rawData));
}
reoptimizeAgentPlan(agentIdOrIndex, options = {}) {
return __awaiter(this, void 0, void 0, function* () {
return AgentReoptimizeHelper.execute(this.getJobEditor(), agentIdOrIndex, options);
});
}
addDelayAfterWaypoint(agentIdOrIndex, waypointIndex, delaySeconds) {
return AgentTimeOffsetHelper.execute(this.getJobEditor(), agentIdOrIndex, waypointIndex, delaySeconds);
}
/**
* @deprecated use addDelayAfterWaypoint()
*/
addTimeOffsetAfterWaypoint(agentIdOrIndex, waypointIndex, offsetSeconds) {
return this.addDelayAfterWaypoint(agentIdOrIndex, waypointIndex, offsetSeconds);
}
moveWaypoint(agentIdOrIndex, fromWaypointIndex, toWaypointIndex) {
return __awaiter(this, void 0, void 0, function* () {
return WaypointMoveHelper.execute(this.getJobEditor(), agentIdOrIndex, fromWaypointIndex, toWaypointIndex);
});
}
assertArray(array, name) {
if (!Array.isArray(array)) {
throw new InvalidParameter(name + " must be an array", name);
}
}
normalizeAddAssignOptions(agentIndex, options) {
const normalizedOptions = Object.assign({}, (options !== null && options !== void 0 ? options : {}));
if (normalizedOptions.afterId && normalizedOptions.afterId !== "") {
const insertPosition = this.resolveInsertPosition(agentIndex, normalizedOptions.afterId);
normalizedOptions.afterWaypointIndex = insertPosition;
delete normalizedOptions.afterId;
}
this.validateAfterWaypointIndex(agentIndex, normalizedOptions);
return normalizedOptions;
}
resolveInsertPosition(agentIndex, afterId) {
const waypoints = this.getJobEditor().getAgentWaypoints(agentIndex);
let lastMatchingIndex = -1;
for (let i = 0; i < waypoints.length; i++) {
const hasMatchedAction = waypoints[i].actions.some((action) => action.job_id === afterId || action.shipment_id === afterId);
if (hasMatchedAction) {
lastMatchingIndex = i;
}
}
if (lastMatchingIndex === -1) {
throw new InvalidInsertionPosition(`Shipment or Job '${afterId}' not found in agent ${agentIndex} route`, agentIndex, undefined, afterId);
}
return lastMatchingIndex;
}
validateAfterWaypointIndex(agentIndex, options) {
if (options.afterWaypointIndex === undefined) {
return;
}
InsertPositionResolver.validateAfterWaypointIndex(this.getJobEditor(), agentIndex, options.afterWaypointIndex);
}
normalizeRemoveOptions(options) {
return options !== null && options !== void 0 ? options : {};
}
getJobEditor() {
if (!this.jobEditor) {
this.jobEditor = new RouteResultJobEditor(this.rawData, this.callOptions, this.routingOptions);
}
return this.jobEditor;
}
getShipmentEditor() {
if (!this.shipmentEditor) {
this.shipmentEditor = new RouteResultShipmentEditor(this.rawData, this.callOptions, this.routingOptions);
}
return this.shipmentEditor;
}
}