Skip to content

Customization

Learn how to customize the appearance and behavior of your route optimization maps.

Overview

The library provides extensive customization options for:

  • Map appearance and styles
  • Route polylines
  • Markers and labels
  • Optimization behavior
  • Event handling

Map Customization

Map Styles

Apply custom styles to your map:

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

const config: MapConfig = {
  apiKey: 'YOUR_API_KEY',
  center: { lat: 13.7563, lng: 100.5018 },
  zoom: 12,
  styles: [
    {
      featureType: 'water',
      elementType: 'geometry',
      stylers: [{ color: '#a2daf2' }],
    },
    {
      featureType: 'road',
      elementType: 'geometry',
      stylers: [{ color: '#ffffff' }, { visibility: 'on' }],
    },
    {
      featureType: 'poi',
      stylers: [{ visibility: 'off' }],
    },
  ],
};

Dark Mode

Create a dark mode map:

typescript
const darkModeStyles: google.maps.MapTypeStyle[] = [
  { elementType: 'geometry', stylers: [{ color: '#242f3e' }] },
  { elementType: 'labels.text.stroke', stylers: [{ color: '#242f3e' }] },
  { elementType: 'labels.text.fill', stylers: [{ color: '#746855' }] },
  {
    featureType: 'water',
    elementType: 'geometry',
    stylers: [{ color: '#17263c' }],
  },
  {
    featureType: 'road',
    elementType: 'geometry',
    stylers: [{ color: '#38414e' }],
  },
  {
    featureType: 'road',
    elementType: 'labels.text.fill',
    stylers: [{ color: '#9ca5b3' }],
  },
];

const darkConfig: MapConfig = {
  apiKey: 'YOUR_API_KEY',
  styles: darkModeStyles,
};

Map Type

Set different map types:

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

Gesture Handling

Control user interactions:

typescript
const config: MapConfig = {
  apiKey: 'YOUR_API_KEY',
  gestureHandling: 'cooperative', // Requires Ctrl+scroll to zoom
  // Options: 'cooperative' | 'greedy' | 'none' | 'auto'
};

Map Restrictions

Restrict the map to specific regions:

typescript
const config: MapConfig = {
  apiKey: 'YOUR_API_KEY',
  restriction: {
    latLngBounds: {
      north: 20.5,
      south: 5.5,
      east: 106.0,
      west: 97.0,
    },
    strictBounds: false,
  },
};

Route Customization

Polyline Styling

Customize route appearance:

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

const route: Route = {
  stops: [
    { id: '1', location: { lat: 13.7563, lng: 100.5018 } },
    { id: '2', location: { lat: 13.7467, lng: 100.5352 } },
  ],
  options: {
    polylineOptions: {
      strokeColor: '#FF6B6B',
      strokeOpacity: 1.0,
      strokeWeight: 6,
      geodesic: true,
    },
  },
};

Animated Routes

Create animated routes with gradient colors:

typescript
const animatedRoute: Route = {
  stops: [...],
  options: {
    polylineOptions: {
      strokeColor: '#4ECDC4',
      strokeOpacity: 0.8,
      strokeWeight: 8,
      geodesic: true,
      icons: [
        {
          icon: {
            path: google.maps.SymbolPath.FORWARD_CLOSED_ARROW,
            scale: 3,
            strokeColor: '#FFFFFF',
          },
          offset: '100%',
          repeat: '50px',
        },
      ],
    },
  },
};

Multi-Route Styling

Different styles for different routes:

typescript
const routes = {
  express: {
    stops: [...],
    options: {
      polylineOptions: {
        strokeColor: '#FF0000',
        strokeWeight: 8,
      },
    },
  },
  standard: {
    stops: [...],
    options: {
      polylineOptions: {
        strokeColor: '#0000FF',
        strokeWeight: 5,
      },
    },
  },
  economy: {
    stops: [...],
    options: {
      polylineOptions: {
        strokeColor: '#00FF00',
        strokeWeight: 3,
      },
    },
  },
};

Marker Customization

Custom Marker Icons

Use custom images for markers:

typescript
const route: Route = {
  stops: [...],
  options: {
    markerOptions: {
      startMarker: {
        icon: {
          url: '/icons/start-pin.png',
          scaledSize: new google.maps.Size(40, 40),
          anchor: new google.maps.Point(20, 40),
        },
      },
      endMarker: {
        icon: {
          url: '/icons/end-pin.png',
          scaledSize: new google.maps.Size(40, 40),
          anchor: new google.maps.Point(20, 40),
        },
      },
      stopMarker: {
        icon: {
          url: '/icons/stop-pin.png',
          scaledSize: new google.maps.Size(30, 30),
          anchor: new google.maps.Point(15, 30),
        },
      },
    },
  },
};

SVG Markers

Use SVG for scalable markers:

typescript
const svgMarker = {
  path: 'M 0,0 C -2,-20 -10,-22 -10,-30 A 10,10 0 1,1 10,-30 C 10,-22 2,-20 0,0 z',
  fillColor: '#FF6B6B',
  fillOpacity: 1,
  strokeColor: '#000000',
  strokeWeight: 2,
  scale: 1,
};

const route: Route = {
  stops: [...],
  options: {
    markerOptions: {
      stopMarker: {
        icon: svgMarker,
      },
    },
  },
};

Marker Labels

Add custom labels to markers:

typescript
const route: Route = {
  stops: [...],
  options: {
    markerOptions: {
      showLabels: true,
      labelColor: '#FFFFFF',
      startMarker: {
        label: {
          text: 'START',
          color: '#FFFFFF',
          fontWeight: 'bold',
          fontSize: '14px',
        },
      },
      endMarker: {
        label: {
          text: 'END',
          color: '#FFFFFF',
          fontWeight: 'bold',
          fontSize: '14px',
        },
      },
    },
  },
};

Dynamic Marker Colors

Color-code markers by status:

typescript
function getMarkerColor(status: string): string {
  const colors = {
    pending: '#FFA500',
    inProgress: '#4CAF50',
    completed: '#2196F3',
    failed: '#F44336',
  };
  return colors[status] || '#9E9E9E';
}

const stops: Stop[] = [
  {
    id: '1',
    location: { lat: 13.7563, lng: 100.5018 },
    metadata: { status: 'completed' },
  },
  {
    id: '2',
    location: { lat: 13.7467, lng: 100.5352 },
    metadata: { status: 'inProgress' },
  },
];

// React example
stops.forEach((stop) => {
  addMarker(stop.location, {
    icon: {
      path: google.maps.SymbolPath.CIRCLE,
      scale: 10,
      fillColor: getMarkerColor(stop.metadata.status),
      fillOpacity: 1,
      strokeColor: '#FFFFFF',
      strokeWeight: 2,
    },
  });
});

Optimization Customization

Search Mode

Control optimization behavior:

typescript
import { OptimizationRequest } from '@route-optimization/core';

const request: OptimizationRequest = {
  shipments: [...],
  vehicles: [...],
  searchMode: 'RETURN_FAST', // Fast results
  // or
  searchMode: 'CONSUME_ALL_AVAILABLE_TIME', // Best results
};

Time Windows

Set delivery time constraints:

typescript
const request: OptimizationRequest = {
  shipments: [
    {
      id: 'delivery-1',
      deliveryLocation: { latitude: 13.7563, longitude: 100.5018 },
      deliveryTimeWindows: [
        {
          startTime: '2025-12-12T09:00:00Z',
          endTime: '2025-12-12T12:00:00Z',
        },
      ],
    },
    {
      id: 'delivery-2',
      deliveryLocation: { latitude: 13.7467, longitude: 100.5352 },
      deliveryTimeWindows: [
        {
          startTime: '2025-12-12T14:00:00Z',
          endTime: '2025-12-12T17:00:00Z',
        },
      ],
    },
  ],
  vehicles: [...],
};

Vehicle Constraints

Configure vehicle capacity and costs:

typescript
const request: OptimizationRequest = {
  shipments: [...],
  vehicles: [
    {
      id: 'truck-1',
      loadLimits: [
        {
          type: 'weight',
          maxLoad: 1000, // kg
        },
        {
          type: 'volume',
          maxLoad: 50, // cubic meters
        },
      ],
      costPerKilometer: 2.5,
      costPerHour: 25,
      travelMode: 'DRIVING',
    },
    {
      id: 'bike-1',
      loadLimits: [
        {
          type: 'weight',
          maxLoad: 20,
        },
      ],
      costPerKilometer: 0.5,
      costPerHour: 10,
      travelMode: 'BICYCLING',
    },
  ],
};

Custom Cost Functions

Implement custom cost calculations:

typescript
interface CustomVehicle extends OptimizationVehicle {
  customCostFactor?: number;
}

function calculateCustomCost(distance: number, duration: number, vehicle: CustomVehicle): number {
  const baseCost =
    distance * (vehicle.costPerKilometer || 0) + duration * (vehicle.costPerHour || 0);

  const customFactor = vehicle.customCostFactor || 1;

  return baseCost * customFactor;
}

Framework-Specific Customization

React Customization

Custom Hooks

Create reusable custom hooks:

typescript
import { useRouteMap, useRouteOptimization } from '@route-optimization/react';
import { useState, useCallback } from 'react';

function useOptimizedRouteMap(config: MapConfig) {
  const { mapRef, isReady, renderRoute, clearRoute } = useRouteMap(config);
  const { optimize, data, isLoading } = useRouteOptimization({
    apiKey: config.apiKey,
  });

  const [currentRoute, setCurrentRoute] = useState<Route | null>(null);

  const optimizeAndRender = useCallback(
    async (request: OptimizationRequest) => {
      clearRoute();
      const result = await optimize(request);

      if (result.success && result.routes?.[0]) {
        const route = convertToRoute(result.routes[0]);
        setCurrentRoute(route);
        renderRoute(route);
      }
    },
    [optimize, renderRoute, clearRoute]
  );

  return {
    mapRef,
    isReady,
    isLoading,
    currentRoute,
    optimizeAndRender,
    clearRoute,
  };
}

Styled Components

Integrate with styling libraries:

typescript
import styled from 'styled-components';

const MapContainer = styled.div`
  width: 100%;
  height: 600px;
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);

  @media (max-width: 768px) {
    height: 400px;
  }
`;

function StyledMapComponent() {
  const { mapRef } = useRouteMap({ apiKey: 'YOUR_API_KEY' });

  return <MapContainer ref={mapRef} />;
}

Vue Customization

Custom Composables

Create reusable composables:

typescript
import { useRouteMap, useRouteOptimization } from '@route-optimization/vue';
import { ref, computed, type Ref } from 'vue';

export function useOptimizedRouteMap(
  mapElement: Ref<HTMLDivElement | undefined>,
  config: MapConfig
) {
  const { isReady, renderRoute, clearRoute } = useRouteMap({
    mapElement,
    ...config,
  });

  const { optimize, data, isLoading } = useRouteOptimization({
    apiKey: config.apiKey,
  });

  const currentRoute = ref<Route | null>(null);

  const optimizeAndRender = async (request: OptimizationRequest) => {
    clearRoute();
    const result = await optimize(request);

    if (result.success && result.routes?.[0]) {
      const route = convertToRoute(result.routes[0]);
      currentRoute.value = route;
      renderRoute(route);
    }
  };

  return {
    isReady,
    isLoading,
    currentRoute: computed(() => currentRoute.value),
    optimizeAndRender,
    clearRoute,
  };
}

Scoped Styles

Use scoped styles in Vue components:

vue
<template>
  <div class="map-wrapper">
    <div ref="mapElement" class="map-container" />
  </div>
</template>

<style scoped>
.map-wrapper {
  padding: 20px;
  background: #f5f5f5;
}

.map-container {
  width: 100%;
  height: 600px;
  border-radius: 12px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

@media (max-width: 768px) {
  .map-container {
    height: 400px;
  }
}
</style>

Vanilla Customization

Custom Classes

Extend base classes:

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

class CustomRouteMap extends RouteMap {
  private theme: 'light' | 'dark';

  constructor(config: RouteMapConfig, theme: 'light' | 'dark' = 'light') {
    super({
      ...config,
      styles: theme === 'dark' ? darkModeStyles : config.styles,
    });
    this.theme = theme;
  }

  setTheme(theme: 'light' | 'dark'): void {
    this.theme = theme;
    const map = this.getMap();
    if (map) {
      map.setOptions({
        styles: theme === 'dark' ? darkModeStyles : [],
      });
    }
  }

  getTheme(): 'light' | 'dark' {
    return this.theme;
  }
}

Event-Driven Customization

Add custom event handling:

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

class CustomRouteOptimizer extends RouteOptimizer {
  private analytics: Analytics;

  constructor(config: RouteCalculatorConfig, analytics: Analytics) {
    super(config);
    this.analytics = analytics;
    this.setupAnalytics();
  }

  private setupAnalytics(): void {
    this.on('start', () => {
      this.analytics.trackEvent('optimization_started');
    });

    this.on('success', (response) => {
      this.analytics.trackEvent('optimization_completed', {
        routes: response.routes?.length || 0,
        distance: response.metrics?.totalDistance,
      });
    });

    this.on('error', (error) => {
      this.analytics.trackEvent('optimization_failed', {
        error: error.message,
      });
    });
  }
}

Theming

CSS Variables

Use CSS variables for consistent theming:

css
:root {
  --map-height: 600px;
  --map-border-radius: 12px;
  --map-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);

  --route-color-primary: #4caf50;
  --route-color-secondary: #2196f3;
  --route-color-warning: #ffc107;
  --route-color-danger: #f44336;

  --marker-size: 40px;
  --marker-color: #ff6b6b;
}

[data-theme='dark'] {
  --route-color-primary: #66bb6a;
  --route-color-secondary: #42a5f5;
  --marker-color: #ff8a80;
}

.map-container {
  height: var(--map-height);
  border-radius: var(--map-border-radius);
  box-shadow: var(--map-shadow);
}

Dynamic Theming

Switch themes at runtime:

typescript
// React
function ThemeSwitcher() {
  const [theme, setTheme] = useState<'light' | 'dark'>('light');

  const config: MapConfig = {
    apiKey: 'YOUR_API_KEY',
    styles: theme === 'dark' ? darkModeStyles : [],
  };

  const { mapRef } = useRouteMap(config);

  return (
    <div>
      <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
        Toggle Theme
      </button>
      <div ref={mapRef} />
    </div>
  );
}

Best Practices

1. Consistent Styling

Use a design system:

typescript
const designSystem = {
  colors: {
    primary: '#4CAF50',
    secondary: '#2196F3',
    danger: '#F44336',
  },
  routes: {
    express: {
      strokeColor: '#F44336',
      strokeWeight: 8,
    },
    standard: {
      strokeColor: '#4CAF50',
      strokeWeight: 6,
    },
  },
  markers: {
    size: 40,
    anchor: { x: 20, y: 40 },
  },
};

2. Accessibility

Ensure sufficient color contrast:

typescript
const accessibleColors = {
  route: '#0066CC', // WCAG AA compliant
  marker: '#CC0000',
  background: '#FFFFFF',
  text: '#000000',
};

3. Performance

Optimize custom markers:

typescript
// ✅ Good - Reuse marker icons
const markerIcon = {
  url: '/icons/marker.png',
  scaledSize: new google.maps.Size(40, 40),
};

stops.forEach((stop) => {
  addMarker(stop.location, { icon: markerIcon });
});

// ❌ Avoid - Creating new icons for each marker
stops.forEach((stop) => {
  addMarker(stop.location, {
    icon: {
      url: '/icons/marker.png',
      scaledSize: new google.maps.Size(40, 40),
    },
  });
});

4. Responsive Design

Adapt to screen sizes:

typescript
function getResponsiveMapConfig(): MapConfig {
  const isMobile = window.innerWidth < 768;

  return {
    apiKey: 'YOUR_API_KEY',
    zoom: isMobile ? 10 : 12,
    gestureHandling: isMobile ? 'cooperative' : 'auto',
  };
}

Next Steps

Released under the MIT License.