Vehicle routing and delivery planning with real road-network costs.
LinkedIT takes a depot, a fleet, and a set of deliveries, then decides which vehicle should handle each stop and in what order. We use jsprit for the optimization and OSRM for road travel times, distances, and map-ready route geometry.
Planning a few deliveries by hand is manageable. Add more stops, several vehicles, capacity limits, service times, priorities, and delivery windows, and the number of possible plans grows quickly.
LinkedIT turns those inputs into a vehicle-routing problem. The current dashboard lets our team choose a depot, define vehicles by ID and capacity, and add deliveries manually or import them from CSV. A delivery contains coordinates, demand, unload time, and priority; CSV and direct API requests can also include a time window. The API additionally accepts vehicle-specific start and end locations—when they are omitted, vehicles start and finish at the depot.
For road-aware planning, the backend asks OSRM for one distance-and-duration matrix before the solver starts. jsprit then works entirely against that in-memory matrix while assigning deliveries and testing stop sequences. Once it has selected the final routes, OSRM is called again for the road geometry needed by the map.
The result includes each used vehicle, its ordered stops, arrival and departure times, remaining load, route distance and duration, delivered load, and a GeoJSON LineString. Jobs that cannot be placed in a feasible route are returned separately instead of disappearing from the plan.
Depot + fleet + deliveries
↓
OSRM Table
↓
distance / duration matrix
↓
jsprit
↓
vehicle assignment + stop order
↓
OSRM Route
↓
route metrics + GeoJSON geometry
Manual planning gets unreliable when deliveries compete for limited vehicle capacity and time. A route that looks short on a map may be slow on the road, while a locally convenient assignment can make the rest of the fleet inefficient or infeasible.
We built LinkedIT to make those trade-offs explicit: model the real constraints, solve the fleet plan as one problem, and return an answer that dispatchers can inspect rather than a list of abstract solver values.
- Set the depot through location search, presets, GPS, the map picker, or exact coordinates.
- Add and edit vehicles with application-defined capacity units.
- Add deliveries with demand, service duration, and priority (
1is highest). - Import delivery jobs from CSV with a validation preview, duplicate checks, and optional time-window columns.
- Load the included nine-stop Bhubaneswar dataset for a quick walkthrough.
- Assign delivery jobs across a finite fleet and optimize the stop order for each used vehicle.
- Enforce vehicle capacity and delivery time windows supplied through CSV or the API.
- Account for service time at every delivery and report jobs the solver could not assign.
- Preserve optional per-vehicle start and end locations in API requests.
- Build an asymmetric OSRM distance/time matrix in a single request before optimization.
- Keep solver cost lookups in memory instead of making network calls during the search.
- Request full GeoJSON road geometry only for the final routes.
- Switch to offline great-circle costs and straight-line geometry with the
crowflyprovider.
- Render the depot, deliveries, numbered stops, and route lines on a Leaflet map with OpenStreetMap tiles.
- Give each vehicle a consistent route color; selecting a vehicle focuses its route across the map and result views.
- Review orders by pending, assigned, or unassigned status and filter by vehicle or priority.
- Compare route distance, duration, capacity use, and complete
DEPOT → stops → DEPOTsequences. - Follow estimated arrivals in the per-vehicle timeline. The API also returns aggregate distance, cumulative duration, assignment counts, vehicles used, and objective cost.
flowchart LR
A[React planning dashboard] -->|Depot, vehicles, deliveries| B[Spring Boot API]
B -->|Unique coordinates| C[OSRM Table]
C -->|Road distance and time matrix| D[jsprit optimizer]
D -->|Vehicle assignment and stop order| E[OSRM Route]
E -->|GeoJSON LineStrings| B
B -->|Routes, stops, loads, metrics| A
- The dashboard sends an optimization request to
POST /api/optimize. - The backend validates IDs, coordinates, capacities, priorities, service times, and time windows.
- OSRM returns road distances in metres and travel times in seconds for every unique location pair.
- The request is mapped into a finite-fleet jsprit delivery problem and solved over 200 iterations.
- The final jsprit solution is mapped into routes, stops, loads, totals, and unassigned job IDs.
- Geometry requests for the used vehicles run through a bounded executor and return GeoJSON road lines.
- React draws the routes and exposes the plan through Orders, Routes, and Timeline views.
Repeated optimization results, OSRM matrices, and route geometries are held in bounded in-memory caches. The checked-in configuration keeps entries for four hours, up to 500 entries per cache.
| Layer | Technology |
|---|---|
| Frontend | React 19, TypeScript 6, Vite 8, Tailwind CSS 4 |
| Map | Leaflet 1.9, OpenStreetMap tiles |
| Backend | Java 21, Spring Boot 3.3.5 |
| Optimization | jsprit 2.1.0-SNAPSHOT (jsprit-core) |
| Road routing | OSRM Table and Route services |
| Build | Maven 3.6+, npm |
| Test and lint | JUnit 5, Spring Boot Test, Mockito, Oxlint |
LinkedIT/
├── frontend/ # React planning dashboard and API client
├── routing-backend/ # Spring Boot API, validation, routing, and jsprit adapters
├── jsprit-core/ # Upstream optimization engine we build on
├── docs/ # README media
├── PROJECT_GUIDE/ # Extended project notes
├── pom.xml # Maven reactor for jsprit-core and routing-backend
└── Dockerfile # Production backend image
Application code stays in routing-backend; we depend on jsprit-core rather than mixing LinkedIT-specific behavior into the engine.
- Java 21
- Maven 3.6 or newer
- Node.js
^20.19.0or>=22.12.0 - npm
- Internet access for the checked-in public OSRM configuration, or no routing service if using
crowfly
From the repository root, build the reactor once so the backend can resolve the local jsprit module, then start Spring Boot:
mvn -pl routing-backend -am install
mvn -pl routing-backend spring-boot:runThe API starts on http://localhost:8080. Check it with:
curl http://localhost:8080/api/healthThe repository currently runs in OSRM mode against https://router.project-osrm.org. For a local OSRM instance or offline planning, set Spring Boot environment variables before starting the backend:
| Environment variable | Purpose | Checked-in value |
|---|---|---|
ROUTING_PROVIDER |
osrm for road routing, crowfly for offline great-circle routing |
osrm |
ROUTING_OSRM_BASE_URL |
Base URL of the OSRM server | https://router.project-osrm.org |
CORS_ALLOWED_ORIGINS |
Comma-separated frontend origins | http://localhost:5173,http://localhost:4173 |
The public OSRM server is convenient for development and the demo, but a self-hosted regional instance is the better fit for controlled or heavier workloads. See DEPLOYMENT_AND_OSRM_GUIDE.md for local OSRM preparation and deployment notes.
In a second terminal:
cd frontend
npm install
npm run devOpen http://localhost:5173. The API client reads VITE_API_BASE_URL and falls back to http://localhost:8080. To override it, copy the example environment file:
cp .env.example .env.localThen edit VITE_API_BASE_URL in .env.local. On PowerShell, use Copy-Item .env.example .env.local instead.
POST /api/optimize is the main application endpoint. Coordinates are WGS84 latitude/longitude, service and time-window values are seconds, and capacity/demand use the same application-defined integer unit.
{
"depot": { "id": "DEPOT-1", "latitude": 20.2961, "longitude": 85.8245 },
"vehicles": [
{ "id": "V1", "capacity": 50 }
],
"jobs": [
{
"id": "D1",
"latitude": 20.305,
"longitude": 85.817,
"demand": 18,
"serviceDuration": 240,
"priority": 2,
"timeWindow": { "start": 28800, "end": 43200 }
}
]
}A shortened response has this shape:
{
"routes": [
{
"vehicleId": "V1",
"stops": [
{
"jobId": "D1",
"sequence": 1,
"latitude": 20.305,
"longitude": 85.817,
"arrivalTime": 28800,
"departureTime": 29040,
"remainingLoad": 0
}
],
"distance": 14320.5,
"duration": 2340.2,
"initialLoad": 18,
"deliveredLoad": 18,
"geometry": {
"type": "LineString",
"coordinates": [[85.8245, 20.2961], [85.817, 20.305]]
}
}
],
"unassignedJobs": [],
"summary": {
"totalDistance": 14320.5,
"totalDuration": 2340.2,
"vehiclesUsed": 1,
"totalJobs": 1,
"assignedJobs": 1,
"unassignedJobs": 0,
"objectiveCost": 14.3205
}
}GET /api/health returns the backend status for local checks and deployment probes.
jsprit. We map vehicles to a finite fleet and deliveries to capacity-bearing jobs with service times, priorities, and optional time windows. jsprit searches for a low-cost feasible assignment and ordering; our application layer then converts its solution into the stable API model used by the dashboard.
OSRM Table. One request loads the full asymmetric road matrix before optimization. Distance values are metres and durations are seconds. During solver search, a cost lookup is an in-memory matrix access—there is no HTTP request inside the optimization loop.
OSRM Route. Geometry is deliberately postponed until the stop order is final. The backend makes one Route-service request per used vehicle and asks for overview=full&geometries=geojson; these calls can run concurrently. Route geometry is for display, while the Table matrix remains the source of route distance and time.
Each LinkedIT optimization is a static snapshot: a depot, delivery jobs, and a finite fleet of capacity-limited vehicles. It plans delivery-only routes, supports static time windows and priorities, and returns routes that end at their configured vehicle end location (the depot in the dashboard workflow).
It does not include authentication, persistent storage, live vehicle tracking, dynamic dispatch, or traffic prediction. Routing uses the costs supplied by the configured provider and may reuse them from the in-memory cache.
LinkedIT’s application layer is built around jsprit for vehicle-routing optimization and OSRM for road-network costs and geometry. The dashboard uses OpenStreetMap data and tiles through Leaflet. We do not claim authorship of these upstream projects.
This repository is distributed under the Apache License 2.0. The vendored jsprit-core module retains its upstream GraphHopper copyright, license, and notices; see NOTICE.md and jsprit-core/LICENSE.md.
