Skip to content

Map Adapters ​

Learn about the flexible map adapter system that allows you to use different mapping providers with the Route Optimization Map library.

Overview ​

The library uses an adapter pattern to support multiple mapping providers. Currently, Google Maps is the primary supported provider, with the architecture designed to support additional providers in the future.

Current Adapter: Google Maps ​

The Google Maps adapter is built-in and provides full integration with the Google Maps JavaScript API.

Configuration ​

typescript
import { RouteMap } from '@route-optimization/vanilla';

const routeMap = new RouteMap({
  apiKey: 'YOUR_GOOGLE_MAPS_API_KEY',
  center: { lat: 13.7563, lng: 100.5018 },
  zoom: 12,
});

Google Maps API Features Used ​

The library leverages these Google Maps features:

  • Maps JavaScript API - Core map rendering
  • Directions API - Route calculation and polylines
  • Markers API - Stop and waypoint visualization
  • Geocoding API - Address to coordinates conversion
  • Places API - Location search and autocomplete
  • Route Optimization API - Multi-stop route optimization

Map Configuration ​

Basic Configuration ​

typescript
interface MapConfig {
  apiKey: string;
  center?: LatLng;
  zoom?: number;
  mapTypeId?: string;
  styles?: google.maps.MapTypeStyle[];
  restriction?: google.maps.MapRestriction;
  gestureHandling?: 'cooperative' | 'greedy' | 'none' | 'auto';
}

Map Type ​

Control the map appearance:

typescript
const config = {
  apiKey: 'YOUR_API_KEY',
  mapTypeId: 'roadmap', // 'roadmap' | 'satellite' | 'hybrid' | 'terrain'
};

Map Styles ​

Customize map colors and features:

typescript
const customStyles = [
  {
    featureType: 'water',
    elementType: 'geometry',
    stylers: [{ color: '#193341' }],
  },
  {
    featureType: 'road',
    elementType: 'geometry',
    stylers: [{ color: '#2c3e50' }],
  },
];

const config = {
  apiKey: 'YOUR_API_KEY',
  styles: customStyles,
};

Map Restrictions ​

Limit the viewable area:

typescript
const config = {
  apiKey: 'YOUR_API_KEY',
  restriction: {
    latLngBounds: {
      north: 20.0,
      south: 5.0,
      east: 106.0,
      west: 97.0,
    },
    strictBounds: true,
  },
};

Gesture Handling ​

Control user interaction:

typescript
const config = {
  apiKey: 'YOUR_API_KEY',
  gestureHandling: 'cooperative', // Require Ctrl+scroll to zoom
};

Advanced Google Maps Integration ​

Using Google Maps Directly ​

Access the underlying Google Maps instance:

React ​

tsx
import { useRouteMap } from '@route-optimization/react';

function MapComponent() {
  const { map, isReady } = useRouteMap({
    apiKey: 'YOUR_API_KEY',
  });

  useEffect(() => {
    if (isReady && map) {
      // Access native Google Maps API
      map.setMapTypeId('satellite');

      // Add custom controls
      const controlDiv = document.createElement('div');
      controlDiv.innerHTML = '<button>Custom Control</button>';
      map.controls[google.maps.ControlPosition.TOP_CENTER].push(controlDiv);
    }
  }, [isReady, map]);

  return <div ref={mapRef} style={{ width: '100%', height: '600px' }} />;
}

Vue ​

vue
<script setup lang="ts">
import { watch } from 'vue';
import { useRouteMap } from '@route-optimization/vue';

const mapRef = ref<HTMLDivElement>();
const { map, isReady } = useRouteMap({
  apiKey: 'YOUR_API_KEY',
  mapElement: mapRef,
});

watch(isReady, (ready) => {
  if (ready && map.value) {
    // Access native Google Maps API
    map.value.setMapTypeId('satellite');
  }
});
</script>

Vanilla JS ​

javascript
import { RouteMap } from '@route-optimization/vanilla';

const routeMap = new RouteMap({ apiKey: 'YOUR_API_KEY' });

await routeMap.initialize(mapElement);

// Access the map instance
const googleMap = routeMap.getMap();
googleMap.setMapTypeId('satellite');

Custom Markers ​

Override default marker appearance:

typescript
const route = {
  stops: [...],
  options: {
    markerOptions: {
      startMarker: {
        icon: {
          url: '/icons/start.png',
          scaledSize: { width: 32, height: 32 },
          anchor: { x: 16, y: 32 },
        },
        label: {
          text: 'START',
          color: '#FFFFFF',
          fontSize: '14px',
          fontWeight: 'bold',
        },
      },
      endMarker: {
        icon: '/icons/end.png',
        label: 'END',
      },
      stopMarker: {
        icon: '/icons/stop.png',
        label: (index) => `${index + 1}`,
      },
    },
  },
};

Custom Polylines ​

Customize route lines:

typescript
const route = {
  stops: [...],
  options: {
    polylineOptions: {
      strokeColor: '#FF0000',
      strokeOpacity: 0.8,
      strokeWeight: 6,
      geodesic: true,
      icons: [{
        icon: {
          path: google.maps.SymbolPath.FORWARD_CLOSED_ARROW,
          scale: 2,
        },
        offset: '100%',
        repeat: '100px',
      }],
    },
  },
};

Info Windows ​

Add popup information windows:

typescript
// React example
const { map } = useRouteMap({ apiKey: 'YOUR_API_KEY' });

useEffect(() => {
  if (map && isReady) {
    const infoWindow = new google.maps.InfoWindow();

    // Add click listener to markers
    map.data.addListener('click', (event) => {
      infoWindow.setContent(`
        <div>
          <h3>Stop Information</h3>
          <p>${event.feature.getProperty('name')}</p>
        </div>
      `);
      infoWindow.setPosition(event.latLng);
      infoWindow.open(map);
    });
  }
}, [map, isReady]);

Future Adapters ​

The library is designed to support additional mapping providers:

Planned Adapters ​

  • Mapbox - Modern, customizable maps
  • Leaflet - Open-source mapping
  • Azure Maps - Microsoft Azure integration
  • HERE Maps - Enterprise mapping solution

Adapter Interface ​

When implementing custom adapters, follow this interface:

typescript
interface MapAdapter {
  initialize(element: HTMLElement, config: MapConfig): Promise<void>;
  destroy(): void;

  renderRoute(route: Route, options?: RouteOptions): void;
  clearRoute(): void;

  setCenter(location: LatLng): void;
  getCenter(): LatLng | null;

  setZoom(zoom: number): void;
  getZoom(): number | null;

  fitBounds(bounds: MapBounds): void;
  getBounds(): MapBounds | null;

  addMarker(location: LatLng, options?: MarkerOptions): void;
  removeMarker(id: string): void;
  clearMarkers(): void;
}

Custom Adapter Implementation ​

Creating a Custom Adapter ​

If you need to support a different mapping provider:

typescript
import { MapAdapter, MapConfig, Route } from '@route-optimization/core';

export class CustomMapAdapter implements MapAdapter {
  private map: any;

  async initialize(element: HTMLElement, config: MapConfig): Promise<void> {
    // Initialize your mapping provider
    this.map = await loadCustomMap(element, config);
  }

  destroy(): void {
    // Cleanup
    this.map?.remove();
  }

  renderRoute(route: Route, options?: RouteOptions): void {
    // Render route on your map
    const coordinates = route.stops.map((stop) => [stop.location.lng, stop.location.lat]);

    this.map.addRoute(coordinates, options);
  }

  clearRoute(): void {
    this.map.clearRoutes();
  }

  setCenter(location: LatLng): void {
    this.map.setCenter([location.lng, location.lat]);
  }

  getCenter(): LatLng | null {
    const center = this.map.getCenter();
    return center ? { lat: center.lat, lng: center.lng } : null;
  }

  // Implement remaining methods...
}

Using a Custom Adapter ​

typescript
import { RouteMap } from '@route-optimization/vanilla';
import { CustomMapAdapter } from './CustomMapAdapter';

const routeMap = new RouteMap({
  adapter: new CustomMapAdapter(),
  // adapter-specific config
});

API Key Management ​

Environment Variables ​

Store API keys securely:

env
# .env.local
VITE_GOOGLE_MAPS_API_KEY=your_api_key
REACT_APP_GOOGLE_MAPS_API_KEY=your_api_key
NUXT_PUBLIC_GOOGLE_MAPS_API_KEY=your_api_key

Key Restrictions ​

Secure your API keys with restrictions:

  1. Application restrictions

    • HTTP referrers (websites)
    • IP addresses (servers)
    • Android apps
    • iOS apps
  2. API restrictions

    • Limit to specific APIs:
      • Maps JavaScript API
      • Directions API
      • Route Optimization API

Loading Keys Dynamically ​

typescript
// React
const apiKey =
  import.meta.env.VITE_GOOGLE_MAPS_API_KEY || process.env.REACT_APP_GOOGLE_MAPS_API_KEY;

// Vue
const apiKey = import.meta.env.VITE_GOOGLE_MAPS_API_KEY;

// Vanilla JS
const apiKey = process.env.GOOGLE_MAPS_API_KEY;

Error Handling ​

Common Map Errors ​

typescript
const routeMap = new RouteMap({
  apiKey: 'YOUR_API_KEY',
  onError: (error) => {
    switch (error.code) {
      case 'INVALID_API_KEY':
        console.error('Invalid API key');
        break;
      case 'OVER_QUERY_LIMIT':
        console.error('API quota exceeded');
        break;
      case 'REQUEST_DENIED':
        console.error('Request denied - check API restrictions');
        break;
      default:
        console.error('Map error:', error);
    }
  },
});

Retry Logic ​

typescript
async function initializeWithRetry(routeMap: RouteMap, element: HTMLElement, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      await routeMap.initialize(element);
      return;
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      await new Promise((resolve) => setTimeout(resolve, 1000 * (i + 1)));
    }
  }
}

Performance Optimization ​

Lazy Loading ​

Load map libraries only when needed:

typescript
// React lazy loading
import { lazy, Suspense } from 'react';

const MapComponent = lazy(() => import('./MapComponent'));

function App() {
  return (
    <Suspense fallback={<div>Loading map...</div>}>
      <MapComponent />
    </Suspense>
  );
}

Marker Clustering ​

For many markers, use clustering:

typescript
import { MarkerClusterer } from '@googlemaps/markerclusterer';

const { map } = useRouteMap({ apiKey: 'YOUR_API_KEY' });

useEffect(() => {
  if (map && markers.length > 100) {
    new MarkerClusterer({
      map,
      markers,
      algorithm: new SuperClusterAlgorithm({ radius: 200 }),
    });
  }
}, [map, markers]);

Viewport-Based Rendering ​

Only render visible elements:

typescript
const { bounds } = useMapControls(map);

const visibleStops = useMemo(() => {
  if (!bounds) return stops;

  return stops.filter((stop) => {
    const { lat, lng } = stop.location;
    return lat >= bounds.south && lat <= bounds.north && lng >= bounds.west && lng <= bounds.east;
  });
}, [stops, bounds]);

Best Practices ​

1. Initialize Once ​

Avoid multiple initializations:

typescript
// ✅ Good - Initialize once
const routeMap = new RouteMap({ apiKey });
await routeMap.initialize(element);

// ❌ Bad - Multiple initializations
routeMap.initialize(element);
routeMap.initialize(element); // Don't do this

2. Clean Up Resources ​

Always destroy when done:

typescript
// React
useEffect(() => {
  return () => {
    routeMap.destroy();
  };
}, []);

// Vue
onUnmounted(() => {
  routeMap.destroy();
});

// Vanilla JS
window.addEventListener('beforeunload', () => {
  routeMap.destroy();
});

3. Check Ready State ​

Verify map is ready before operations:

typescript
if (routeMap.isReady()) {
  routeMap.renderRoute(route);
}

4. Handle Errors Gracefully ​

Always provide error handlers:

typescript
const routeMap = new RouteMap({
  apiKey: 'YOUR_API_KEY',
  onError: (error) => {
    // Log to monitoring service
    logError(error);
    // Show user-friendly message
    showNotification('Map failed to load. Please refresh.');
  },
});

Next Steps ​

Released under the MIT License.