Skip to content

Vue Integration Guide

Learn how to integrate route optimization and mapping into your Vue 3 applications using @route-optimization/vue.

Installation

bash
npm install @route-optimization/vue @route-optimization/core
# or
pnpm add @route-optimization/vue @route-optimization/core
# or
yarn add @route-optimization/vue @route-optimization/core

Core Concepts

The Vue package provides composables that integrate with Vue 3's Composition API:

  • useRouteMap - Main composable for map rendering and route management
  • useMapControls - Composable for map interaction controls
  • useRouteOptimization - Composable for route calculation and optimization
  • useOptimizationStatus - Composable for optimization progress tracking

Basic Map Rendering

Simple Route Map

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

const mapRef = ref<HTMLDivElement>();

const { renderRoute, clearRoute, isReady } = useRouteMap({
  apiKey: 'YOUR_GOOGLE_MAPS_API_KEY',
  center: { lat: 13.7563, lng: 100.5018 }, // Bangkok
  zoom: 12,
  mapElement: mapRef,
});

const handleShowRoute = () => {
  renderRoute({
    stops: [
      {
        id: '1',
        location: { lat: 13.7563, lng: 100.5018 },
        name: 'Start Location',
      },
      {
        id: '2',
        location: { lat: 13.7467, lng: 100.5352 },
        name: 'End Location',
      },
    ],
  });
};
</script>

<template>
  <div>
    <div ref="mapRef" style="width: 100%; height: 600px" />

    <button @click="handleShowRoute" :disabled="!isReady">Show Route</button>
    <button @click="clearRoute" :disabled="!isReady">Clear Route</button>
  </div>
</template>

Route Map View Component

For simpler usage, use the pre-built RouteMapView component:

vue
<script setup lang="ts">
import { ref } from 'vue';
import { RouteMapView } from '@route-optimization/vue';
import type { Route } from '@route-optimization/core';

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

const handleError = (error: Error) => {
  console.error('Map error:', error);
};
</script>

<template>
  <RouteMapView
    api-key="YOUR_GOOGLE_MAPS_API_KEY"
    :route="route"
    :map-config="{
      center: { lat: 13.7563, lng: 100.5018 },
      zoom: 12,
    }"
    @error="handleError"
  />
</template>

Map Controls

Using Map Controls Composable

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

const mapRef = ref<HTMLDivElement>();

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

const { center, zoom, bounds, setCenter, setZoom, fitBounds } = useMapControls(map);

const handleCenterBangkok = () => {
  setCenter({ lat: 13.7563, lng: 100.5018 });
};

const handleZoomIn = () => {
  setZoom((zoom.value ?? 12) + 1);
};

const currentCenter = computed(() => {
  if (!center.value) return 'Loading...';
  return `${center.value.lat.toFixed(4)}, ${center.value.lng.toFixed(4)}`;
});
</script>

<template>
  <div>
    <div ref="mapRef" style="width: 100%; height: 600px" />

    <div class="controls">
      <p>Center: {{ currentCenter }}</p>
      <p>Zoom: {{ zoom }}</p>

      <button @click="handleCenterBangkok">Center on Bangkok</button>
      <button @click="handleZoomIn">Zoom In</button>
    </div>
  </div>
</template>

Route Optimization

Basic Optimization

vue
<script setup lang="ts">
import { useRouteOptimization } from '@route-optimization/vue';
import type { OptimizationRequest } from '@route-optimization/core';

const { status, data, error, isLoading, isSuccess, optimize, reset } = useRouteOptimization({
  useMockMode: true, // Use mock mode for development
});

const handleOptimize = async () => {
  const request: OptimizationRequest = {
    shipments: [
      {
        id: 'shipment-1',
        deliveryLocation: { latitude: 13.7563, longitude: 100.5018 },
      },
      {
        id: 'shipment-2',
        deliveryLocation: { latitude: 13.7467, longitude: 100.5352 },
      },
    ],
    vehicles: [
      {
        id: 'vehicle-1',
        startLocation: { latitude: 13.7563, longitude: 100.5018 },
      },
    ],
  };

  try {
    const response = await optimize(request);
    console.log('Optimization result:', response);
  } catch (err) {
    console.error('Optimization failed:', err);
  }
};
</script>

<template>
  <div>
    <button @click="handleOptimize" :disabled="isLoading">
      {{ isLoading ? 'Optimizing...' : 'Optimize Routes' }}
    </button>

    <div v-if="isSuccess && data" class="result">
      <h3>Optimization Complete!</h3>
      <p>Routes: {{ data.routes?.length ?? 0 }}</p>
      <p>Total Distance: {{ data.metrics?.totalDistance ?? 0 }} km</p>
    </div>

    <div v-if="error" class="error">Error: {{ error }}</div>
  </div>
</template>

Production Mode with Google Cloud

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

const { optimize } = useRouteOptimization({
  useMockMode: false,
  projectId: 'your-google-cloud-project',
  credentials: {
    client_email: 'service-account@project.iam.gserviceaccount.com',
    private_key: import.meta.env.VITE_GOOGLE_PRIVATE_KEY,
  },
});
</script>

Progress Tracking

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

const { optimize } = useRouteOptimization({ useMockMode: true });

const {
  progress,
  step,
  setProgress,
  reset: resetProgress,
} = useOptimizationStatus({
  onStart: () => console.log('Optimization started'),
  onSuccess: () => console.log('Optimization complete!'),
  onError: (error) => console.error('Failed:', error),
});

const handleOptimize = async () => {
  resetProgress();
  setProgress(0, 'Initializing...');

  // Simulate progress
  setProgress(25, 'Calculating distances...');

  const response = await optimize(request);

  setProgress(75, 'Optimizing routes...');

  if (response.success) {
    setProgress(100, 'Complete!');
  }
};
</script>

<template>
  <div>
    <button @click="handleOptimize">Optimize</button>

    <div class="progress">
      <div class="progress-bar" :style="{ width: `${progress}%` }" />
      <p>{{ step }}</p>
    </div>
  </div>
</template>

<style scoped>
.progress {
  width: 100%;
  background: #f0f0f0;
  border-radius: 4px;
  overflow: hidden;
}

.progress-bar {
  height: 24px;
  background: #4caf50;
  transition: width 0.3s ease;
}
</style>

Complete Example

Combining map rendering with route optimization:

vue
<script setup lang="ts">
import { ref, reactive } from 'vue';
import { useRouteOptimization, RouteMapView } from '@route-optimization/vue';
import type { Route } from '@route-optimization/core';

const route = ref<Route | null>(null);
const locations = reactive([
  { lat: 13.7563, lng: 100.5018, name: 'Bangkok' },
  { lat: 13.7467, lng: 100.5352, name: 'Chatuchak' },
  { lat: 13.7307, lng: 100.5418, name: 'Victory Monument' },
]);

const { optimize, isLoading, isSuccess } = useRouteOptimization({
  useMockMode: true,
});

const handleOptimize = async () => {
  const request = {
    shipments: locations.map((loc, i) => ({
      id: `shipment-${i}`,
      deliveryLocation: { latitude: loc.lat, longitude: loc.lng },
    })),
    vehicles: [
      {
        id: 'vehicle-1',
        startLocation: {
          latitude: locations[0].lat,
          longitude: locations[0].lng,
        },
      },
    ],
  };

  const response = await optimize(request);

  if (response.success && response.routes?.[0]) {
    // Convert optimization result to Route format
    route.value = {
      stops:
        response.routes[0].stops?.map((stop, i) => ({
          id: stop.shipmentId || `stop-${i}`,
          location: {
            lat: stop.location?.latitude ?? 0,
            lng: stop.location?.longitude ?? 0,
          },
          name: locations[i]?.name ?? `Stop ${i + 1}`,
        })) ?? [],
    };
  }
};
</script>

<template>
  <div class="app">
    <div class="sidebar">
      <h2>Route Optimization</h2>

      <div class="locations">
        <div v-for="(loc, i) in locations" :key="i" class="location-item">
          {{ loc.name }}
        </div>
      </div>

      <button @click="handleOptimize" :disabled="isLoading">
        {{ isLoading ? 'Optimizing...' : 'Optimize Route' }}
      </button>

      <div v-if="isSuccess" class="success">✓ Route optimized successfully!</div>
    </div>

    <div class="map-container">
      <RouteMapView
        :api-key="import.meta.env.VITE_GOOGLE_MAPS_API_KEY"
        :route="route"
        :map-config="{
          center: { lat: 13.7563, lng: 100.5018 },
          zoom: 12,
        }"
      />
    </div>
  </div>
</template>

<style scoped>
.app {
  display: flex;
  height: 100vh;
}

.sidebar {
  width: 300px;
  padding: 20px;
  background: #f5f5f5;
}

.map-container {
  flex: 1;
}

.locations {
  margin: 20px 0;
}

.location-item {
  padding: 10px;
  margin: 5px 0;
  background: white;
  border-radius: 4px;
}

button {
  width: 100%;
  padding: 12px;
  background: #4caf50;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.success {
  margin-top: 10px;
  padding: 10px;
  background: #e8f5e9;
  color: #2e7d32;
  border-radius: 4px;
}
</style>

TypeScript Support

All composables are fully typed. Import types from the packages:

ts
import type {
  Route,
  Stop,
  MapConfig,
  OptimizationRequest,
  OptimizationResponse,
} from '@route-optimization/core';

import type {
  UseRouteMapOptions,
  UseRouteMapReturn,
  UseMapControlsReturn,
  UseRouteOptimizationResult,
} from '@route-optimization/vue';

Composition API Patterns

Composable Reusability

Create custom composables for common patterns:

ts
// composables/useOptimizedRoute.ts
import { ref } from 'vue';
import { useRouteOptimization } from '@route-optimization/vue';
import type { Route } from '@route-optimization/core';

export function useOptimizedRoute() {
  const route = ref<Route | null>(null);
  const { optimize, isLoading, isSuccess } = useRouteOptimization({
    useMockMode: true,
  });

  const optimizeLocations = async (locations: Array<{ lat: number; lng: number }>) => {
    const request = {
      shipments: locations.map((loc, i) => ({
        id: `shipment-${i}`,
        deliveryLocation: { latitude: loc.lat, longitude: loc.lng },
      })),
      vehicles: [
        {
          id: 'vehicle-1',
          startLocation: { latitude: locations[0].lat, longitude: locations[0].lng },
        },
      ],
    };

    const response = await optimize(request);

    if (response.success && response.routes?.[0]) {
      route.value = {
        stops:
          response.routes[0].stops?.map((stop, i) => ({
            id: stop.shipmentId || `stop-${i}`,
            location: {
              lat: stop.location?.latitude ?? 0,
              lng: stop.location?.longitude ?? 0,
            },
            name: `Stop ${i + 1}`,
          })) ?? [],
      };
    }

    return route.value;
  };

  return {
    route,
    optimizeLocations,
    isLoading,
    isSuccess,
  };
}

Reactive State Management

Use Vue's reactivity system with the composables:

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

const selectedRoute = ref<Route | null>(null);
const mapRef = ref<HTMLDivElement>();

const { renderRoute, isReady } = useRouteMap({
  apiKey: 'YOUR_API_KEY',
  mapElement: mapRef,
});

// Automatically render when route changes
watch(selectedRoute, (newRoute) => {
  if (newRoute && isReady.value) {
    renderRoute(newRoute);
  }
});

const routeStats = computed(() => {
  if (!selectedRoute.value) return null;

  return {
    stops: selectedRoute.value.stops.length,
    // Add more computed stats
  };
});
</script>

Best Practices

1. Environment Variables

Store API keys in environment variables:

env
# .env.local
VITE_GOOGLE_MAPS_API_KEY=your_api_key
VITE_GOOGLE_PROJECT_ID=your_project_id

Access in your app:

ts
const apiKey = import.meta.env.VITE_GOOGLE_MAPS_API_KEY;

2. Error Handling

Use try-catch with proper error handling:

vue
<script setup lang="ts">
import { ref } from 'vue';

const error = ref<string | null>(null);

const handleOptimize = async () => {
  error.value = null;

  try {
    const result = await optimize(request);
    // Handle success
  } catch (err) {
    error.value = err instanceof Error ? err.message : 'Unknown error';
  }
};
</script>

<template>
  <div v-if="error" class="error">
    {{ error }}
  </div>
</template>

3. Cleanup

Components automatically clean up, but you can use onUnmounted:

ts
import { onUnmounted } from 'vue';

onUnmounted(() => {
  clearRoute();
  reset();
});

4. Computed Properties

Use computed for derived state:

ts
const routeDistance = computed(() => {
  if (!data.value?.metrics) return 0;
  return data.value.metrics.totalDistance;
});

Nuxt 3 Integration

For Nuxt 3 applications, use client-only rendering for map components:

vue
<template>
  <ClientOnly>
    <RouteMapView :api-key="apiKey" :route="route" />
  </ClientOnly>
</template>

Next Steps

Released under the MIT License.