@route-optimization/core
v1.0.4
Published
Core package for route optimization map visualization
Maintainers
Readme
@route-optimization/core
Core package for route optimization and map visualization. Framework-agnostic TypeScript library providing route optimization via Google Cloud, map adapters, types, and utilities.
🚛 Route Optimization • 🗺️ Map Visualization • 🎯 TypeScript • 🎭 Mock Mode
⚠️ Important: Client-Side vs Server-Side
This package has two entry points:
📦 Main Export (Client-Safe)
import { GoogleMapsAdapter, type Route } from '@route-optimization/core';✅ Safe for: React, Vue, Next.js client components, browsers
Includes: Map adapters, types, utilities
Excludes: RouteCalculator (uses Node.js gRPC)
🖥️ Server Export (Node.js Only)
import { RouteCalculator } from '@route-optimization/core/server';✅ Safe for: Node.js, API routes, Next.js server components
❌ Don't use in: React components, browser code
Causes error: Module not found: Can't resolve 'fs'
📖 Read the Client vs Server Guide
Table of Contents
- Features
- Installation
- Quick Start
- Client-Side vs Server-Side
- Setup Guide
- Usage
- API Reference
- Troubleshooting
- Development
- Related Packages
Features
- 🎯 TypeScript-first: Full type safety with comprehensive type definitions
- 🚛 Route Optimization: Integrated Google Route Optimization API with mock mode
- 🗺️ Map Adapters: Pluggable adapter pattern supporting multiple map providers
- 📦 Zero Dependencies: Pure TypeScript core with no framework dependencies
- 🧪 Fully Tested: 80%+ test coverage with unit tests
- 📐 Coordinate Utilities: Distance calculation, bounds calculation, formatting
- ✅ Validation: Built-in validation for routes, stops, and coordinates
- 🎭 Mock Mode: Test optimization without API calls or credentials
Installation
npm install @route-optimization/core
# or
pnpm add @route-optimization/core
# or
yarn add @route-optimization/coreQuick Start
Route Optimization (5 minutes setup)
⚠️ Server-Side Only: RouteCalculator uses Node.js and cannot run in browsers.
Use this in: API routes, server components, backend services.
Learn more about client vs server usage
// ✅ Server-side code only (Node.js, API routes, etc.)
import { RouteCalculator } from '@route-optimization/core/server';
// 1. Start with mock mode (no credentials needed)
const calculator = new RouteCalculator({ useMockMode: true });
await calculator.initialize();
// 2. Define your optimization problem
const response = await calculator.optimizeRoutes({
shipments: [
{
id: 'delivery-1',
deliveries: [{ location: { latitude: 13.7467, longitude: 100.5342 } }],
label: 'Customer A',
},
{
id: 'delivery-2',
deliveries: [{ location: { latitude: 13.7363, longitude: 100.4918 } }],
label: 'Customer B',
},
],
vehicles: [
{
id: 'van-1',
startLocation: { latitude: 13.7563, longitude: 100.5018 },
},
],
});
// 3. Use the optimized routes
if (response.success) {
console.log('Optimized routes:', response.routes);
console.log('Total cost:', response.metrics?.totalCost);
}Ready for production? Replace useMockMode: true with your Google Cloud credentials:
// Using API Key (simpler)
const calculator = new RouteCalculator({
projectId: 'your-project-id',
apiKey: 'YOUR_API_KEY',
});
// OR using Service Account (more secure)
const calculator = new RouteCalculator({
projectId: 'your-project-id',
credentialsPath: './service-account-key.json',
});Map Visualization (Client-Safe)
// ✅ Safe for client-side (React, Vue, browsers)
import { GoogleMapsAdapter } from '@route-optimization/core';
const adapter = new GoogleMapsAdapter();
await adapter.initialize({
apiKey: 'YOUR_GOOGLE_MAPS_API_KEY',
container: document.getElementById('map'),
});
// Render a route
adapter.renderRoute({
id: 'route-1',
vehicleId: 'vehicle-1',
stops: [
{
id: 'depot',
location: { lat: 13.7563, lng: 100.5018 },
type: 'START',
sequence: 0,
},
],
});When to Use What?
| Scenario | Recommendation | Why | | ---------------------------------- | ---------------------------- | --------------------------- | | Local development | Mock mode | Free, fast, no setup | | Unit testing | Mock mode | Deterministic, no API calls | | Integration testing | Mock mode or staging project | Controlled environment | | Production (< 1000 requests/month) | Real API with free tier | Cost-effective | | Production (high volume) | Real API with optimization | Better routes, traffic data | | Prototyping/demos | Mock mode | Quick to set up |
Setup Guide
Google Cloud Setup (for Production)
To use the Route Optimization API with real data, you need to set up Google Cloud:
1. Create a Google Cloud Project
- Go to Google Cloud Console
- Create a new project or select existing one
- Note your Project ID
2. Enable Required APIs
Enable these APIs in your project:
- Cloud Optimization API
- Routes API (optional, for directions)
3. Set Up Billing
Route Optimization API requires billing to be enabled:
- Go to Billing
- Link a billing account to your project
4. Choose Authentication Method
You have two options for authentication:
Option A: API Key (Simpler, Recommended for Getting Started)
- Go to Credentials
- Click "Create Credentials" > "API Key"
- Copy the API key
- (Optional but recommended) Click "Restrict Key" to:
- Add application restrictions
- Restrict to Cloud Optimization API
Pros:
- ✅ Simpler setup
- ✅ No file management
- ✅ Easy to rotate
Cons:
- ⚠️ Less granular permissions
- ⚠️ Should be restricted properly
Option B: Service Account (More Secure for Production)
- Go to IAM & Admin > Service Accounts
- Click "Create Service Account"
- Name it (e.g., "route-optimizer")
- Grant role: "Cloud Optimization API User"
- Click "Create Key" and download JSON file
- Save as
service-account-key.jsonin your project
Pros:
- ✅ More secure
- ✅ Granular IAM permissions
- ✅ Better audit trail
Cons:
- ⚠️ More complex setup
- ⚠️ File management required
5. Initialize RouteCalculator
Using API Key (Recommended for Quick Start)
import { RouteCalculator } from '@route-optimization/core';
const calculator = new RouteCalculator({
projectId: 'your-project-id',
apiKey: 'YOUR_API_KEY',
debug: true, // Enable logging during setup
});
await calculator.initialize();Using Service Account (File Path)
import { RouteCalculator } from '@route-optimization/core';
const calculator = new RouteCalculator({
projectId: 'your-project-id',
credentialsPath: './service-account-key.json',
debug: true,
});
await calculator.initialize();Using Service Account (Credentials Object)
Instead of a file path, you can pass credentials directly:
import serviceAccountKey from './service-account-key.json';
const calculator = new RouteCalculator({
projectId: 'your-project-id',
credentials: serviceAccountKey,
});Development Without Google Cloud
Use mock mode for development and testing:
const calculator = new RouteCalculator({
useMockMode: true,
debug: true,
});
await calculator.initialize(); // No credentials neededMock mode:
- ✅ No API key or credentials required
- ✅ No billing charges
- ✅ Instant responses
- ✅ Deterministic results for testing
- ❌ Simplified optimization algorithm
- ❌ No real-world traffic data
Cost Estimation
Google Route Optimization API pricing (as of 2024):
| Request Size | Price per Request | | ------------------- | --------------------------- | | 0-10 shipments | Free (1,000 requests/month) | | 11-100 shipments | $0.50 | | 101-1,000 shipments | $5.00 | | 1,001+ shipments | Contact sales |
Tips to reduce costs:
- Use mock mode during development
- Batch multiple requests together
- Cache optimization results when possible
- Set reasonable timeouts to avoid long-running requests
- Use
searchMode: 'RETURN_FAST'for quicker, cheaper results
Usage
Basic Usage with Google Maps
import { GoogleMapsAdapter, StopType } from '@route-optimization/core';
import type { Route } from '@route-optimization/core';
// Initialize the adapter
const mapAdapter = new GoogleMapsAdapter();
await mapAdapter.initialize({
apiKey: 'YOUR_GOOGLE_MAPS_API_KEY',
container: 'map-container',
center: { lat: 13.7563, lng: 100.5018 },
zoom: 12,
});
// Create a route
const route: Route = {
id: 'route-1',
vehicle: {
id: 'truck-1',
type: VehicleType.LIGHT_TRUCK,
startLocation: { lat: 13.7563, lng: 100.5018 },
},
stops: [
{
id: 'stop-1',
location: { lat: 13.7467, lng: 100.5342 },
type: StopType.PICKUP,
label: 'Pickup Point',
},
{
id: 'stop-2',
location: { lat: 13.7363, lng: 100.4918 },
type: StopType.DELIVERY,
label: 'Delivery Point',
},
],
totalDistance: 5000,
totalDuration: 900,
};
// Render the route
mapAdapter.renderRoute(route);Using Utilities
import {
calculateBounds,
calculateDistance,
formatDistance,
formatDuration,
validateRoute,
} from '@route-optimization/core';
// Calculate bounds for multiple points
const bounds = calculateBounds([
{ lat: 13.7563, lng: 100.5018 },
{ lat: 13.7467, lng: 100.5342 },
]);
// Calculate distance between two points
const distance = calculateDistance(
{ lat: 13.7563, lng: 100.5018 },
{ lat: 13.7467, lng: 100.5342 }
);
console.log(formatDistance(distance)); // "3.2 km"
// Validate a route
validateRoute(route); // Throws error if invalidRoute Optimization with RouteCalculator
The RouteCalculator service provides route optimization using Google Route Optimization API with support for mock mode.
import { RouteCalculator } from '@route-optimization/core';
import type { OptimizationRequest, OptimizationResponse } from '@route-optimization/core';
// Initialize with API Key (simplest)
const calculator = new RouteCalculator({
projectId: 'your-google-cloud-project',
apiKey: 'YOUR_API_KEY',
debug: true,
});
// OR initialize with Service Account
const calculatorWithSA = new RouteCalculator({
projectId: 'your-google-cloud-project',
credentialsPath: './service-account-key.json',
debug: true,
});
// OR use mock mode for testing
const mockCalculator = new RouteCalculator({
useMockMode: true,
debug: true,
});
// Initialize the calculator
await calculator.initialize();
// Create an optimization request
const request: OptimizationRequest = {
shipments: [
{
id: 'shipment-1',
pickups: [
{
location: { latitude: 13.7563, longitude: 100.5018 },
duration: '300s',
},
],
deliveries: [
{
location: { latitude: 13.7467, longitude: 100.5342 },
duration: '300s',
},
],
label: 'Package A',
demands: [{ type: 'weight', value: 10 }],
},
{
id: 'shipment-2',
deliveries: [
{
location: { latitude: 13.7363, longitude: 100.4918 },
duration: '300s',
},
],
label: 'Package B',
},
],
vehicles: [
{
id: 'vehicle-1',
startLocation: { latitude: 13.7563, longitude: 100.5018 },
endLocation: { latitude: 13.7563, longitude: 100.5018 },
loadLimits: {
weight: { hardMaxLoad: 50 },
},
costPerKilometer: 0.5,
costPerHour: 10,
},
],
globalDurationCostPerHour: 20,
searchMode: 'RETURN_FAST',
timeout: '30s',
};
// Optimize routes
const response: OptimizationResponse = await calculator.optimizeRoutes(request);
if (response.success) {
console.log('Optimization successful!');
console.log('Routes:', response.routes);
console.log('Metrics:', response.metrics);
console.log('Statistics:', response.statistics);
} else {
console.error('Optimization failed:', response.error);
}RouteCalculator Configuration
interface RouteCalculatorConfig {
projectId?: string; // Google Cloud project ID
apiKey?: string; // API Key for authentication (alternative to service account)
credentialsPath?: string; // Path to service account key file
credentials?: object; // Service account key JSON object
useMockMode?: boolean; // Use mock mode (no API calls)
defaultSearchMode?: 'RETURN_FAST' | 'CONSUME_ALL_AVAILABLE_TIME';
defaultTimeout?: string; // e.g., "30s"
debug?: boolean; // Enable debug logging
}Authentication Options (choose one):
apiKey: Simple API key authentication (recommended for getting started)credentialsPath: Path to service account JSON file (more secure)credentials: Service account credentials object (for dynamic loading)useMockMode: true: No authentication needed (for testing)
#### Optimization Request
```typescript
interface OptimizationRequest {
shipments: Shipment[];
vehicles: OptimizationVehicle[];
globalDurationCostPerHour?: number;
searchMode?: 'RETURN_FAST' | 'CONSUME_ALL_AVAILABLE_TIME';
timeout?: string;
considerTraffic?: boolean;
}
interface Shipment {
id?: string;
pickups?: PickupDelivery[];
deliveries?: PickupDelivery[];
demands?: Array<{ type: string; value: number }>;
label?: string;
penaltyCost?: number;
}
interface OptimizationVehicle {
id?: string;
startLocation: OptimizationLocation;
endLocation?: OptimizationLocation;
loadLimits?: { [resourceType: string]: LoadLimit };
fixedCost?: number;
costPerKilometer?: number;
costPerHour?: number;
travelMode?: 'DRIVING' | 'WALKING' | 'BICYCLING' | 'TRANSIT';
maxRouteDuration?: string;
}Optimization Response
interface OptimizationResponse {
success: boolean;
routes?: OptimizedRouteInfo[];
metrics?: OptimizationMetrics;
statistics?: RouteStatistics;
skippedShipments?: SkippedShipment[];
error?: string;
}
interface OptimizedRouteInfo {
vehicleId: string;
shipmentIds: string[];
totalDistance?: number; // meters
totalDuration?: string; // e.g., "3600s"
stops: number;
cost?: number;
}
interface OptimizationMetrics {
totalCost: number;
totalDistance?: number; // meters
usedVehicles: number;
skippedShipments: number;
optimizationDuration?: string;
}
interface RouteStatistics {
averageStopsPerVehicle: number;
maxStopsInRoute: number;
minStopsInRoute: number;
utilizationRate: number; // 0-1
}RouteCalculator Methods
initialize(): Promise<void>- Initialize the Google Route Optimization clientoptimizeRoutes(request: OptimizationRequest): Promise<OptimizationResponse>- Optimize routesupdateConfig(config: Partial<RouteCalculatorConfig>): void- Update configurationgetConfig(): RouteCalculatorConfig- Get current configuration
Mock Mode
Mock mode allows you to test optimization without making actual API calls or setting up Google Cloud credentials:
const calculator = new RouteCalculator({
useMockMode: true,
debug: true,
});
await calculator.initialize(); // No API setup needed
const response = await calculator.optimizeRoutes(request);
// Returns simulated optimization resultsError Handling
The calculator provides detailed error messages with helpful tips:
const response = await calculator.optimizeRoutes(request);
if (!response.success) {
console.error('Error:', response.error);
// Error messages include troubleshooting tips
// Example: "API not enabled. Enable it at: https://..."
}Advanced Examples
Multi-Vehicle Optimization
const request: OptimizationRequest = {
shipments: [
{ id: 'pkg-1', deliveries: [{ location: { latitude: 13.7467, longitude: 100.5342 } }] },
{ id: 'pkg-2', deliveries: [{ location: { latitude: 13.7363, longitude: 100.4918 } }] },
{ id: 'pkg-3', deliveries: [{ location: { latitude: 13.7263, longitude: 100.5218 } }] },
],
vehicles: [
{
id: 'truck-1',
startLocation: { latitude: 13.7563, longitude: 100.5018 },
costPerKilometer: 0.8,
loadLimits: { weight: { hardMaxLoad: 100 } },
},
{
id: 'truck-2',
startLocation: { latitude: 13.7563, longitude: 100.5018 },
costPerKilometer: 0.5,
loadLimits: { weight: { hardMaxLoad: 50 } },
},
],
};
const response = await calculator.optimizeRoutes(request);
response.routes?.forEach((route) => {
console.log(`${route.vehicleId}: ${route.stops} stops, ${route.totalDistance}m`);
});With Time Windows
const request: OptimizationRequest = {
shipments: [
{
id: 'urgent-delivery',
deliveries: [
{
location: { latitude: 13.7467, longitude: 100.5342 },
duration: '300s',
timeWindows: [
{
startTime: '2024-01-15T08:00:00Z',
endTime: '2024-01-15T10:00:00Z',
},
],
},
],
penaltyCost: 1000, // High penalty if skipped
},
],
vehicles: [
{
id: 'express-van',
startLocation: { latitude: 13.7563, longitude: 100.5018 },
maxRouteDuration: '14400s', // 4 hours max
},
],
};With Load Capacity
const request: OptimizationRequest = {
shipments: [
{
id: 'heavy-pkg',
deliveries: [{ location: { latitude: 13.7467, longitude: 100.5342 } }],
demands: [
{ type: 'weight', value: 30 },
{ type: 'volume', value: 5 },
],
},
],
vehicles: [
{
id: 'cargo-truck',
startLocation: { latitude: 13.7563, longitude: 100.5018 },
loadLimits: {
weight: {
softMaxLoad: 80,
hardMaxLoad: 100,
costPerUnitAboveSoftMax: 2,
},
volume: {
hardMaxLoad: 10,
},
},
},
],
};Analyzing Results
const response = await calculator.optimizeRoutes(request);
if (response.success && response.metrics && response.statistics) {
console.log('=== Optimization Results ===');
console.log(`Total Cost: $${response.metrics.totalCost.toFixed(2)}`);
console.log(`Total Distance: ${(response.metrics.totalDistance! / 1000).toFixed(1)} km`);
console.log(`Vehicles Used: ${response.metrics.usedVehicles}`);
console.log(`Avg Stops/Vehicle: ${response.statistics.averageStopsPerVehicle.toFixed(1)}`);
console.log(`Utilization: ${(response.statistics.utilizationRate * 100).toFixed(1)}%`);
if (response.skippedShipments && response.skippedShipments.length > 0) {
console.log(`\nSkipped Shipments: ${response.skippedShipments.length}`);
response.skippedShipments.forEach((s) => {
console.log(` - ${s.id}: ${s.reason}`);
});
}
}API Reference
Types
LatLng
interface LatLng {
lat: number; // -90 to 90
lng: number; // -180 to 180
}Stop
interface Stop {
id: string;
location: LatLng;
type: StopType;
label?: string;
address?: string;
sequence?: number;
timeWindow?: TimeWindow;
serviceDuration?: number;
demand?: number;
metadata?: Record<string, unknown>;
}Route
interface Route {
id: string;
vehicle: Vehicle;
stops: Stop[];
segments?: RouteSegment[];
totalDistance: number;
totalDuration: number;
totalCost?: number;
metrics?: RouteMetrics;
metadata?: Record<string, unknown>;
}Map Adapters
IMapAdapter
Interface for map provider adapters.
interface IMapAdapter {
initialize(config: MapConfig): Promise<void>;
renderRoute(route: Route, options?: RouteRenderOptions): void;
renderRoutes(routes: Route[], options?: RouteRenderOptions): void;
addMarker(stop: Stop, config?: MarkerConfig): string;
removeMarker(markerId: string): void;
clearMarkers(): void;
fitBounds(bounds?: MapBounds): void;
setCenter(center: LatLng): void;
setZoom(zoom: number): void;
clearRoutes(): void;
getBounds(): MapBounds | null;
destroy(): void;
getMapInstance(): unknown;
}GoogleMapsAdapter
Google Maps implementation of IMapAdapter.
const adapter = new GoogleMapsAdapter({
events: {
onClick: (latLng) => console.log('Map clicked:', latLng),
onMarkerClick: (stop) => console.log('Marker clicked:', stop),
},
clustering: true,
});Utilities
Coordinate Utilities
calculateBounds(points: LatLng[]): MapBoundscalculateDistance(from: LatLng, to: LatLng): numberformatDistance(meters: number, locale?: string): stringformatDuration(seconds: number): stringisValidCoordinate(coord: LatLng): booleangetBoundsCenter(bounds: MapBounds): LatLngdecodePolyline(encoded: string): LatLng[]
Validation Utilities
validateRoute(route: Route): voidvalidateStop(stop: Stop): voidvalidateApiKey(apiKey: string): voidvalidateRoutes(routes: Route[]): void
Troubleshooting
RouteCalculator Issues
API Not Enabled Error
Route Optimization API is not enabled.Solution: Enable the API in Google Cloud Console:
- Go to Cloud Optimization API
- Select your project
- Click "Enable"
Alternative: Use mock mode for testing:
const calculator = new RouteCalculator({ useMockMode: true });Authentication Failed
Authentication failed. Please check your credentials.Solutions:
- Verify your service account key file path is correct
- Ensure the service account has the "Cloud Optimization API User" role
- Check that the key file has valid JSON format
- Try using mock mode to isolate the issue
Billing Error
Billing issue: PERMISSION_DENIEDSolutions:
- Enable billing for your Google Cloud project
- Ensure the service account has billing permissions
- Use mock mode for development without billing
No Routes Returned
If optimization succeeds but no routes are returned:
const response = await calculator.optimizeRoutes(request);
console.log('Skipped shipments:', response.skippedShipments);Common causes:
- Vehicle capacity too low for shipment demands
- Time windows too restrictive
- Start/end locations too far from shipments
- Insufficient vehicles for number of shipments
Solutions:
- Increase vehicle
loadLimits - Adjust or remove time windows
- Add
penaltyCostto shipments to allow skipping - Add more vehicles or reduce shipments
Debug Mode
Enable debug logging to troubleshoot issues:
const calculator = new RouteCalculator({
debug: true,
useMockMode: false,
projectId: 'your-project',
});
await calculator.initialize();
const response = await calculator.optimizeRoutes(request);
// Console will show detailed information:
// 🚀 RouteCalculator initialized
// 🚀 Sending optimization request to Google API...
// 📦 Shipments: 5
// 🚛 Vehicles: 2
// ✅ Received response in 1234msMap Adapter Issues
Google Maps Not Loading
Ensure you:
- Have a valid Google Maps API key
- Enabled "Maps JavaScript API" in Google Cloud Console
- Set up billing for your project
- Added API key restrictions if needed
Markers Not Appearing
Check that:
- Stop coordinates are valid (lat: -90 to 90, lng: -180 to 180)
- Map is fully initialized before adding markers
- Container element exists in DOM
Development
# Install dependencies
pnpm install
# Build
pnpm build
# Test
pnpm test
# Test with coverage
pnpm test -- --coverage
# Lint
pnpm lintBundle Size
- Core: < 5KB (gzipped)
- Zero runtime dependencies
- Tree-shakeable: Only import what you need
Browser Support
- Chrome >= 90
- Firefox >= 88
- Safari >= 14
- Edge >= 90
License
MIT
Related Packages
- @route-optimization/react - React hooks and components
- @route-optimization/vue - Vue composables and components
- @route-optimization/vanilla - Vanilla JavaScript API
