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

382 lines (381 loc) 17.2 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 { AgentHasNoPlan, AgentNotFound, NoItemsProvided, ItemsNotUnique } from "../../models"; import { RouteMatrixHelper } from "./strategies/preserve-order/utils/route-matrix-helper"; import { RoutingHelper } from "./strategies/preserve-order/utils/routing-helper"; import { RoutePlanner } from "../../route-planner"; import { Utils } from "../utils"; import { LegRecalculator } from "./strategies"; const MISSING_ROUTE_METRIC = -1; /** * Base class for route result editors with shared functionality */ export class RouteResultEditorBase { constructor(rawData, callOptions, routingOptions) { this.rawData = rawData; this.callOptions = callOptions; this.routingOptions = routingOptions; this.rawData = rawData; } validateAgent(agentIndex) { const agentFound = this.rawData.properties.params.agents[agentIndex]; if (!agentFound) { throw new AgentNotFound(`Agent with index ${agentIndex} not found`, agentIndex); } } ensureItemsProvided(indexes, itemType) { if (indexes.length === 0) { throw new NoItemsProvided(`No ${itemType} provided`, itemType); } } ensureItemsUnique(indexes, itemType) { if (indexes.length !== new Set(indexes).size) { const capitalized = itemType.charAt(0).toUpperCase() + itemType.slice(1); throw new ItemsNotUnique(`${capitalized} are not unique`, itemType); } } ensureNewItemsValid(items, itemType) { if (items.length === 0) { throw new NoItemsProvided(`No ${itemType} provided`, itemType); } if (items.length !== new Set(items).size) { const capitalized = itemType.charAt(0).toUpperCase() + itemType.slice(1); throw new ItemsNotUnique(`${capitalized} are not unique`, itemType); } } getRawData() { return this.rawData; } cloneInputData() { return Utils.cloneObject(this.rawData.properties.params); } executePlan(inputData) { return __awaiter(this, void 0, void 0, function* () { const planner = new RoutePlanner(this.callOptions, inputData); const newResult = yield planner.plan(); this.updateResult(newResult); return true; }); } executeAgentPlan(agentIndex, inputData) { var _a, _b, _c, _d; return __awaiter(this, void 0, void 0, function* () { if (!((_a = inputData.shipments) === null || _a === void 0 ? void 0 : _a.length) && !((_b = inputData.jobs) === null || _b === void 0 ? void 0 : _b.length)) { this.updateAgentAsUnassigned(agentIndex); } else { const planner = new RoutePlanner(this.callOptions, inputData); const newResult = yield planner.plan(); if (((_c = newResult.getRaw().features) === null || _c === void 0 ? void 0 : _c.length) === 0) { this.updateAgentAsUnassigned(agentIndex); return true; } const newAgentData = (_d = newResult.getRaw().features) === null || _d === void 0 ? void 0 : _d[0]; newAgentData.properties.agent_index = agentIndex; const featureIndex = this.rawData.features.findIndex((feature) => feature.properties.agent_index === agentIndex); if (featureIndex === -1) { this.rawData.features.push(newAgentData); } else { this.rawData.features[featureIndex] = newAgentData; } } return true; }); } updateIssues() { var _a, _b; const issues = this.rawData.properties.issues || (this.rawData.properties.issues = {}); const params = this.rawData.properties.params; const assignedAgentIndexes = new Set(); const assignedJobIndexes = new Set(); const assignedShipmentIndexes = new Set(); for (const feature of this.rawData.features || []) { const agentIndex = (_a = feature === null || feature === void 0 ? void 0 : feature.properties) === null || _a === void 0 ? void 0 : _a.agent_index; if (typeof agentIndex === "number") { assignedAgentIndexes.add(agentIndex); } for (const action of ((_b = feature === null || feature === void 0 ? void 0 : feature.properties) === null || _b === void 0 ? void 0 : _b.actions) || []) { if (typeof action.job_index === "number") { assignedJobIndexes.add(action.job_index); } if (typeof action.shipment_index === "number") { assignedShipmentIndexes.add(action.shipment_index); } } } issues.unassigned_agents = this.getUnassignedIndexes(params.agents.length, assignedAgentIndexes); issues.unassigned_jobs = this.getUnassignedIndexes((params.jobs || []).length, assignedJobIndexes); issues.unassigned_shipments = this.getUnassignedIndexes((params.shipments || []).length, assignedShipmentIndexes); } updateAgentAsUnassigned(agentIndex) { const featureIndex = this.rawData.features.findIndex((feature) => feature.properties.agent_index === agentIndex); if (featureIndex !== -1) { this.rawData.features.splice(featureIndex, 1); } } getUnassignedIndexes(totalCount, assignedIndexes) { const unassigned = []; for (let index = 0; index < totalCount; index++) { if (!assignedIndexes.has(index)) { unassigned.push(index); } } return unassigned; } updateResult(newResult) { this.rawData.features = newResult.getRaw().features; this.rawData.properties.issues = newResult.getRaw().properties.issues; } getMatrixHelper() { return new RouteMatrixHelper(this.callOptions, this.routingOptions); } getRoutingHelper() { return new RoutingHelper(this.callOptions, this.routingOptions); } getAgentFeature(agentIndex) { const rawData = this.rawData; const agentFeature = rawData.features.find((f) => f.properties.agent_index === agentIndex); if (!agentFeature) { throw new AgentHasNoPlan(`Agent with index ${agentIndex} has no Plan`, agentIndex); } return agentFeature; } getOrCreateAgentFeature(agentIndex) { return __awaiter(this, void 0, void 0, function* () { const rawData = this.rawData; let agentFeature = rawData.features.find((f) => f.properties.agent_index === agentIndex); let isNewAgentFeature = false; if (!agentFeature) { // Create a minimal feature structure for unassigned agents const agent = rawData.properties.params.agents[agentIndex]; agentFeature = this.createEmptyAgentFeature(agentIndex, agent); isNewAgentFeature = true; rawData.features.push(agentFeature); // Remove from unassigned list if present if (rawData.properties.issues && rawData.properties.issues.unassigned_agents) { const unassignedIndex = rawData.properties.issues.unassigned_agents.indexOf(agentIndex); if (unassignedIndex !== -1) { rawData.properties.issues.unassigned_agents.splice(unassignedIndex, 1); } } } if (isNewAgentFeature) { yield LegRecalculator.fillMissingLegData(this, agentFeature); } return agentFeature; }); } createEmptyAgentFeature(agentIndex, agent) { const startTime = (agent.time_windows && agent.time_windows.length > 0 && agent.time_windows[0].length > 0) ? agent.time_windows[0][0] : 0; const startLocation = this.resolveAgentLocation(agentIndex, agent, "start"); const endLocation = this.resolveAgentLocation(agentIndex, agent, "end"); const hasStartLocation = !!startLocation; const hasEndLocation = !!endLocation; const hasSeparateStartEnd = hasStartLocation && hasEndLocation; const effectiveStart = startLocation !== null && startLocation !== void 0 ? startLocation : endLocation; const effectiveEnd = endLocation !== null && endLocation !== void 0 ? endLocation : startLocation; if (!effectiveStart || !effectiveEnd) { throw new Error(`Agent ${agentIndex} must have start_location(_index) or end_location(_index)`); } const startAction = { type: "start", index: 0, start_time: startTime, duration: 0, location_index: effectiveStart.locationIndex, location_id: effectiveStart.locationId, waypoint_index: 0 }; const endAction = hasEndLocation ? { type: "end", index: 1, start_time: startTime, duration: 0, location_index: effectiveEnd.locationIndex, location_id: effectiveEnd.locationId, waypoint_index: hasSeparateStartEnd ? 1 : 0 } : undefined; const waypoints = hasSeparateStartEnd ? [ { original_location: effectiveStart.location, original_location_index: effectiveStart.locationIndex, original_location_id: effectiveStart.locationId, start_time: startTime, duration: 0, actions: [Object.assign({}, startAction)], prev_leg_index: undefined, next_leg_index: 0 }, { original_location: effectiveEnd.location, original_location_index: effectiveEnd.locationIndex, original_location_id: effectiveEnd.locationId, start_time: startTime, duration: 0, actions: endAction ? [Object.assign({}, endAction)] : [], prev_leg_index: 0, next_leg_index: undefined } ] : [ { original_location: effectiveStart.location, original_location_index: effectiveStart.locationIndex, original_location_id: effectiveStart.locationId, start_time: startTime, duration: 0, actions: endAction ? [Object.assign({}, startAction), Object.assign({}, endAction)] : [Object.assign({}, startAction)], prev_leg_index: undefined, next_leg_index: undefined } ]; const legs = hasSeparateStartEnd ? [ { from_waypoint_index: 0, to_waypoint_index: 1, time: MISSING_ROUTE_METRIC, distance: MISSING_ROUTE_METRIC, steps: [] } ] : []; const geometryCoordinates = hasSeparateStartEnd ? [[effectiveStart.location, effectiveEnd.location]] // this should be overwritten later by locations from Routing API : []; return { type: 'Feature', geometry: { type: 'MultiLineString', coordinates: geometryCoordinates }, properties: { agent_index: agentIndex, agent_id: agent.id || `agent-${agentIndex}`, mode: agent.mode || this.rawData.properties.params.mode || 'drive', waypoints, legs, time: 0, start_time: startTime, end_time: startTime, distance: 0, actions: endAction ? [startAction, endAction] : [startAction] } }; } resolveAgentLocation(agentIndex, agent, boundary) { const locationIndex = boundary === "start" ? agent.start_location_index : agent.end_location_index; if (locationIndex !== undefined) { const indexedLocation = this.rawData.properties.params.locations[locationIndex]; if (!indexedLocation || !indexedLocation.location) { throw new Error(`Agent ${agentIndex} has invalid ${boundary}_location_index ${locationIndex}`); } return { location: indexedLocation.location, locationIndex, locationId: indexedLocation.id }; } const location = boundary === "start" ? agent.start_location : agent.end_location; if (location) { return { location }; } return undefined; } findEndActionIndex(actions) { return actions.findIndex((a) => a.type === 'end'); } reindexActions(actions) { actions.forEach((action, idx) => { action.index = idx; }); } addAgentCapabilities(agents) { for (let agentIndex = 0; agentIndex < agents.length; agentIndex++) { const agent = agents[agentIndex]; const capabilityName = `assign-agent-${agentIndex}`; if (!agent.capabilities) { agent.capabilities = []; } if (!agent.capabilities.includes(capabilityName)) { agent.capabilities.push(capabilityName); } } } getAgentIndexForShipment(shipmentIndex) { const features = this.getRawData().features; for (const feature of features) { for (const action of feature.properties.actions) { if (action.shipment_index === shipmentIndex) { return feature.properties.agent_index; } } } return undefined; } getAgentShipments(agentIndex) { const agentFeature = this.getRawData().features.find(feature => feature.properties.agent_index === agentIndex); if (!agentFeature) { return []; } const shipmentIndexes = []; for (const action of agentFeature.properties.actions) { if (action.shipment_index !== undefined && !shipmentIndexes.includes(action.shipment_index)) { shipmentIndexes.push(action.shipment_index); } } return shipmentIndexes; } getAgentIndexForJob(jobIndex) { for (const agentFeature of this.getRawData().features) { for (const action of agentFeature.properties.actions) { if (action.job_index === jobIndex) { return agentFeature.properties.agent_index; } } } return undefined; } getAgentJobs(agentIndex) { const agentFeature = this.getRawData().features.find(feature => feature.properties.agent_index === agentIndex); if (!agentFeature) { return []; } const jobIndexes = []; for (const action of agentFeature.properties.actions) { if (action.job_index !== undefined && !jobIndexes.includes(action.job_index)) { jobIndexes.push(action.job_index); } } return jobIndexes; } getAgentActions(agentIndex) { const agentFeature = this.getRawData().features.find(feature => feature.properties.agent_index === agentIndex); return agentFeature ? agentFeature.properties.actions : []; } getAgentWaypoints(agentIndex) { const agentFeature = this.getRawData().features.find(feature => feature.properties.agent_index === agentIndex); return agentFeature ? agentFeature.properties.waypoints : []; } getExistingConsecutiveTravelTimes(agentIndex) { var _a; const agentFeature = this.getRawData().features.find(feature => feature.properties.agent_index === agentIndex); if (!((_a = agentFeature === null || agentFeature === void 0 ? void 0 : agentFeature.properties) === null || _a === void 0 ? void 0 : _a.legs)) { return []; } return agentFeature.properties.legs.map(leg => leg.time); } }