Fuel-aware route optimization for long-haul US trucking.
RouteOpt is a full-stack route planning application that helps drivers and fleet operators reduce fuel spend on cross-country trips. Given a start and destination, the system computes a driving route, identifies reachable stations along the path, and selects a cost-efficient fueling strategy based on route distance, station pricing, and vehicle range.
The repository includes:
- a production-quality React + TypeScript frontend with a dark-map command-center interface
- a Django REST API that computes optimized fuel stop plans
- Docker Compose setup for one-command local deployment
- deployment configuration for Render (backend) and Vercel (frontend)
Home — Ready to Optimize
Route Result — New York → Dallas
Fuel Stop Detail Popup
- Fuel-optimized trip planning for long-haul US routes
- Interactive full-screen Mapbox experience with route line, custom markers, and stop popups
- Responsive sidebar-to-bottom-sheet layout for desktop and mobile
- Typed frontend and backend contracts
- Reducer-driven request lifecycle and resilient error handling
- Docker Compose setup — spin up both services with a single command
- Render + Vercel deployment setup included in the repo
The frontend is designed as a logistics control surface rather than a demo dashboard.
- Drivers enter a start and destination
- The frontend requests an optimized route from the API
- The backend geocodes the locations, fetches a route from Mapbox, finds nearby candidate stations, and computes the best stop sequence
- The UI renders:
- total trip fuel cost
- route mileage and estimated duration
- total gallons added
- every planned fuel stop with price, gallons, and stop cost
- Selecting a stop in the list focuses the map and opens its popup
- Selecting a marker on the map scrolls the corresponding stop card into view
The frontend is built with:
- React 18
- TypeScript in strict mode
- Vite
- Tailwind CSS v3
- Framer Motion
- Mapbox GL JS via
react-map-gl - Lucide React
Key frontend responsibilities:
- collect trip input
- manage request state transitions
- render optimized route summaries
- visualize routes and fuel stops on an interactive map
- keep map movement and UI interactions in sync
The backend is a Django + Django REST Framework service that exposes a single core optimization endpoint:
POST /route/
It is responsible for:
- validating incoming route requests
- geocoding start and destination text
- fetching route geometry from Mapbox Directions
- locating nearby stations from the fuel price dataset
- running the fuel optimization algorithm
- returning a structured response to the frontend
Frontend form submit
-> POST /route/
-> Validate request
-> Geocode origin and destination
-> Fetch route geometry from Mapbox
-> Find nearby candidate stations
-> Optimize fuel stops
-> Return route summary + stop list + polyline coordinates
-> Render map and sidebar UI
| Layer | Technology |
|---|---|
| Frontend framework | React 18 + Vite |
| Frontend language | TypeScript |
| Styling | Tailwind CSS v3 |
| Animation | Framer Motion |
| Mapping | Mapbox GL JS + react-map-gl |
| Icons | Lucide React |
| Backend | Django 5 + DRF |
| Routing & geocoding | Mapbox APIs |
| Optimization data structures | NumPy + SciPy KDTree |
| Containerization | Docker + Docker Compose |
| Frontend server (Docker) | Nginx (Alpine) |
| Deployment | Vercel + Render |
.
├── src/ # React frontend source
├── route_optimizer/ # Django backend
│ ├── config/
│ ├── data/
│ ├── route/
│ │ ├── management/
│ │ └── services/
│ ├── manage.py
│ ├── requirements.txt
│ └── Dockerfile # Backend container
├── Dockerfile # Frontend container (multi-stage Nginx build)
├── docker-compose.yml # Compose file for local full-stack run
├── nginx.conf # Nginx config for the frontend container
├── render.yaml # Render Blueprint for the backend
├── vercel.json # Vercel project configuration
├── DEPLOYMENT.md # Platform-specific deployment notes
├── package.json # Frontend dependencies and scripts
└── README.md
- Node.js 20+
- npm 10+
- Python 3.11+
- a Mapbox token
git clone https://github.com/NDDimension/fuel-route-optimizer.git
cd fuel-route-optimizerCreate a root .env file:
VITE_API_BASE_URL=http://localhost:8000
VITE_MAPBOX_TOKEN=pk.your_mapbox_public_token_hereInstall frontend dependencies:
npm installMove into the Django app:
cd route_optimizerCreate and activate a virtual environment:
python -m venv .venv
source .venv/bin/activateInstall backend dependencies:
pip install -r requirements.txtCreate route_optimizer/.env:
MAPBOX_TOKEN=pk.your_mapbox_public_token_here
DJANGO_SECRET_KEY=change-me
DJANGO_DEBUG=True
FUEL_CSV_PATH=data/fuel_prices.csv
GEOCODE_CACHE_PATH=geocode_cache.db
MAX_OFF_ROUTE_MILES=5.0Recommended before the first run:
python manage.py preload_fuel_dataFrom route_optimizer/:
python manage.py runserverThe API will be available at http://localhost:8000.
From the repository root in a separate terminal:
npm run devThe app will be available at http://localhost:5173.
Docker Compose runs both the frontend and backend together with a single command. The frontend is built into a static bundle and served by Nginx; the backend runs via Gunicorn.
- Docker Desktop (or Docker Engine + Compose plugin)
- a Mapbox token
Root .env (for the frontend build arg):
VITE_MAPBOX_TOKEN=pk.your_mapbox_public_token_hereroute_optimizer/.env (for the backend):
MAPBOX_TOKEN=pk.your_mapbox_public_token_here
DJANGO_SECRET_KEY=change-me-to-a-long-random-string
DJANGO_DEBUG=False
FUEL_CSV_PATH=data/fuel_prices.csv
GEOCODE_CACHE_PATH=geocode_cache.db
MAX_OFF_ROUTE_MILES=5.0
ALLOWED_HOSTS=localhost,127.0.0.1
CORS_ALLOWED_ORIGINS=http://localhost:3000From the repository root:
docker compose up --build| Service | URL |
|---|---|
| Frontend (Nginx) | http://localhost:3000 |
| Backend (Gunicorn) | http://localhost:8000 |
docker compose downThe geocode cache is persisted in a bind mount at route_optimizer/geocode_cache.db so it survives container restarts.
Frontend (Dockerfile at repo root) — multi-stage build:
node:20-slimbuilds the Vite app withVITE_API_BASE_URLandVITE_MAPBOX_TOKENbaked in at build timenginx:alpineserves the static output on port 80 (mapped to 3000)
Backend (route_optimizer/Dockerfile):
python:3.11-slimwith Gunicorn binding on0.0.0.0:8000
Note: Because Vite bakes the API URL into the bundle at build time, changing
VITE_API_BASE_URLrequires a rebuild (docker compose up --build). For production deployments pointing at a remote backend, update the URL before building.
Run from the repository root:
npm run dev # start Vite dev server
npm run build # production build to dist/
npm run preview # locally preview the production buildRequest body:
{
"start": "Chicago, IL",
"end": "Dallas, TX"
}Successful response:
{
"start": "Chicago, IL",
"end": "Dallas, TX",
"total_miles": 917.3,
"duration_hours": 13.8,
"route": [{ "lat": 41.878, "lon": -87.629 }],
"fuel_stops": [
{
"station_id": "4821",
"name": "PILOT TRAVEL CENTER #220",
"city": "Springfield",
"state": "IL",
"lat": 39.801,
"lon": -89.643,
"route_mile": 201.4,
"off_route_miles": 0.3,
"gallons_added": 32.1,
"price_per_gallon": 3.129,
"stop_cost": 100.44
}
],
"total_fuel_cost": 284.73,
"total_gallons": 91.7
}Health endpoint:
GET /health/
For complete backend details, request/response behavior, and algorithm notes, see route_optimizer/README.md.
This repository is configured for:
- Render — Django API backend
- Vercel — React frontend
Included deployment files:
Render uses a Blueprint with Gunicorn, a /health/ health check, a persistent disk mount for cache data, and environment-driven Django host and CORS configuration.
Vercel uses standard Vite build output (npm install → npm run build → dist/ as output directory).
See the Docker Setup section above. For production, set DJANGO_DEBUG=False, use a strong DJANGO_SECRET_KEY, and configure ALLOWED_HOSTS and CORS_ALLOWED_ORIGINS to match your domain.
| Variable | Required | Purpose |
|---|---|---|
VITE_API_BASE_URL |
Yes | Base URL for the Django API |
VITE_MAPBOX_TOKEN |
Yes | Public Mapbox token for map rendering |
| Variable | Required | Purpose |
|---|---|---|
MAPBOX_TOKEN |
Yes | Mapbox token for geocoding and routing |
DJANGO_SECRET_KEY |
Yes | Django application secret |
DJANGO_DEBUG |
Yes | Debug mode toggle |
ALLOWED_HOSTS |
Production | Allowed hostnames for Django |
CORS_ALLOWED_ORIGINS |
Production | Explicit frontend origins |
CORS_ALLOWED_ORIGIN_REGEXES |
Optional | Regex support for preview deployments |
FUEL_CSV_PATH |
No | Fuel price CSV path (default: data/fuel_prices.csv) |
GEOCODE_CACHE_PATH |
No | SQLite cache path (default: geocode_cache.db) |
MAX_OFF_ROUTE_MILES |
No | Candidate station distance threshold (default: 5.0) |
- The frontend keeps imperative map operations isolated inside a dedicated hook.
- The route API lifecycle is reducer-driven to keep success, loading, idle, and error states explicit.
- Large route geometry is not stored in React state unnecessarily.
- Fuel stop cards and map markers are memoized to avoid unnecessary rerenders.
- The backend performs only one upstream route fetch per request and keeps station lookup and optimization in memory.
The repository includes a fuel price dataset used by the backend optimizer to locate and compare candidate stops along a route.
- authentication and saved trips
- fleet-level route comparison
- cost sensitivity simulations by MPG and tank size
- historical fuel trend overlays
- exportable trip summaries for dispatch teams


