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
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
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:
const config = {
apiKey: 'YOUR_API_KEY',
mapTypeId: 'roadmap', // 'roadmap' | 'satellite' | 'hybrid' | 'terrain'
};Map Styles
Customize map colors and features:
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:
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:
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
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
<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
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:
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:
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:
// 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:
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:
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
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.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_keyKey Restrictions
Secure your API keys with restrictions:
Application restrictions
- HTTP referrers (websites)
- IP addresses (servers)
- Android apps
- iOS apps
API restrictions
- Limit to specific APIs:
- Maps JavaScript API
- Directions API
- Route Optimization API
- Limit to specific APIs:
Loading Keys Dynamically
// 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
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
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:
// 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:
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:
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:
// ✅ Good - Initialize once
const routeMap = new RouteMap({ apiKey });
await routeMap.initialize(element);
// ❌ Bad - Multiple initializations
routeMap.initialize(element);
routeMap.initialize(element); // Don't do this2. Clean Up Resources
Always destroy when done:
// 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:
if (routeMap.isReady()) {
routeMap.renderRoute(route);
}4. Handle Errors Gracefully
Always provide error handlers:
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
- Learn about Type System
- Explore Customization options
- Check out Performance tips