Solvice Maps: Integration Examples and Patterns
Integration Overview
This guide provides practical examples and integration patterns for implementing Solvice Maps in various applications and use cases. Each example includes complete code samples, best practices, and performance considerations.Quick Start Integration
1. Basic Route Calculation
Use Case: Simple A-to-B navigation with turn-by-turn directions.Copy
// 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}`);
});
2. Distance Matrix for Multiple Locations
Use Case: Calculate travel times between delivery locations and warehouses.Copy
# 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 Integration Patterns
1. Real-Time Delivery Optimization
Use Case: Dynamic route optimization for delivery fleet with real-time updates.Copy
// 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);
});
2. Time-Dependent Route Planning
Use Case: Plan routes considering traffic patterns throughout the day.Copy
# 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)")
3. React Web Application Integration
Use Case: Interactive web application with map visualization.Copy
// 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;
4. Mobile Application Integration (React Native)
Use Case: Mobile navigation app with offline capability.Copy
// 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;
Enterprise Integration Patterns
1. Microservices Architecture Integration
Use Case: Integrate routing service into existing microservices architecture.Copy
// 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();
2. Event-Driven Architecture
Use Case: Process routing requests through message queues.Copy
// 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);
});
Best Practices Summary
1. Performance Optimization
- Cache frequently requested routes using content-based keys
- Use appropriate endpoints (sync vs async) based on request size
- Implement request batching for multiple similar requests
- Set reasonable timeouts for external API calls
2. Error Handling
- Implement retry logic with exponential backoff
- Handle rate limits gracefully with queueing
- Provide fallback mechanisms for offline scenarios
- Log errors comprehensively for debugging
3. Security
- Store API keys securely using environment variables or secret management
- Validate all input data before sending to API
- Implement rate limiting in your application layer
- Use HTTPS for all API communications
4. Monitoring
- Track API usage and costs
- Monitor response times and error rates
- Set up alerts for service degradation
- Log request/response data for analysis
5. Scalability
- Design stateless services for horizontal scaling
- Use event-driven patterns for heavy processing
- Implement circuit breakers for external service protection
- Cache results appropriately to reduce API calls