Core package for route optimization map visualization
npm install @route-optimization/coreCore 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
This package has two entry points:
typescript
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)$3
`typescript
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
- Google Cloud Setup
- Development Without Google Cloud
- Usage
- Route Optimization with RouteCalculator
- Basic Usage with Google Maps
- Using Utilities
- Advanced Examples
- 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
`bash
npm install @route-optimization/core
or
pnpm add @route-optimization/core
or
yarn add @route-optimization/core
`Quick Start
$3
> β οΈ 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
`typescript
// β
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:`typescript
// 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',
});
`$3
`typescript
// β
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,
},
],
});
`$3
| 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
$3
To use the Route Optimization API with real data, you need to set up Google Cloud:
#### 1. Create a Google Cloud Project
1. Go to Google Cloud Console
2. Create a new project or select existing one
3. 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:
1. Go to Billing
2. 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)
1. Go to Credentials
2. Click "Create Credentials" > "API Key"
3. Copy the API key
4. (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)
1. Go to IAM & Admin > Service Accounts
2. Click "Create Service Account"
3. Name it (e.g., "route-optimizer")
4. Grant role: "Cloud Optimization API User"
5. Click "Create Key" and download JSON file
6. Save as
service-account-key.json in your projectPros:
- β
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)
`typescript
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)
`typescript
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:
`typescript
import serviceAccountKey from './service-account-key.json';const calculator = new RouteCalculator({
projectId: 'your-project-id',
credentials: serviceAccountKey,
});
`$3
Use mock mode for development and testing:
`typescript
const calculator = new RouteCalculator({
useMockMode: true,
debug: true,
});await calculator.initialize(); // No credentials needed
`Mock mode:
- β
No API key or credentials required
- β
No billing charges
- β
Instant responses
- β
Deterministic results for testing
- β Simplified optimization algorithm
- β No real-world traffic data
$3
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 resultsUsage
$3
`typescript
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);
`$3
`typescript
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 invalid
`$3
The
RouteCalculator service provides route optimization using Google Route Optimization API with support for mock mode.`typescript
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
`typescript
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
`typescript
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 - Initialize the Google Route Optimization client
- optimizeRoutes(request: OptimizationRequest): Promise - Optimize routes
- updateConfig(config: Partial - Update configuration
- getConfig(): RouteCalculatorConfig - Get current configuration#### Mock Mode
Mock mode allows you to test optimization without making actual API calls or setting up Google Cloud credentials:
`typescript
const calculator = new RouteCalculator({
useMockMode: true,
debug: true,
});await calculator.initialize(); // No API setup needed
const response = await calculator.optimizeRoutes(request);
// Returns simulated optimization results
`#### Error Handling
The calculator provides detailed error messages with helpful tips:
`typescript
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://..."
}
`$3
#### Multi-Vehicle Optimization
`typescript
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
`typescript
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
`typescript
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
`typescript
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
$3
####
LatLng`typescript
interface LatLng {
lat: number; // -90 to 90
lng: number; // -180 to 180
}
`####
Stop`typescript
interface Stop {
id: string;
location: LatLng;
type: StopType;
label?: string;
address?: string;
sequence?: number;
timeWindow?: TimeWindow;
serviceDuration?: number;
demand?: number;
metadata?: Record;
}
`####
Route`typescript
interface Route {
id: string;
vehicle: Vehicle;
stops: Stop[];
segments?: RouteSegment[];
totalDistance: number;
totalDuration: number;
totalCost?: number;
metrics?: RouteMetrics;
metadata?: Record;
}
`$3
####
IMapAdapterInterface for map provider adapters.
`typescript
interface IMapAdapter {
initialize(config: MapConfig): Promise;
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;
}
`####
GoogleMapsAdapterGoogle Maps implementation of
IMapAdapter.`typescript
const adapter = new GoogleMapsAdapter({
events: {
onClick: (latLng) => console.log('Map clicked:', latLng),
onMarkerClick: (stop) => console.log('Marker clicked:', stop),
},
clustering: true,
});
`$3
#### Coordinate Utilities
-
calculateBounds(points: LatLng[]): MapBounds
- calculateDistance(from: LatLng, to: LatLng): number
- formatDistance(meters: number, locale?: string): string
- formatDuration(seconds: number): string
- isValidCoordinate(coord: LatLng): boolean
- getBoundsCenter(bounds: MapBounds): LatLng
- decodePolyline(encoded: string): LatLng[]#### Validation Utilities
-
validateRoute(route: Route): void
- validateStop(stop: Stop): void
- validateApiKey(apiKey: string): void
- validateRoutes(routes: Route[]): voidTroubleshooting
$3
#### API Not Enabled Error
`
Route Optimization API is not enabled.
`Solution: Enable the API in Google Cloud Console:
1. Go to Cloud Optimization API
2. Select your project
3. Click "Enable"
Alternative: Use mock mode for testing:
`typescript
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_DENIED
`Solutions:
- 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:
`typescript
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 penaltyCost to shipments to allow skipping
- Add more vehicles or reduce shipments#### Debug Mode
Enable debug logging to troubleshoot issues:
`typescript
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 1234ms
`$3
#### Google Maps Not Loading
Ensure you:
1. Have a valid Google Maps API key
2. Enabled "Maps JavaScript API" in Google Cloud Console
3. Set up billing for your project
4. 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
`bash
Install dependencies
pnpm installBuild
pnpm buildTest
pnpm testTest with coverage
pnpm test -- --coverageLint
pnpm lint
``- Core: < 5KB (gzipped)
- Zero runtime dependencies
- Tree-shakeable: Only import what you need
- Chrome >= 90
- Firefox >= 88
- Safari >= 14
- Edge >= 90
MIT
- @route-optimization/react - React hooks and components
- @route-optimization/vue - Vue composables and components
- @route-optimization/vanilla - Vanilla JavaScript API