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.
// 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.
# 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.
// 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.
# 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.
// 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='&copy; <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.
// 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.
// 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.
// 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
These integration examples provide a solid foundation for implementing Solvice Maps in various application architectures and use cases.