🚀 New /route/batch
endpoint! Learn more
// JavaScript/Node.js Example
const axios = require('axios');
const SOLVICE_API_KEY = 'your-api-key';
const BASE_URL = 'https://routing.solvice.io';
async function calculateRoute(start, end) {
try {
const response = await axios.post(`${BASE_URL}/route`, {
coordinates: [start, end],
profile: 'car',
steps: true,
geometries: 'geojson'
}, {
headers: {
'X-API-Key': SOLVICE_API_KEY,
'Content-Type': 'application/json'
}
});
const route = response.data.routes[0];
return {
distance: route.distance,
duration: route.duration,
geometry: route.geometry,
steps: route.legs[0].steps
};
} catch (error) {
console.error('Route calculation failed:', error.response?.data || error.message);
throw error;
}
}
// Usage
const start = [4.3517, 50.8503]; // Brussels
const end = [2.3522, 48.8566]; // Paris
calculateRoute(start, end)
.then(route => {
console.log(`Distance: ${route.distance/1000} km`);
console.log(`Duration: ${route.duration/60} minutes`);
console.log(`First instruction: ${route.steps[0].name}`);
});
# Python Example
import requests
import numpy as np
from typing import List, Tuple
class SolviceMapsClient:
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = 'https://routing.solvice.io'
self.headers = {
'X-API-Key': api_key,
'Content-Type': 'application/json'
}
def calculate_matrix(self, sources: List[Tuple[float, float]],
destinations: List[Tuple[float, float]]) -> dict:
"""Calculate distance matrix between sources and destinations."""
payload = {
'sources': sources,
'destinations': destinations,
'profile': 'car',
'annotations': ['duration', 'distance']
}
# Use sync endpoint for small matrices
if len(sources) * len(destinations) <= 1000:
response = requests.post(
f'{self.base_url}/table/sync',
json=payload,
headers=self.headers
)
else:
# Use async endpoint for large matrices
response = requests.post(
f'{self.base_url}/table',
json=payload,
headers=self.headers
)
response.raise_for_status()
return response.json()
def find_nearest_warehouse(self, customer_location: Tuple[float, float],
warehouses: List[Tuple[float, float]]) -> dict:
"""Find the nearest warehouse to a customer location."""
result = self.calculate_matrix([customer_location], warehouses)
durations = result['durations'][0]
distances = result['distances'][0]
nearest_index = np.argmin(durations)
return {
'warehouse_index': nearest_index,
'warehouse_location': warehouses[nearest_index],
'travel_time': durations[nearest_index],
'distance': distances[nearest_index]
}
# Usage Example
client = SolviceMapsClient('your-api-key')
# Warehouse locations
warehouses = [
[4.3517, 50.8503], # Brussels
[2.3522, 48.8566], # Paris
[3.0686, 50.6365], # Lille
]
# Customer location
customer = [1.0952, 49.4431] # Rouen
nearest = client.find_nearest_warehouse(customer, warehouses)
print(f"Nearest warehouse: {nearest['warehouse_location']}")
print(f"Travel time: {nearest['travel_time']/60:.1f} minutes")
print(f"Distance: {nearest['distance']/1000:.1f} km")
// Advanced JavaScript Example with WebSocket updates
class DeliveryOptimizer {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseUrl = 'https://routing.solvice.io';
this.activeCalculations = new Map();
}
async optimizeDeliveryRoutes(vehicles, deliveries) {
const results = [];
for (const vehicle of vehicles) {
const route = await this.calculateOptimalRoute(
vehicle.location,
deliveries.filter(d => d.assignedVehicle === vehicle.id),
vehicle.capacity
);
results.push({ vehicleId: vehicle.id, route });
}
return results;
}
async calculateOptimalRoute(startLocation, deliveries, capacity) {
// Create distance matrix for all locations
const locations = [startLocation, ...deliveries.map(d => d.location)];
const matrixResponse = await fetch(`${this.baseUrl}/table`, {
method: 'POST',
headers: {
'X-API-Key': this.apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify({
sources: locations,
destinations: locations,
profile: 'car',
annotations: ['duration', 'distance']
})
});
const matrixData = await matrixResponse.json();
if (matrixData.id) {
// Async processing - poll for results
const matrix = await this.pollForResults(`/table/${matrixData.id}/response`);
return this.solveTSP(matrix, deliveries, capacity);
} else {
// Sync response
return this.solveTSP(matrixData, deliveries, capacity);
}
}
async pollForResults(url, maxWaitTime = 300000) {
const startTime = Date.now();
while (Date.now() - startTime < maxWaitTime) {
try {
const response = await fetch(`${this.baseUrl}${url}`, {
headers: { 'X-API-Key': this.apiKey }
});
if (response.ok) {
return await response.json();
} else if (response.status === 404) {
// Still processing
await new Promise(resolve => setTimeout(resolve, 5000));
continue;
} else {
throw new Error(`Request failed: ${response.status}`);
}
} catch (error) {
console.error('Polling error:', error);
await new Promise(resolve => setTimeout(resolve, 5000));
}
}
throw new Error('Request timeout');
}
solveTSP(matrix, deliveries, capacity) {
// Simplified TSP solver using nearest neighbor heuristic
const durations = matrix.durations;
const distances = matrix.distances;
let currentLocation = 0; // Start location
const unvisited = new Set(deliveries.map((_, i) => i + 1)); // +1 because index 0 is start
const route = [0];
let totalDistance = 0;
let totalDuration = 0;
let currentCapacity = 0;
while (unvisited.size > 0) {
let nearest = null;
let nearestDistance = Infinity;
for (const location of unvisited) {
const delivery = deliveries[location - 1];
// Check capacity constraint
if (currentCapacity + delivery.weight <= capacity) {
const distance = durations[currentLocation][location];
if (distance < nearestDistance) {
nearestDistance = distance;
nearest = location;
}
}
}
if (nearest === null) {
// No more deliveries fit - return to depot
totalDistance += distances[currentLocation][0];
totalDuration += durations[currentLocation][0];
route.push(0);
currentCapacity = 0;
currentLocation = 0;
continue;
}
// Visit nearest location
unvisited.delete(nearest);
route.push(nearest);
totalDistance += distances[currentLocation][nearest];
totalDuration += durations[currentLocation][nearest];
currentCapacity += deliveries[nearest - 1].weight;
currentLocation = nearest;
}
// Return to depot
totalDistance += distances[currentLocation][0];
totalDuration += durations[currentLocation][0];
route.push(0);
return {
route,
totalDistance,
totalDuration,
deliveries: route.slice(1, -1).map(i => deliveries[i - 1])
};
}
}
// Usage
const optimizer = new DeliveryOptimizer('your-api-key');
const vehicles = [
{ id: 'v1', location: [4.3517, 50.8503], capacity: 1000 },
{ id: 'v2', location: [2.3522, 48.8566], capacity: 1500 }
];
const deliveries = [
{ id: 'd1', location: [1.0952, 49.4431], weight: 100, assignedVehicle: 'v1' },
{ id: 'd2', location: [7.7521, 48.5734], weight: 200, assignedVehicle: 'v1' },
{ id: 'd3', location: [5.3698, 43.2965], weight: 300, assignedVehicle: 'v2' }
];
optimizer.optimizeDeliveryRoutes(vehicles, deliveries)
.then(results => {
console.log('Optimized routes:', results);
});
# Time-dependent routing example
import requests
import datetime
from typing import List, Dict, Any
class TrafficAwareRouter:
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = 'https://routing.solvice.io'
self.headers = {
'X-API-Key': api_key,
'Content-Type': 'application/json'
}
def get_traffic_slice(self, departure_time: datetime.datetime) -> float:
"""Convert departure time to traffic slice."""
hour = departure_time.hour
minute = departure_time.minute
# Map hour to slice (0-12 representing different traffic conditions)
if hour < 6:
return 0 # Early morning
elif hour < 8:
return 1 + (hour - 6) + (minute / 60) # Morning buildup
elif hour < 10:
return 2 + (hour - 8) + (minute / 60) # Peak morning
elif hour < 16:
return 4 + ((hour - 10) / 6) * 6 # Midday (interpolated)
elif hour < 19:
return 10 + (hour - 16) + (minute / 60) # Evening peak
else:
return 12 # Evening/night
def plan_time_dependent_route(self,
start: List[float],
destinations: List[List[float]],
departure_time: datetime.datetime) -> Dict[str, Any]:
"""Plan route considering traffic at departure time."""
traffic_slice = self.get_traffic_slice(departure_time)
day_type = 'weekday' if departure_time.weekday() < 5 else 'weekend'
# Calculate route with traffic consideration
payload = {
'sources': [start],
'destinations': destinations,
'profile': 'car',
'traffic_slice': traffic_slice,
'day_type': day_type,
'annotations': ['duration', 'distance']
}
response = requests.post(
f'{self.base_url}/table/sync',
json=payload,
headers=self.headers
)
response.raise_for_status()
result = response.json()
# Find optimal route considering traffic
durations = result['durations'][0]
distances = result['distances'][0]
# Sort destinations by travel time
destination_data = [
{
'index': i,
'location': destinations[i],
'duration': durations[i],
'distance': distances[i],
'arrival_time': departure_time + datetime.timedelta(seconds=durations[i])
}
for i in range(len(destinations))
]
destination_data.sort(key=lambda x: x['duration'])
return {
'departure_time': departure_time,
'traffic_slice': traffic_slice,
'day_type': day_type,
'optimal_sequence': destination_data
}
def create_cube_for_day(self,
start: List[float],
destinations: List[List[float]],
date: datetime.date) -> str:
"""Create a cube showing travel times throughout the day."""
day_type = 'weekday' if date.weekday() < 5 else 'weekend'
# Define time slices for the entire day
time_slices = [
{'slice': i, 'time': f'{6 + i}:00'}
for i in range(13) # 6 AM to 6 PM
]
payload = {
'sources': [start],
'destinations': destinations,
'profile': 'car',
'time_slices': time_slices,
'day_type': day_type,
'generate_polynomials': True
}
response = requests.post(
f'{self.base_url}/cube',
json=payload,
headers=self.headers
)
response.raise_for_status()
cube_data = response.json()
return cube_data['id']
def get_optimal_departure_times(self, cube_id: str) -> Dict[str, Any]:
"""Analyze cube results to find optimal departure times."""
response = requests.get(
f'{self.base_url}/cube/{cube_id}/response',
headers=self.headers
)
response.raise_for_status()
cube_data = response.json()
# Analyze time slices to find optimal departure times
optimal_times = {}
for dest_idx in range(len(cube_data['destinations'])):
min_duration = float('inf')
optimal_slice = None
for slice_data in cube_data['time_slices']:
duration = slice_data['durations'][0][dest_idx]
if duration < min_duration:
min_duration = duration
optimal_slice = slice_data['slice']
optimal_times[f'destination_{dest_idx}'] = {
'optimal_departure_hour': 6 + optimal_slice,
'minimum_duration': min_duration,
'destination': cube_data['destinations'][dest_idx]
}
return optimal_times
# Usage Example
router = TrafficAwareRouter('your-api-key')
start_location = [4.3517, 50.8503] # Brussels
destinations = [
[2.3522, 48.8566], # Paris
[3.0686, 50.6365], # Lille
[1.0952, 49.4431] # Rouen
]
# Plan route for specific departure time
departure = datetime.datetime(2024, 1, 15, 8, 30) # Monday 8:30 AM
route = router.plan_time_dependent_route(start_location, destinations, departure)
print(f"Departure time: {route['departure_time']}")
print(f"Traffic slice: {route['traffic_slice']}")
print("\nOptimal sequence:")
for dest in route['optimal_sequence']:
print(f" → {dest['location']} (arrival: {dest['arrival_time']}, {dest['duration']/60:.1f} min)")
# Create cube for entire day analysis
cube_id = router.create_cube_for_day(start_location, destinations, departure.date())
print(f"\nCube created: {cube_id}")
# Wait for processing and get optimal departure times
import time
time.sleep(30) # Wait for cube processing
optimal_times = router.get_optimal_departure_times(cube_id)
print("\nOptimal departure times:")
for dest, info in optimal_times.items():
print(f" {dest}: {info['optimal_departure_hour']}:00 ({info['minimum_duration']/60:.1f} min)")
// React Component Example
import React, { useState, useEffect } from 'react';
import { MapContainer, TileLayer, Marker, Polyline, Popup } from 'react-leaflet';
import 'leaflet/dist/leaflet.css';
const SolviceMapsComponent = () => {
const [route, setRoute] = useState(null);
const [loading, setLoading] = useState(false);
const [startPoint, setStartPoint] = useState([4.3517, 50.8503]); // Brussels
const [endPoint, setEndPoint] = useState([2.3522, 48.8566]); // Paris
const calculateRoute = async () => {
setLoading(true);
try {
const response = await fetch('https://routing.solvice.io/route', {
method: 'POST',
headers: {
'X-API-Key': process.env.REACT_APP_SOLVICE_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({
coordinates: [startPoint, endPoint],
profile: 'car',
steps: true,
geometries: 'geojson'
})
});
const data = await response.json();
if (data.routes && data.routes.length > 0) {
const routeData = data.routes[0];
setRoute({
geometry: routeData.geometry.coordinates,
distance: routeData.distance,
duration: routeData.duration,
steps: routeData.legs[0].steps
});
}
} catch (error) {
console.error('Route calculation error:', error);
} finally {
setLoading(false);
}
};
const handleMapClick = (e) => {
const { lat, lng } = e.latlng;
if (!startPoint || (startPoint && endPoint)) {
setStartPoint([lng, lat]);
setEndPoint(null);
setRoute(null);
} else {
setEndPoint([lng, lat]);
}
};
useEffect(() => {
if (startPoint && endPoint) {
calculateRoute();
}
}, [startPoint, endPoint]);
// Convert Solvice Maps coordinate format [lng, lat] to Leaflet format [lat, lng]
const toLeafletCoords = (coords) => coords ? [coords[1], coords[0]] : null;
const routeCoords = route?.geometry?.map(coord => [coord[1], coord[0]]) || [];
return (
<div style={{ height: '600px', width: '100%' }}>
<div style={{ padding: '10px', background: '#f0f0f0' }}>
<h3>Solvice Maps Route Demo</h3>
<p>Click on the map to set start and end points</p>
{loading && <p>Calculating route...</p>}
{route && (
<div>
<p><strong>Distance:</strong> {(route.distance / 1000).toFixed(1)} km</p>
<p><strong>Duration:</strong> {Math.round(route.duration / 60)} minutes</p>
</div>
)}
</div>
<MapContainer
center={[50.8503, 4.3517]}
zoom={7}
style={{ height: '500px', width: '100%' }}
onClick={handleMapClick}
>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
/>
{startPoint && (
<Marker position={toLeafletCoords(startPoint)}>
<Popup>Start Point</Popup>
</Marker>
)}
{endPoint && (
<Marker position={toLeafletCoords(endPoint)}>
<Popup>End Point</Popup>
</Marker>
)}
{route && routeCoords.length > 0 && (
<Polyline
positions={routeCoords}
color="blue"
weight={4}
opacity={0.7}
/>
)}
</MapContainer>
</div>
);
};
export default SolviceMapsComponent;
// React Native Example
import React, { useState, useEffect } from 'react';
import { View, Text, Button, Alert, AsyncStorage } from 'react-native';
import MapView, { Marker, Polyline } from 'react-native-maps';
import NetInfo from '@react-native-async-storage/async-storage';
class SolviceMapsService {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseUrl = 'https://routing.solvice.io';
this.cache = new Map();
}
async calculateRoute(start, end, options = {}) {
const cacheKey = `${start.join(',')}-${end.join(',')}`;
// Check cache first
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey);
}
try {
const response = await fetch(`${this.baseUrl}/route`, {
method: 'POST',
headers: {
'X-API-Key': this.apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify({
coordinates: [start, end],
profile: options.profile || 'car',
steps: options.steps || true,
geometries: 'geojson'
})
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
if (data.routes && data.routes.length > 0) {
const route = data.routes[0];
const result = {
coordinates: route.geometry.coordinates.map(coord => ({
latitude: coord[1],
longitude: coord[0]
})),
distance: route.distance,
duration: route.duration,
steps: route.legs[0].steps
};
// Cache the result
this.cache.set(cacheKey, result);
// Store in AsyncStorage for offline access
await AsyncStorage.setItem(`route_${cacheKey}`, JSON.stringify(result));
return result;
}
} catch (error) {
console.error('Route calculation failed:', error);
// Try to load from offline cache
try {
const cachedRoute = await AsyncStorage.getItem(`route_${cacheKey}`);
if (cachedRoute) {
Alert.alert('Offline Mode', 'Using cached route data');
return JSON.parse(cachedRoute);
}
} catch (cacheError) {
console.error('Cache error:', cacheError);
}
throw error;
}
}
async calculateMatrix(sources, destinations) {
const networkState = await NetInfo.fetch();
if (!networkState.isConnected) {
throw new Error('No internet connection');
}
const response = await fetch(`${this.baseUrl}/table/sync`, {
method: 'POST',
headers: {
'X-API-Key': this.apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify({
sources,
destinations,
profile: 'car',
annotations: ['duration', 'distance']
})
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
}
}
const NavigationApp = () => {
const [route, setRoute] = useState(null);
const [loading, setLoading] = useState(false);
const [startLocation, setStartLocation] = useState({
latitude: 50.8503,
longitude: 4.3517
});
const [endLocation, setEndLocation] = useState({
latitude: 48.8566,
longitude: 2.3522
});
const mapsService = new SolviceMapsService('your-api-key');
const calculateRoute = async () => {
setLoading(true);
try {
const start = [startLocation.longitude, startLocation.latitude];
const end = [endLocation.longitude, endLocation.latitude];
const routeData = await mapsService.calculateRoute(start, end, {
profile: 'car',
steps: true
});
setRoute(routeData);
} catch (error) {
Alert.alert('Error', 'Failed to calculate route: ' + error.message);
} finally {
setLoading(false);
}
};
const onMapPress = (event) => {
const { coordinate } = event.nativeEvent;
if (!route) {
setStartLocation(coordinate);
} else {
setEndLocation(coordinate);
setRoute(null);
}
};
useEffect(() => {
if (startLocation && endLocation) {
calculateRoute();
}
}, [startLocation, endLocation]);
return (
<View style={{ flex: 1 }}>
<MapView
style={{ flex: 1 }}
initialRegion={{
latitude: 50.8503,
longitude: 4.3517,
latitudeDelta: 5.0,
longitudeDelta: 5.0
}}
onPress={onMapPress}
>
<Marker
coordinate={startLocation}
title="Start"
pinColor="green"
/>
<Marker
coordinate={endLocation}
title="End"
pinColor="red"
/>
{route && (
<Polyline
coordinates={route.coordinates}
strokeColor="blue"
strokeWidth={4}
/>
)}
</MapView>
<View style={{ padding: 20, backgroundColor: 'white' }}>
<Text style={{ fontSize: 18, fontWeight: 'bold' }}>
Solvice Maps Navigation
</Text>
{loading && <Text>Calculating route...</Text>}
{route && (
<View>
<Text>Distance: {(route.distance / 1000).toFixed(1)} km</Text>
<Text>Duration: {Math.round(route.duration / 60)} minutes</Text>
</View>
)}
<Button
title="Recalculate Route"
onPress={calculateRoute}
disabled={loading || !startLocation || !endLocation}
/>
</View>
</View>
);
};
export default NavigationApp;
// Express.js Microservice Example
const express = require('express');
const redis = require('redis');
const axios = require('axios');
class RoutingMicroservice {
constructor(config) {
this.app = express();
this.solviceApiKey = config.solviceApiKey;
this.solviceBaseUrl = config.solviceBaseUrl;
this.redis = redis.createClient(config.redisUrl);
this.setupMiddleware();
this.setupRoutes();
}
setupMiddleware() {
this.app.use(express.json());
this.app.use((req, res, next) => {
// Add request ID for tracing
req.requestId = require('uuid').v4();
console.log(`[${req.requestId}] ${req.method} ${req.path}`);
next();
});
}
setupRoutes() {
// Health check
this.app.get('/health', (req, res) => {
res.json({ status: 'healthy', timestamp: new Date().toISOString() });
});
// Route calculation with caching
this.app.post('/routes/calculate', async (req, res) => {
try {
await this.calculateRoute(req, res);
} catch (error) {
console.error(`[${req.requestId}] Route calculation error:`, error);
res.status(500).json({
error: 'Internal server error',
requestId: req.requestId
});
}
});
// Batch route calculation
this.app.post('/routes/batch', async (req, res) => {
try {
await this.calculateBatchRoutes(req, res);
} catch (error) {
console.error(`[${req.requestId}] Batch calculation error:`, error);
res.status(500).json({
error: 'Internal server error',
requestId: req.requestId
});
}
});
// Distance matrix
this.app.post('/matrix/calculate', async (req, res) => {
try {
await this.calculateMatrix(req, res);
} catch (error) {
console.error(`[${req.requestId}] Matrix calculation error:`, error);
res.status(500).json({
error: 'Internal server error',
requestId: req.requestId
});
}
});
}
async calculateRoute(req, res) {
const { start, end, profile = 'car', useTraffic = false } = req.body;
// Validate input
if (!start || !end || start.length !== 2 || end.length !== 2) {
return res.status(400).json({
error: 'Invalid coordinates',
requestId: req.requestId
});
}
// Generate cache key
const cacheKey = `route:${start.join(',')}:${end.join(',')}:${profile}`;
// Check cache
const cachedResult = await this.redis.get(cacheKey);
if (cachedResult) {
console.log(`[${req.requestId}] Cache hit`);
return res.json({
...JSON.parse(cachedResult),
cached: true,
requestId: req.requestId
});
}
// Call Solvice Maps API
const solviceResponse = await axios.post(`${this.solviceBaseUrl}/route`, {
coordinates: [start, end],
profile,
steps: true,
geometries: 'geojson'
}, {
headers: {
'X-API-Key': this.solviceApiKey,
'Content-Type': 'application/json'
}
});
const route = solviceResponse.data.routes[0];
const result = {
distance: route.distance,
duration: route.duration,
geometry: route.geometry,
steps: route.legs[0].steps.map(step => ({
instruction: step.maneuver.type,
name: step.name,
distance: step.distance,
duration: step.duration
}))
};
// Cache result for 1 hour
await this.redis.setex(cacheKey, 3600, JSON.stringify(result));
res.json({
...result,
cached: false,
requestId: req.requestId
});
}
async calculateBatchRoutes(req, res) {
const { routes } = req.body;
if (!Array.isArray(routes) || routes.length === 0) {
return res.status(400).json({
error: 'Routes array is required',
requestId: req.requestId
});
}
// Process routes in parallel with concurrency limit
const CONCURRENCY_LIMIT = 5;
const results = [];
for (let i = 0; i < routes.length; i += CONCURRENCY_LIMIT) {
const batch = routes.slice(i, i + CONCURRENCY_LIMIT);
const batchPromises = batch.map(async (route, index) => {
try {
const mockReq = { body: route, requestId: `${req.requestId}-${i + index}` };
const mockRes = {
json: (data) => data,
status: () => mockRes
};
return await this.calculateRoute(mockReq, mockRes);
} catch (error) {
return {
error: error.message,
route: route
};
}
});
const batchResults = await Promise.all(batchPromises);
results.push(...batchResults);
}
res.json({
results,
requestId: req.requestId
});
}
async calculateMatrix(req, res) {
const { sources, destinations, profile = 'car' } = req.body;
if (!Array.isArray(sources) || !Array.isArray(destinations)) {
return res.status(400).json({
error: 'Sources and destinations must be arrays',
requestId: req.requestId
});
}
// Determine if we should use sync or async endpoint
const totalCombinations = sources.length * destinations.length;
const useAsync = totalCombinations > 1000;
try {
const endpoint = useAsync ? '/table' : '/table/sync';
const solviceResponse = await axios.post(`${this.solviceBaseUrl}${endpoint}`, {
sources,
destinations,
profile,
annotations: ['duration', 'distance']
}, {
headers: {
'X-API-Key': this.solviceApiKey,
'Content-Type': 'application/json'
}
});
if (useAsync) {
// Return job ID for async processing
res.json({
jobId: solviceResponse.data.id,
status: 'processing',
estimatedCompletion: solviceResponse.data.estimated_completion,
checkUrl: `/matrix/status/${solviceResponse.data.id}`,
requestId: req.requestId
});
} else {
// Return immediate results
res.json({
durations: solviceResponse.data.durations,
distances: solviceResponse.data.distances,
sources: solviceResponse.data.sources,
destinations: solviceResponse.data.destinations,
requestId: req.requestId
});
}
} catch (error) {
throw error;
}
}
start(port = 3000) {
this.app.listen(port, () => {
console.log(`Routing microservice running on port ${port}`);
});
}
}
// Usage
const service = new RoutingMicroservice({
solviceApiKey: process.env.SOLVICE_API_KEY,
solviceBaseUrl: 'https://routing.solvice.io',
redisUrl: process.env.REDIS_URL
});
service.start();
// Event-driven processing with RabbitMQ
const amqp = require('amqplib');
const axios = require('axios');
class RoutingEventProcessor {
constructor(config) {
this.config = config;
this.connection = null;
this.channel = null;
}
async connect() {
this.connection = await amqp.connect(this.config.rabbitmqUrl);
this.channel = await this.connection.createChannel();
// Setup queues
await this.channel.assertQueue('routing.requests', { durable: true });
await this.channel.assertQueue('routing.responses', { durable: true });
await this.channel.assertQueue('routing.errors', { durable: true });
console.log('Connected to RabbitMQ');
}
async processRoutingRequests() {
await this.channel.consume('routing.requests', async (msg) => {
if (msg) {
try {
const request = JSON.parse(msg.content.toString());
console.log('Processing routing request:', request.id);
const result = await this.calculateRoute(request);
// Send success response
await this.channel.sendToQueue(
'routing.responses',
Buffer.from(JSON.stringify({
requestId: request.id,
status: 'completed',
result: result,
timestamp: new Date().toISOString()
})),
{ persistent: true }
);
this.channel.ack(msg);
console.log('Routing request completed:', request.id);
} catch (error) {
console.error('Routing request failed:', error);
// Send error response
await this.channel.sendToQueue(
'routing.errors',
Buffer.from(JSON.stringify({
requestId: JSON.parse(msg.content.toString()).id,
status: 'failed',
error: error.message,
timestamp: new Date().toISOString()
})),
{ persistent: true }
);
this.channel.nack(msg, false, false); // Don't requeue
}
}
});
console.log('Started processing routing requests');
}
async calculateRoute(request) {
const { coordinates, profile, options = {} } = request;
const response = await axios.post(`${this.config.solviceBaseUrl}/route`, {
coordinates,
profile,
steps: options.includeSteps || true,
geometries: 'geojson'
}, {
headers: {
'X-API-Key': this.config.solviceApiKey,
'Content-Type': 'application/json'
},
timeout: 30000 // 30 second timeout
});
const route = response.data.routes[0];
return {
distance: route.distance,
duration: route.duration,
geometry: route.geometry,
steps: options.includeSteps ? route.legs[0].steps : undefined
};
}
async publishRoutingRequest(request) {
const message = {
id: require('uuid').v4(),
...request,
timestamp: new Date().toISOString()
};
await this.channel.sendToQueue(
'routing.requests',
Buffer.from(JSON.stringify(message)),
{ persistent: true }
);
return message.id;
}
async close() {
if (this.channel) {
await this.channel.close();
}
if (this.connection) {
await this.connection.close();
}
}
}
// Usage
const processor = new RoutingEventProcessor({
rabbitmqUrl: process.env.RABBITMQ_URL,
solviceApiKey: process.env.SOLVICE_API_KEY,
solviceBaseUrl: 'https://routing.solvice.io'
});
// Start processing
processor.connect()
.then(() => processor.processRoutingRequests())
.catch(console.error);
// Graceful shutdown
process.on('SIGINT', async () => {
console.log('Shutting down...');
await processor.close();
process.exit(0);
});