Skip to main content

Finger Route

|

This example demonstrates how to draw a route by tapping or clicking on the map to place waypoints, then calculating and displaying a route through those points using the Maps SDK for TypeScript.

Live Demo

Overview

The example demonstrates the following features:

  • Enabling draw markers mode to collect waypoints
  • Drawing routes by tapping/clicking on the map
  • Calculating routes through multiple waypoints
  • Displaying route information (distance and duration)
  • Managing route calculation state

Code Implementation

Initialize GemKit and Setup

index.ts
import {
GemKit,
GemMap,
PositionService,
RoutingService,
RoutePreferences,
GemError,
Route,
Coordinates
} from '@magiclane/maps-sdk';
import { GEMKIT_TOKEN } from './token';

let map: GemMap | null = null;
let routingHandler: any = null;
let areRoutesBuilt = false;
let isInDrawingMode = false;

// UI Elements
let drawBtn: HTMLButtonElement;
let buildBtn: HTMLButtonElement;
let cancelBtn: HTMLButtonElement;
let clearBtn: HTMLButtonElement;

window.addEventListener('DOMContentLoaded', async () => {
const gemKit = await GemKit.initialize(GEMKIT_TOKEN);
await PositionService.instance;

const container = document.getElementById('map-container');
if (!container) throw new Error('Map container not found');

const viewId = 2;
const wrapper = gemKit.createView(viewId, onMapCreated);
if (wrapper) container.appendChild(wrapper);

updateUI();
});

async function onMapCreated(gemMap: GemMap) {
map = gemMap;
updateUI();
map.centerOnCoordinates(Coordinates.fromLatLong(44.4268, 26.1025), { zoomLevel: 70 });
}

Enable Draw Markers Mode

index.ts
function onDrawPressed() {
if (!map) return;
map.enableDrawMarkersMode();
isInDrawingMode = true;
updateUI();
showMessage("Tap on the map to place waypoints.");
}

Build Route from Waypoints

index.ts
function onBuildRouteButtonPressed() {
if (!map) return;

// Disable draw mode and get the collected waypoints
const waypoints = map.disableDrawMarkersMode();

// Configure route preferences for finger-drawn routes
const routePreferences = new RoutePreferences({
accurateTrackMatch: false,
ignoreRestrictionsOverTrack: true
});

showMessage('The route is being calculated.');

// Calculate route through all waypoints
routingHandler = RoutingService.calculateRoute(
waypoints,
routePreferences,
(err: GemError, routes: Route[]) => {
routingHandler = null;
isInDrawingMode = false;
updateUI();

if (err === GemError.success && routes.length > 0) {
const routesMap = map!.preferences.routes;

// Add routes to map with labels
routes.forEach((route, idx) => {
routesMap.add(route, idx === 0, { label: getRouteMapLabel(route) });
});

// Center map on the calculated routes
map!.centerOnRoutes({ routes });
areRoutesBuilt = true;
updateUI();
} else {
showMessage('Route calculation failed.');
}
}
);
updateUI();
}

Clear and Cancel Operations

index.ts
function onClearRoutesButtonPressed() {
if (!map) return;

// Remove all routes from the map
map.preferences.routes.clear();
areRoutesBuilt = false;
updateUI();
}

function onCancelRouteButtonPressed() {
if (routingHandler) {
// Cancel ongoing route calculation
RoutingService.cancelRoute(routingHandler);
routingHandler = null;
isInDrawingMode = false;
updateUI();
}
}

UI Management

index.ts
function updateUI() {
// Show/hide buttons based on current state
drawBtn.style.display = (!routingHandler && !areRoutesBuilt && !isInDrawingMode) ? 'block' : 'none';
buildBtn.style.display = (!routingHandler && !areRoutesBuilt && isInDrawingMode) ? 'block' : 'none';
cancelBtn.style.display = routingHandler ? 'block' : 'none';
clearBtn.style.display = areRoutesBuilt ? 'block' : 'none';
}

Utility Functions

index.ts
// Helper function to create map label for routes
function getRouteMapLabel(route: Route): string {
const timeDistance = route.getTimeDistance();
const totalDistance = timeDistance.unrestrictedDistanceM + timeDistance.restrictedDistanceM;
const totalDuration = timeDistance.unrestrictedTimeS + timeDistance.restrictedTimeS;

return `${convertDistance(totalDistance)} \n${convertDuration(totalDuration)}`;
}

// Convert distance to human-readable format
function convertDistance(meters: number): string {
if (meters >= 1000) {
const kilometers = meters / 1000;
return `${kilometers.toFixed(1)} km`;
} else {
return `${meters.toString()} m`;
}
}

// Convert duration to human-readable format
function convertDuration(seconds: number): string {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const hoursText = hours > 0 ? `${hours} h ` : '';
const minutesText = `${minutes} min`;
return hoursText + minutesText;
}

// Utility: show a temporary message
function showMessage(message: string, duration = 3000) {
let msgDiv = document.getElementById('status-msg');
if (!msgDiv) {
msgDiv = document.createElement('div');
msgDiv.id = 'status-msg';
msgDiv.style.cssText = `
position: fixed; top: 20px; left: 50%; transform: translateX(-50%);
background: #333; color: #fff; padding: 12px 20px; border-radius: 8px;
z-index: 2000; font-size: 1em;
`;
document.body.appendChild(msgDiv);
}
msgDiv.textContent = message;
setTimeout(() => { msgDiv.textContent = ''; }, duration);
}

Key Features

  • Draw Markers Mode: Interactive waypoint placement by tapping/clicking on the map
  • Multi-Waypoint Routes: Calculate routes through multiple user-defined points
  • Route Preferences: Customizable routing options for finger-drawn routes
  • State Management: Clear UI state transitions between drawing, calculating, and viewing routes
  • Cancellation Support: Cancel ongoing route calculations

Route Preferences for Finger Routes

When calculating routes from user-drawn waypoints, specific preferences are used:

const routePreferences = new RoutePreferences({
accurateTrackMatch: false, // Don't require precise track matching
ignoreRestrictionsOverTrack: true // Ignore road restrictions
});

These settings are important for finger-drawn routes because:

  • Users may not place waypoints precisely on roads
  • The route should follow the general path indicated by waypoints
  • Road restrictions may prevent routing through some waypoints

Workflow

  1. Start Drawing: Click "Draw Route" to enable waypoint placement
  2. Add Waypoints: Tap/click on the map to add waypoints along your desired route
  3. Build Route: Click "Build Route" to calculate a route through all waypoints
  4. View Results: The route is displayed with distance and duration labels
  5. Clear/Restart: Clear routes to start over or cancel during calculation

UI States

The example manages four distinct UI states:

  • Initial: Show "Draw Route" button
  • Drawing: Show "Build Route" button (while placing waypoints)
  • Calculating: Show "Cancel" button (during route calculation)
  • Complete: Show "Clear Routes" button (when routes are displayed)

Tips for Best Results

  • Place waypoints on or near roads for better routing
  • Use at least 2 waypoints (start and end points)
  • More waypoints give you more control over the route path
  • Zoom in for more precise waypoint placement
  • Routes are calculated to follow roads between waypoints

Next Steps