Skip to main content
GET
/
v1
/
locations
/
nearby
Find Nearby Locations
curl --request GET \
  --url https://api.bookovia.com/v1/locations/nearby \
  --header 'X-API-Key: <api-key>'
{
  "error": {
    "code": "invalid_coordinates",
    "message": "Invalid latitude or longitude values",
    "details": {
      "latitude": "Must be between -90 and 90",
      "longitude": "Must be between -180 and 180"
    }
  }
}

Documentation Index

Fetch the complete documentation index at: https://docs.bookovia.com/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The Find Nearby Locations endpoint helps you discover relevant points of interest (POIs), services, and amenities near specific coordinates or along a route. This is essential for fleet management, emergency services, and trip planning applications.
Results include real-time availability data for supported services like fuel stations, parking, and rest areas.

Authentication

This endpoint requires authentication via API key in the X-API-Key header. Required permissions: locations:read

Request Parameters

latitude
number
required
Latitude coordinate for the search center (-90 to 90)
longitude
number
required
Longitude coordinate for the search center (-180 to 180)
radius_km
number
default:"5"
Search radius in kilometers (0.1 to 50)
category
string
Filter by category. Options: “fuel”, “parking”, “food”, “lodging”, “medical”, “repair”, “rest_area”, “charging”, “truck_stop”
subcategory
string
Further filter by subcategory (depends on category)
limit
integer
default:"20"
Maximum number of results to return (1 to 100)
include_availability
boolean
default:"true"
Whether to include real-time availability information
include_pricing
boolean
default:"false"
Whether to include pricing information (where available)
vehicle_type
string
Vehicle type for relevant filtering: “car”, “truck”, “motorcycle”, “van”, “bus”
route_trip_id
string
Optional trip ID to find locations along the route instead of radius search
sort_by
string
default:"distance"
Sort results by: “distance”, “rating”, “availability”, “price”

Request Example

curl -X GET "https://api.bookovia.com/v1/locations/nearby?latitude=40.7128&longitude=-74.0060&radius_km=10&category=fuel&include_availability=true" \
  -H "X-API-Key: bkv_test_your_api_key_here"

Response

search_center
object
The coordinates used as the search center
locations
array
Array of nearby locations matching the search criteria
search_metadata
object
Metadata about the search results

Success Response

{
  "search_center": {
    "latitude": 40.7128,
    "longitude": -74.0060,
    "radius_km": 10
  },
  "locations": [
    {
      "location_id": "fuel_station_001",
      "name": "Shell Gas Station",
      "category": "fuel",
      "subcategory": "gas_station",
      "coordinates": {
        "latitude": 40.7145,
        "longitude": -74.0052,
        "address": "456 Broadway, New York, NY 10013"
      },
      "distance_km": 1.2,
      "rating": 4.2,
      "review_count": 127,
      "availability": {
        "status": "open",
        "current_wait_time_minutes": 3,
        "fuel_availability": {
          "regular": "available",
          "premium": "available", 
          "diesel": "available",
          "biodiesel": "limited"
        },
        "pump_count": 12,
        "available_pumps": 8,
        "truck_friendly": true
      },
      "services": [
        "fuel",
        "convenience_store",
        "restrooms",
        "truck_parking",
        "car_wash",
        "atm"
      ],
      "pricing": {
        "regular_price_per_liter": 1.45,
        "premium_price_per_liter": 1.65,
        "diesel_price_per_liter": 1.38,
        "last_updated": "2024-04-13T11:30:00Z"
      },
      "contact_info": {
        "phone": "+1-555-123-4567",
        "website": "https://shell.com/locations/456-broadway",
        "hours": {
          "monday": "24 hours",
          "tuesday": "24 hours", 
          "wednesday": "24 hours",
          "thursday": "24 hours",
          "friday": "24 hours",
          "saturday": "24 hours",
          "sunday": "24 hours"
        }
      },
      "amenities": {
        "parking_spaces": 20,
        "truck_spaces": 6,
        "disabled_access": true,
        "electric_charging": false,
        "wifi": true,
        "food_service": "convenience"
      }
    },
    {
      "location_id": "truck_stop_002",
      "name": "Flying J Travel Center",
      "category": "fuel",
      "subcategory": "truck_stop",
      "coordinates": {
        "latitude": 40.7089,
        "longitude": -74.0105,
        "address": "789 West Side Hwy, New York, NY 10014"
      },
      "distance_km": 2.8,
      "rating": 3.9,
      "review_count": 89,
      "availability": {
        "status": "open",
        "current_wait_time_minutes": 8,
        "fuel_availability": {
          "regular": "available",
          "premium": "available",
          "diesel": "available",
          "def": "available"
        },
        "truck_parking_available": 15,
        "truck_parking_total": 25,
        "shower_availability": "available"
      },
      "services": [
        "fuel",
        "truck_parking",
        "showers",
        "laundry",
        "restaurant",
        "truck_maintenance",
        "weigh_station"
      ],
      "contact_info": {
        "phone": "+1-555-987-6543",
        "website": "https://flyingj.com/locations/789-west-side",
        "hours": {
          "all_days": "24 hours"
        }
      }
    }
  ],
  "search_metadata": {
    "total_found": 15,
    "returned_count": 2,
    "search_time_ms": 145,
    "data_freshness": "real_time",
    "filters_applied": {
      "category": "fuel",
      "radius_km": 10,
      "include_availability": true
    }
  }
}

Error Responses

{
  "error": {
    "code": "invalid_coordinates",
    "message": "Invalid latitude or longitude values",
    "details": {
      "latitude": "Must be between -90 and 90",
      "longitude": "Must be between -180 and 180"
    }
  }
}

SDK Examples

import Bookovia from '@bookovia/javascript-sdk';

const client = new Bookovia('bkv_test_your_api_key');

// Find fuel stations for trucks
const fuelStations = await client.locations.findNearby({
  latitude: 40.7128,
  longitude: -74.0060,
  radius_km: 15,
  category: 'fuel',
  vehicle_type: 'truck',
  include_availability: true,
  include_pricing: true
});

console.log(`Found ${fuelStations.locations.length} fuel stations`);

// Filter for available stations with short wait times
const availableStations = fuelStations.locations.filter(station => {
  return station.availability.status === 'open' && 
         station.availability.current_wait_time_minutes < 10;
});

console.log(`${availableStations.length} stations with short wait times`);

// Find emergency services along a route
const emergencyServices = await client.locations.findNearby({
  route_trip_id: 'trip_1234567890abcdef',
  category: 'medical',
  limit: 50
});

// Group by subcategory
const servicesByType = emergencyServices.locations.reduce((acc, service) => {
  if (!acc[service.subcategory]) acc[service.subcategory] = [];
  acc[service.subcategory].push(service);
  return acc;
}, {});

console.log('Emergency services by type:', Object.keys(servicesByType));

// Real-time location finder with geolocation
const findNearestServices = async (serviceCategory) => {
  if (!navigator.geolocation) {
    throw new Error('Geolocation not supported');
  }
  
  return new Promise((resolve, reject) => {
    navigator.geolocation.getCurrentPosition(async (position) => {
      try {
        const nearby = await client.locations.findNearby({
          latitude: position.coords.latitude,
          longitude: position.coords.longitude,
          radius_km: 5,
          category: serviceCategory,
          sort_by: 'distance',
          limit: 10
        });
        
        resolve(nearby.locations);
      } catch (error) {
        reject(error);
      }
    }, reject);
  });
};

// Usage
const nearestParking = await findNearestServices('parking');
console.log('Nearest parking:', nearestParking[0]?.name);

Use Cases

Emergency Response Planning

// Emergency response location finder
class EmergencyResponseLocator {
  constructor(client) {
    this.client = client;
    this.emergencyTypes = {
      medical: ['hospital', 'clinic', 'urgent_care', 'pharmacy'],
      fire: ['fire_station', 'fire_department'],
      police: ['police_station', 'sheriff'],
      repair: ['tow_service', 'auto_repair', 'tire_service']
    };
  }
  
  async findEmergencyServices(latitude, longitude, emergencyType) {
    const services = await this.client.locations.findNearby({
      latitude,
      longitude,
      radius_km: 25,
      category: 'medical', // Base category
      include_availability: true,
      sort_by: 'distance',
      limit: 20
    });
    
    // Filter by emergency type
    const filteredServices = services.locations.filter(service => {
      return this.emergencyTypes[emergencyType]?.includes(service.subcategory);
    });
    
    // Prioritize by availability and distance
    return filteredServices.map(service => ({
      ...service,
      priority_score: this.calculateEmergencyPriority(service),
      estimated_response_time: this.estimateResponseTime(service.distance_km)
    })).sort((a, b) => b.priority_score - a.priority_score);
  }
  
  calculateEmergencyPriority(service) {
    let score = 100;
    
    // Distance penalty
    score -= service.distance_km * 2;
    
    // Availability bonus
    if (service.availability?.status === 'open') {
      score += 20;
    }
    
    // Rating bonus
    if (service.rating > 4.0) {
      score += 10;
    }
    
    return Math.max(0, score);
  }
  
  estimateResponseTime(distance_km) {
    // Estimate based on emergency vehicle speed (average 60 km/h in city)
    const response_time_minutes = (distance_km / 60) * 60 + 5; // +5min for dispatch
    return Math.round(response_time_minutes);
  }
  
  async generateEmergencyPlan(incident_location, incident_type) {
    const services = await this.findEmergencyServices(
      incident_location.latitude,
      incident_location.longitude,
      incident_type
    );
    
    return {
      incident: {
        location: incident_location,
        type: incident_type,
        timestamp: new Date().toISOString()
      },
      primary_response: services[0] || null,
      backup_options: services.slice(1, 4),
      evacuation_routes: await this.findEvacuationRoutes(incident_location),
      coordination_center: await this.findCoordinationCenter(incident_location)
    };
  }
}

// Usage
const emergencyLocator = new EmergencyResponseLocator(client);

const incident = {
  latitude: 40.7128,
  longitude: -74.0060,
  description: "Vehicle accident on highway"
};

const emergencyPlan = await emergencyLocator.generateEmergencyPlan(incident, 'medical');
console.log('Emergency response plan generated');
console.log(`Primary: ${emergencyPlan.primary_response?.name} (${emergencyPlan.primary_response?.estimated_response_time}min)`);

Fleet Fuel Optimization

# Fleet fuel optimization system
import asyncio
from datetime import datetime, timedelta

class FleetFuelOptimizer:
    def __init__(self, client):
        self.client = client
        self.fuel_thresholds = {
            'critical': 0.15,  # 15% fuel remaining
            'warning': 0.25,   # 25% fuel remaining
            'optimal': 0.35    # 35% fuel remaining
        }
    
    async def optimize_fleet_fuel_stops(self, fleet_status):
        """Optimize fuel stops for entire fleet"""
        optimization_results = {}
        
        for vehicle_id, status in fleet_status.items():
            if status['fuel_level'] <= self.fuel_thresholds['optimal']:
                result = await self.find_optimal_fuel_stop(
                    vehicle_id=vehicle_id,
                    current_position=status['position'],
                    fuel_level=status['fuel_level'],
                    destination=status.get('destination'),
                    fuel_capacity=status['fuel_capacity_liters']
                )
                
                optimization_results[vehicle_id] = result
        
        return optimization_results
    
    async def find_optimal_fuel_stop(self, vehicle_id, current_position, fuel_level, destination=None, fuel_capacity=300):
        """Find the most cost-effective fuel stop"""
        lat, lng = current_position
        
        # Calculate maximum detour distance based on fuel level
        max_detour_km = self.calculate_max_detour(fuel_level, fuel_capacity)
        
        # Find fuel stations within range
        fuel_stations = await self.client.locations.find_nearby(
            latitude=lat,
            longitude=lng,
            radius_km=max_detour_km,
            category='fuel',
            vehicle_type='truck',
            include_availability=True,
            include_pricing=True,
            limit=50
        )
        
        # Score and rank fuel stations
        scored_stations = []
        for station in fuel_stations.locations:
            score = await self.score_fuel_station(
                station, current_position, destination, fuel_level
            )
            scored_stations.append({
                'station': station,
                'score': score,
                'cost_analysis': self.analyze_fuel_costs(station, fuel_capacity, fuel_level)
            })
        
        # Sort by score (higher is better)
        scored_stations.sort(key=lambda x: x['score'], reverse=True)
        
        return {
            'vehicle_id': vehicle_id,
            'current_fuel_level': fuel_level,
            'urgency': self.get_fuel_urgency(fuel_level),
            'recommended_station': scored_stations[0] if scored_stations else None,
            'alternative_options': scored_stations[1:6],  # Top 5 alternatives
            'savings_analysis': self.calculate_potential_savings(scored_stations[:3])
        }
    
    def calculate_max_detour(self, fuel_level, fuel_capacity):
        """Calculate maximum detour distance based on remaining fuel"""
        if fuel_level <= self.fuel_thresholds['critical']:
            return 15  # Emergency - stay close
        elif fuel_level <= self.fuel_thresholds['warning']:
            return 25  # Warning - moderate detour allowed
        else:
            return 50  # Optimal planning - longer detours OK
    
    async def score_fuel_station(self, station, current_position, destination, fuel_level):
        """Score a fuel station based on multiple factors"""
        base_score = 100
        
        # Distance penalty
        distance_penalty = station.distance_km * 2
        base_score -= distance_penalty
        
        # Price bonus (lower prices = higher score)
        if station.pricing and station.pricing.diesel_price_per_liter:
            price = station.pricing.diesel_price_per_liter
            # Normalize price (assume 1.40 is average)
            price_score = (1.60 - price) * 50  # Up to 50 points for good price
            base_score += price_score
        
        # Availability bonus
        if station.availability:
            if station.availability.status == 'open':
                base_score += 20
            
            # Wait time penalty
            wait_time = station.availability.get('current_wait_time_minutes', 0)
            base_score -= wait_time * 0.5
            
            # Truck-friendly bonus
            if station.availability.get('truck_friendly'):
                base_score += 15
        
        # Services bonus
        if 'truck_parking' in station.services:
            base_score += 10
        if 'restrooms' in station.services:
            base_score += 5
        
        # Urgency modifier
        urgency = self.get_fuel_urgency(fuel_level)
        if urgency == 'critical':
            # For critical fuel, heavily weight distance over price
            base_score = 100 - (station.distance_km * 5)
        
        return max(0, base_score)
    
    def get_fuel_urgency(self, fuel_level):
        """Determine fuel urgency level"""
        if fuel_level <= self.fuel_thresholds['critical']:
            return 'critical'
        elif fuel_level <= self.fuel_thresholds['warning']:
            return 'warning'
        else:
            return 'normal'
    
    def analyze_fuel_costs(self, station, fuel_capacity, current_fuel_level):
        """Analyze fuel costs for different fill strategies"""
        if not station.pricing or not station.pricing.diesel_price_per_liter:
            return None
        
        price_per_liter = station.pricing.diesel_price_per_liter
        liters_needed = fuel_capacity * (1.0 - current_fuel_level)
        
        return {
            'price_per_liter': price_per_liter,
            'liters_needed': liters_needed,
            'total_cost': price_per_liter * liters_needed,
            'cost_per_km': price_per_liter / 3.5,  # Assume 3.5 km/L efficiency
        }
    
    def calculate_potential_savings(self, top_stations):
        """Calculate potential savings between stations"""
        if len(top_stations) < 2:
            return None
        
        cheapest = min(top_stations, key=lambda x: x['cost_analysis']['price_per_liter'] if x['cost_analysis'] else float('inf'))
        most_expensive = max(top_stations, key=lambda x: x['cost_analysis']['price_per_liter'] if x['cost_analysis'] else 0)
        
        if cheapest['cost_analysis'] and most_expensive['cost_analysis']:
            price_diff = most_expensive['cost_analysis']['price_per_liter'] - cheapest['cost_analysis']['price_per_liter']
            potential_savings = price_diff * cheapest['cost_analysis']['liters_needed']
            
            return {
                'cheapest_station': cheapest['station'].name,
                'most_expensive_station': most_expensive['station'].name,
                'price_difference_per_liter': price_diff,
                'potential_savings_total': potential_savings
            }
        
        return None
    
    async def generate_fleet_fuel_report(self, fleet_status):
        """Generate comprehensive fleet fuel optimization report"""
        optimization_results = await self.optimize_fleet_fuel_stops(fleet_status)
        
        report = {
            'generated_at': datetime.utcnow().isoformat(),
            'fleet_summary': {
                'total_vehicles': len(fleet_status),
                'vehicles_needing_fuel': len(optimization_results),
                'critical_fuel_vehicles': len([
                    v for v in fleet_status.values() 
                    if v['fuel_level'] <= self.fuel_thresholds['critical']
                ]),
                'potential_total_savings': sum([
                    r.get('savings_analysis', {}).get('potential_savings_total', 0)
                    for r in optimization_results.values()
                ])
            },
            'vehicle_optimizations': optimization_results
        }
        
        return report

# Usage example
optimizer = FleetFuelOptimizer(client)

# Current fleet status
fleet_status = {
    'truck_001': {
        'position': [40.7128, -74.0060],
        'fuel_level': 0.20,  # 20% fuel remaining
        'fuel_capacity_liters': 300,
        'destination': [40.7589, -73.9851]
    },
    'truck_002': {
        'position': [40.6892, -74.0445],
        'fuel_level': 0.35,  # 35% fuel remaining
        'fuel_capacity_liters': 400,
        'destination': [40.7831, -73.9712]
    }
}

# Generate optimization report
fuel_report = await optimizer.generate_fleet_fuel_report(fleet_status)

print(f"Fleet Fuel Report - {fuel_report['fleet_summary']['vehicles_needing_fuel']} vehicles need fuel")
print(f"Potential total savings: ${fuel_report['fleet_summary']['potential_total_savings']:.2f}")

for vehicle_id, optimization in fuel_report['vehicle_optimizations'].items():
    if optimization['recommended_station']:
        station = optimization['recommended_station']['station']
        cost = optimization['recommended_station']['cost_analysis']
        print(f"{vehicle_id}: Refuel at {station.name} - ${cost['total_cost']:.2f}")

Service Route Planning

// Service route planning with location optimization
package main

import (
    "context"
    "fmt"
    "math"
    "sort"
    "time"
    "github.com/bookovia/go-sdk"
)

type ServiceRouteOptimizer struct {
    client *bookovia.Client
}

type ServiceLocation struct {
    ID           string      `json:"id"`
    Type         string      `json:"type"`
    Priority     int         `json:"priority"`
    Coordinates  Coordinates `json:"coordinates"`
    TimeWindow   TimeWindow  `json:"time_window"`
    ServiceTime  int         `json:"service_time_minutes"`
}

type TimeWindow struct {
    Start time.Time `json:"start"`
    End   time.Time `json:"end"`
}

type OptimizedRoute struct {
    RouteID              string                    `json:"route_id"`
    TotalDistance        float64                   `json:"total_distance_km"`
    TotalTime            int                       `json:"total_time_minutes"`
    ServiceLocations     []*ServiceLocation       `json:"service_locations"`
    SupportServices      []*bookovia.NearbyLocation `json:"support_services"`
    OptimizationMetrics  OptimizationMetrics       `json:"optimization_metrics"`
    Recommendations      []string                  `json:"recommendations"`
}

type OptimizationMetrics struct {
    RouteEfficiency     float64 `json:"route_efficiency"`
    TimeWindowCompliance float64 `json:"time_window_compliance"`
    ServiceCoverage     int     `json:"service_coverage"`
    SupportAvailability int     `json:"support_availability"`
}

func NewServiceRouteOptimizer(client *bookovia.Client) *ServiceRouteOptimizer {
    return &ServiceRouteOptimizer{client: client}
}

func (s *ServiceRouteOptimizer) OptimizeServiceRoute(ctx context.Context, serviceLocations []*ServiceLocation, startLocation Coordinates) (*OptimizedRoute, error) {
    // Find support services along the route
    supportServices, err := s.findRouteSupportServices(ctx, serviceLocations, startLocation)
    if err != nil {
        return nil, err
    }
    
    // Optimize the order of service locations
    optimizedOrder := s.optimizeLocationOrder(serviceLocations, startLocation)
    
    // Calculate route metrics
    totalDistance := s.calculateTotalDistance(optimizedOrder, startLocation)
    totalTime := s.calculateTotalTime(optimizedOrder)
    
    // Generate optimization metrics
    metrics := s.calculateOptimizationMetrics(optimizedOrder, supportServices)
    
    // Generate recommendations
    recommendations := s.generateRouteRecommendations(optimizedOrder, supportServices, metrics)
    
    return &OptimizedRoute{
        RouteID:             fmt.Sprintf("route_%d", time.Now().Unix()),
        TotalDistance:       totalDistance,
        TotalTime:           totalTime,
        ServiceLocations:    optimizedOrder,
        SupportServices:     supportServices,
        OptimizationMetrics: metrics,
        Recommendations:     recommendations,
    }, nil
}

func (s *ServiceRouteOptimizer) findRouteSupportServices(ctx context.Context, locations []*ServiceLocation, start Coordinates) ([]*bookovia.NearbyLocation, error) {
    var allServices []*bookovia.NearbyLocation
    
    // Check each service location for nearby support services
    allLocations := append([]*ServiceLocation{{Coordinates: start}}, locations...)
    
    for _, location := range allLocations {
        // Find fuel stations
        fuelStations, err := s.client.Locations.FindNearby(ctx, &bookovia.NearbyLocationRequest{
            Latitude:            location.Coordinates.Latitude,
            Longitude:           location.Coordinates.Longitude,
            RadiusKm:           15,
            Category:           "fuel",
            VehicleType:        "van", // Service vehicles
            IncludeAvailability: true,
            Limit:             5,
        })
        
        if err == nil {
            allServices = append(allServices, fuelStations.Locations...)
        }
        
        // Find repair services
        repairServices, err := s.client.Locations.FindNearby(ctx, &bookovia.NearbyLocationRequest{
            Latitude:            location.Coordinates.Latitude,
            Longitude:           location.Coordinates.Longitude,
            RadiusKm:           25,
            Category:           "repair",
            IncludeAvailability: true,
            Limit:             3,
        })
        
        if err == nil {
            allServices = append(allServices, repairServices.Locations...)
        }
        
        // Find food/rest areas
        restServices, err := s.client.Locations.FindNearby(ctx, &bookovia.NearbyLocationRequest{
            Latitude:  location.Coordinates.Latitude,
            Longitude: location.Coordinates.Longitude,
            RadiusKm: 10,
            Category: "food",
            Limit:    3,
        })
        
        if err == nil {
            allServices = append(allServices, restServices.Locations...)
        }
    }
    
    // Remove duplicates
    return s.deduplicateServices(allServices), nil
}

func (s *ServiceRouteOptimizer) deduplicateServices(services []*bookovia.NearbyLocation) []*bookovia.NearbyLocation {
    seen := make(map[string]bool)
    var unique []*bookovia.NearbyLocation
    
    for _, service := range services {
        if !seen[service.LocationID] {
            seen[service.LocationID] = true
            unique = append(unique, service)
        }
    }
    
    return unique
}

func (s *ServiceRouteOptimizer) optimizeLocationOrder(locations []*ServiceLocation, start Coordinates) []*ServiceLocation {
    if len(locations) <= 1 {
        return locations
    }
    
    // Simple nearest-neighbor optimization with priority weighting
    var optimized []*ServiceLocation
    remaining := make([]*ServiceLocation, len(locations))
    copy(remaining, locations)
    
    currentPos := start
    
    for len(remaining) > 0 {
        bestIndex := 0
        bestScore := s.calculateLocationScore(remaining[0], currentPos)
        
        for i := 1; i < len(remaining); i++ {
            score := s.calculateLocationScore(remaining[i], currentPos)
            if score > bestScore {
                bestScore = score
                bestIndex = i
            }
        }
        
        // Add the best location to the route
        bestLocation := remaining[bestIndex]
        optimized = append(optimized, bestLocation)
        currentPos = bestLocation.Coordinates
        
        // Remove from remaining
        remaining = append(remaining[:bestIndex], remaining[bestIndex+1:]...)
    }
    
    return optimized
}

func (s *ServiceRouteOptimizer) calculateLocationScore(location *ServiceLocation, from Coordinates) float64 {
    distance := s.calculateDistance(from, location.Coordinates)
    
    // Base score inversely proportional to distance
    score := 100.0 - distance
    
    // Priority bonus
    score += float64(location.Priority) * 10
    
    // Time window urgency
    now := time.Now()
    if location.TimeWindow.End.Before(now.Add(2 * time.Hour)) {
        score += 20 // Urgent time window
    }
    
    return score
}

func (s *ServiceRouteOptimizer) calculateDistance(from, to Coordinates) float64 {
    // Simplified Haversine formula
    dlat := (to.Latitude - from.Latitude) * math.Pi / 180
    dlon := (to.Longitude - from.Longitude) * math.Pi / 180
    
    a := math.Sin(dlat/2)*math.Sin(dlat/2) + math.Cos(from.Latitude*math.Pi/180)*math.Cos(to.Latitude*math.Pi/180)*math.Sin(dlon/2)*math.Sin(dlon/2)
    c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
    
    return 6371 * c // Earth radius in km
}

func (s *ServiceRouteOptimizer) calculateTotalDistance(locations []*ServiceLocation, start Coordinates) float64 {
    if len(locations) == 0 {
        return 0
    }
    
    totalDistance := 0.0
    currentPos := start
    
    for _, location := range locations {
        totalDistance += s.calculateDistance(currentPos, location.Coordinates)
        currentPos = location.Coordinates
    }
    
    return totalDistance
}

func (s *ServiceRouteOptimizer) calculateTotalTime(locations []*ServiceLocation) int {
    totalTime := 0
    
    for _, location := range locations {
        totalTime += location.ServiceTime
        totalTime += 15 // Travel time buffer between locations
    }
    
    return totalTime
}

func (s *ServiceRouteOptimizer) calculateOptimizationMetrics(locations []*ServiceLocation, supportServices []*bookovia.NearbyLocation) OptimizationMetrics {
    // Route efficiency (simplified)
    efficiency := 85.0 // Base efficiency
    if len(locations) > 8 {
        efficiency -= float64(len(locations)-8) * 2 // Penalty for too many stops
    }
    
    // Time window compliance
    compliance := 90.0
    now := time.Now()
    for _, location := range locations {
        if location.TimeWindow.End.Before(now) {
            compliance -= 10.0 // Penalty for missed windows
        }
    }
    
    // Service coverage (count of service locations)
    serviceCoverage := len(locations)
    
    // Support availability (count of nearby support services)
    supportAvailability := len(supportServices)
    
    return OptimizationMetrics{
        RouteEfficiency:      efficiency,
        TimeWindowCompliance: math.Max(0, compliance),
        ServiceCoverage:      serviceCoverage,
        SupportAvailability:  supportAvailability,
    }
}

func (s *ServiceRouteOptimizer) generateRouteRecommendations(locations []*ServiceLocation, supportServices []*bookovia.NearbyLocation, metrics OptimizationMetrics) []string {
    var recommendations []string
    
    // Efficiency recommendations
    if metrics.RouteEfficiency < 75 {
        recommendations = append(recommendations, "Consider reducing the number of stops or optimizing the route order")
    }
    
    // Time window recommendations
    if metrics.TimeWindowCompliance < 80 {
        recommendations = append(recommendations, "Some service windows may be missed - consider rescheduling or prioritizing urgent appointments")
    }
    
    // Support service recommendations
    fuelStations := 0
    repairServices := 0
    for _, service := range supportServices {
        switch service.Category {
        case "fuel":
            fuelStations++
        case "repair":
            repairServices++
        }
    }
    
    if fuelStations < 2 {
        recommendations = append(recommendations, "Limited fuel stations along route - ensure adequate fuel before departure")
    }
    
    if repairServices < 1 {
        recommendations = append(recommendations, "No repair services found along route - consider carrying emergency repair kit")
    }
    
    // Route optimization recommendations
    if len(locations) > 6 {
        recommendations = append(recommendations, "Consider splitting into multiple routes for better efficiency")
    }
    
    return recommendations
}

// Generate comprehensive service route analysis
func (s *ServiceRouteOptimizer) AnalyzeServiceRoutes(ctx context.Context, routes map[string][]*ServiceLocation) (*ServiceRouteAnalysis, error) {
    analysis := &ServiceRouteAnalysis{
        GeneratedAt: time.Now(),
        RouteCount:  len(routes),
        Routes:      make(map[string]*OptimizedRoute),
    }
    
    totalDistance := 0.0
    totalLocations := 0
    
    for routeID, locations := range routes {
        // Use first location as start point (simplified)
        startLocation := Coordinates{Latitude: 40.7128, Longitude: -74.0060}
        
        optimizedRoute, err := s.OptimizeServiceRoute(ctx, locations, startLocation)
        if err != nil {
            continue
        }
        
        optimizedRoute.RouteID = routeID
        analysis.Routes[routeID] = optimizedRoute
        
        totalDistance += optimizedRoute.TotalDistance
        totalLocations += len(optimizedRoute.ServiceLocations)
    }
    
    analysis.FleetMetrics = FleetMetrics{
        TotalDistance:        totalDistance,
        TotalServiceLocations: totalLocations,
        AverageRouteDistance: totalDistance / float64(len(routes)),
        AverageEfficiency:    s.calculateFleetEfficiency(analysis.Routes),
    }
    
    return analysis, nil
}

func (s *ServiceRouteOptimizer) calculateFleetEfficiency(routes map[string]*OptimizedRoute) float64 {
    if len(routes) == 0 {
        return 0
    }
    
    totalEfficiency := 0.0
    for _, route := range routes {
        totalEfficiency += route.OptimizationMetrics.RouteEfficiency
    }
    
    return totalEfficiency / float64(len(routes))
}

type ServiceRouteAnalysis struct {
    GeneratedAt  time.Time                    `json:"generated_at"`
    RouteCount   int                         `json:"route_count"`
    Routes       map[string]*OptimizedRoute  `json:"routes"`
    FleetMetrics FleetMetrics                `json:"fleet_metrics"`
}

type FleetMetrics struct {
    TotalDistance         float64 `json:"total_distance"`
    TotalServiceLocations int     `json:"total_service_locations"`
    AverageRouteDistance  float64 `json:"average_route_distance"`
    AverageEfficiency     float64 `json:"average_efficiency"`
}

func main() {
    client := bookovia.NewClient("bkv_test_your_api_key")
    optimizer := NewServiceRouteOptimizer(client)
    
    ctx := context.Background()
    
    // Example service locations
    serviceLocations := []*ServiceLocation{
        {
            ID:          "service_001",
            Type:        "maintenance",
            Priority:    3,
            Coordinates: Coordinates{40.7298, -73.9942},
            TimeWindow:  TimeWindow{Start: time.Now().Add(time.Hour), End: time.Now().Add(3 * time.Hour)},
            ServiceTime: 45,
        },
        {
            ID:          "service_002",
            Type:        "delivery",
            Priority:    5,
            Coordinates: Coordinates{40.7589, -73.9851},
            TimeWindow:  TimeWindow{Start: time.Now().Add(2 * time.Hour), End: time.Now().Add(4 * time.Hour)},
            ServiceTime: 30,
        },
    }
    
    startLocation := Coordinates{40.7128, -74.0060}
    
    optimizedRoute, err := optimizer.OptimizeServiceRoute(ctx, serviceLocations, startLocation)
    if err != nil {
        fmt.Printf("Error optimizing route: %v\n", err)
        return
    }
    
    fmt.Printf("Optimized Service Route:\n")
    fmt.Printf("Total Distance: %.1f km\n", optimizedRoute.TotalDistance)
    fmt.Printf("Total Time: %d minutes\n", optimizedRoute.TotalTime)
    fmt.Printf("Route Efficiency: %.1f%%\n", optimizedRoute.OptimizationMetrics.RouteEfficiency)
    fmt.Printf("Support Services Available: %d\n", optimizedRoute.OptimizationMetrics.SupportAvailability)
    
    fmt.Println("\nRecommendations:")
    for _, rec := range optimizedRoute.Recommendations {
        fmt.Printf("- %s\n", rec)
    }
}

Best Practices

Search Optimization

  • Use smaller radii (1-5km) for dense urban areas
  • Expand radius (10-50km) for rural or highway locations
  • Adjust radius based on vehicle type and service urgency
// Efficient multi-category search
const categories = ['fuel', 'food', 'repair'];
const promises = categories.map(category => 
  client.locations.findNearby({
    latitude, longitude, radius_km: 10,
    category, limit: 10
  })
);

const results = await Promise.all(promises);
const allLocations = results.flatMap(r => r.locations);

Real-time Integration

  • Cache availability data with TTL (5-15 minutes)
  • Implement fallback for unavailable real-time data
  • Use WebSocket connections for continuous updates
  • Use route_trip_id parameter for route-based searches
  • Combine with trip tracking for dynamic service discovery
  • Update service recommendations based on route changes

Next Steps

Safety Analytics

Analyze safety risks and events near locations

Fleet Management

Integrate location services with fleet operations

Real-time Streaming

Set up live location and service updates

Route Optimization

Advanced route planning with service integration