Skip to main content

Search Along Route

|

This example demonstrates how to calculate a route, simulate navigation, and search for landmarks along the route path.

Live Demo

Overview

This example shows how to:

  • Calculate a route between two landmarks and display it on the map
  • Start and stop simulated navigation along the route
  • Search for landmarks located along the route path
  • Manage route visualization and alternative routes

Code Implementation

Imports

First, import the required modules from the SDK:

import {
GemKit,
GemMap,
Coordinates,
PositionService,
Landmark,
RoutePreferences,
RoutingService,
GemError,
Route,
NavigationService,
SearchService,
HighlightRenderSettings,
HighlightOptions,
TaskHandler,
GemIcon,
AddressField
} from '@magiclane/maps-sdk';

Setup State Variables

Initialize global variables for route and navigation management:

let map: GemMap | null = null;
let routingHandler: TaskHandler | null = null;
let navigationHandler: TaskHandler | null = null;
let routes: Route[] | null = null;
let isSimulationActive = false;
let areRoutesBuilt = false;

// UI Elements
let controlsDiv: HTMLDivElement;
let buildRouteBtn: HTMLButtonElement;
let cancelRouteBtn: HTMLButtonElement;
let clearRoutesBtn: HTMLButtonElement;
let searchBtn: HTMLButtonElement;
let startSimBtn: HTMLButtonElement;
let stopSimBtn: HTMLButtonElement;
let searchResultsPanel: HTMLDivElement;

Initialize Map and UI Buttons

Setup the map and create control buttons:

index.ts
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, (gemMap: GemMap) => {
map = gemMap;
});
if (wrapper) container.appendChild(wrapper);

// --- Controls Container (Fixed Header) ---
controlsDiv = document.createElement('div');
controlsDiv.style.cssText = `
position: fixed;
top: 30px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 12px;
z-index: 2000;
align-items: center;
justify-content: center;
`;
document.body.appendChild(controlsDiv);

// Build Route button
buildRouteBtn = document.createElement('button');
buildRouteBtn.innerHTML = `${ICONS.route} Build Route`;
styleButton(buildRouteBtn, '#673ab7', '#7e57c2');
buildRouteBtn.onclick = () => onBuildRouteButtonPressed();
controlsDiv.appendChild(buildRouteBtn);

// Cancel Route button
cancelRouteBtn = document.createElement('button');
cancelRouteBtn.innerHTML = `${ICONS.close} Cancel`;
styleButton(cancelRouteBtn, '#f44336', '#ef5350');
cancelRouteBtn.onclick = () => onCancelRouteButtonPressed();
controlsDiv.appendChild(cancelRouteBtn);

// Search Along Route button
searchBtn = document.createElement('button');
searchBtn.innerHTML = `${ICONS.search} Search`;
styleButton(searchBtn, '#2196f3', '#42a5f5');
searchBtn.onclick = () => searchAlongRoute();
controlsDiv.appendChild(searchBtn);

// Start Simulation button
startSimBtn = document.createElement('button');
startSimBtn.innerHTML = `${ICONS.play} Simulate`;
styleButton(startSimBtn, '#4caf50', '#66bb6a');
startSimBtn.onclick = () => startSimulation();
controlsDiv.appendChild(startSimBtn);

// Stop Simulation button
stopSimBtn = document.createElement('button');
stopSimBtn.innerHTML = `${ICONS.stop} Stop`;
styleButton(stopSimBtn, '#f44336', '#ef5350');
stopSimBtn.onclick = () => stopSimulation();
controlsDiv.appendChild(stopSimBtn);

// Clear Routes button (Placed last)
clearRoutesBtn = document.createElement('button');
clearRoutesBtn.innerHTML = `${ICONS.trash} Clear`;
styleButton(clearRoutesBtn, '#ff9800', '#ffb74d');
clearRoutesBtn.onclick = () => onClearRoutesButtonPressed();
controlsDiv.appendChild(clearRoutesBtn);

// Initialize Search Results Sidebar
searchResultsPanel = document.createElement('div');
searchResultsPanel.style.cssText = `
position: fixed; top: 0; left: 0; bottom: 0; width: 320px;
background: #fff; z-index: 1500;
box-shadow: 4px 0 20px rgba(0,0,0,0.1);
transform: translateX(-105%);
transition: transform 0.3s cubic-bezier(0.4, 0.0, 0.2, 1);
display: flex; flex-direction: column;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
`;
document.body.appendChild(searchResultsPanel);

updateUI();
});

Calculate Route

Build a route between San Francisco and San Jose:

index.ts
function onBuildRouteButtonPressed() {
const departureLandmark = Landmark.withCoordinates(Coordinates.fromLatLong(37.77903, -122.41991));
const destinationLandmark = Landmark.withCoordinates(Coordinates.fromLatLong(37.33619, -121.89058));
const routePreferences = new RoutePreferences({});

showMessage('Calculating route...');
routingHandler = RoutingService.calculateRoute(
[departureLandmark, destinationLandmark],
routePreferences,
(err: GemError, calculatedRoutes: Route[]) => {
routingHandler = null;
if (err === GemError.success && calculatedRoutes.length > 0) {
const routesMap = map?.preferences.routes;
calculatedRoutes.forEach((route, index) => {
routesMap?.add(route, index === 0, { label: getRouteLabel(route) });
});
map?.centerOnRoutes({ routes: calculatedRoutes });
showMessage('Route calculated successfully!');
routes = calculatedRoutes;
areRoutesBuilt = true;
} else {
showMessage('Route calculation failed.');
}
updateUI();
}
);
updateUI();
}

Manage Routes

Cancel or clear routes from the map:

index.ts
function onClearRoutesButtonPressed() {
map?.preferences.routes.clear();
map?.deactivateHighlight(); // Clear highlights
routes = null;
areRoutesBuilt = false;
isSimulationActive = false;
closeSearchResults(); // Close sidebar
updateUI();
}

function onCancelRouteButtonPressed() {
if (routingHandler) {
RoutingService.cancelRoute(routingHandler);
routingHandler = null;
showMessage('Route calculation cancelled.');
}
updateUI();
}

Start Navigation Simulation

Simulate navigation along the calculated route:

index.ts
function startSimulation() {
if (isSimulationActive || !areRoutesBuilt || !routes || !map) return;
map.preferences.routes.clearAllButMainRoute?.();
const routesMap = map.preferences.routes;
if (!routesMap.mainRoute) {
showMessage('No main route available');
return;
}

navigationHandler = NavigationService.startSimulation(
routesMap.mainRoute,
undefined,
{
onNavigationInstruction: () => {
if (!isSimulationActive) {
isSimulationActive = true;
updateUI();
}
},
onError: (error: GemError) => {
isSimulationActive = false;
onClearRoutesButtonPressed();
if (error !== GemError.cancel) {
stopSimulation();
}
updateUI();
}
}
);
map.startFollowingPosition?.();
isSimulationActive = true;
updateUI();
showMessage("Simulation started.");
}

Stop Navigation Simulation

Cancel the navigation and clear routes:

index.ts
function stopSimulation() {
if (navigationHandler) {
NavigationService.cancelNavigation(navigationHandler);
navigationHandler = null;
}
onClearRoutesButtonPressed();
isSimulationActive = false;
areRoutesBuilt = false;
updateUI();
showMessage("Simulation stopped.");
}

Search Along Route

Search for landmarks located along the route path:

index.ts
function searchAlongRoute() {
if (!areRoutesBuilt || !map) return;

const routesMap = map.preferences.routes;
if (!routesMap.mainRoute) {
showMessage('No main route available');
return;
}

SearchService.searchAlongRoute({
route: routesMap.mainRoute,
onCompleteCallback: (err: GemError, results: Landmark[]) => {
if (err !== GemError.success) {
showMessage('SearchAlongRoute - no results found');
return;
}

showMessage(`SearchAlongRoute - ${results.length} results found.`);
results.forEach((landmark) => {
console.log('SearchAlongRoute:', landmark.name);
});
}
});
}

Update UI State

Manage button visibility based on application state:

index.ts
function updateUI() {
buildRouteBtn.style.display = (!routingHandler && !areRoutesBuilt)
? 'block'
: 'none';
cancelRouteBtn.style.display = (routingHandler && !areRoutesBuilt)
? 'block'
: 'none';
clearRoutesBtn.style.display = (areRoutesBuilt && !routingHandler)
? 'block'
: 'none';
searchBtn.style.display = (areRoutesBuilt && !routingHandler)
? 'block'
: 'none';
startSimBtn.style.display = (!isSimulationActive && areRoutesBuilt)
? 'block'
: 'none';
stopSimBtn.style.display = isSimulationActive
? 'block'
: 'none';
}

Utility Functions

Format route labels and display messages:

index.ts
function getRouteLabel(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)}`;
}

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

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;
}

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

Route Calculation

Calculate routes using RoutingService:

Parameters:

  • waypoints: Array of Landmark objects (departure and destination)
  • preferences: RoutePreferences object for route customization
  • callback: Receives error code and calculated routes

Route Display:

calculatedRoutes.forEach((route, index) => {
routesMap?.add(route, index === 0, { label: getRouteLabel(route) });
});

The first route is set as the main route (index === 0).

Start simulated navigation using NavigationService:

Parameters:

  • route: The main route to navigate
  • undefined: Optional start position (uses route start if undefined)
  • callbacks: Object with onNavigationInstruction and onError handlers

Following Position:

map.startFollowingPosition?.();

The map camera follows the simulated position along the route.

Search Along Route

Find landmarks along the route path:

Method:

SearchService.searchAlongRoute({
route: routesMap.mainRoute,
onCompleteCallback: (err, results) => { /* ... */ }
});

Results:

  • Returns all landmarks located along the route
  • Results are logged to console for inspection
  • Useful for finding POIs, rest stops, gas stations, etc.

Route Management

Clear All Routes:

map?.preferences.routes.clear();

Keep Only Main Route:

map?.preferences.routes.clearAllButMainRoute?.();

Cancel Calculation:

RoutingService.cancelRoute(routingHandler);

Route Labels

Display distance and duration on route:

const timeDistance = route.getTimeDistance();
const totalDistance = timeDistance.unrestrictedDistanceM
+ timeDistance.restrictedDistanceM;
const totalDuration = timeDistance.unrestrictedTimeS
+ timeDistance.restrictedTimeS;

Labels show formatted distance (km) and duration (hours/minutes).

State Management

The UI updates based on application state:

States:

  • Idle: Show "Build Route" button
  • Calculating: Show "Cancel Route" button
  • Route Built: Show "Clear Routes", "Search", and "Start Simulation"
  • Simulating: Show "Stop Simulation" button only

Implementation Details

  • Handler Management: Store routing and navigation handlers for cancellation
  • Route Storage: Keep calculated routes in state for later use
  • Dynamic UI: Buttons appear/disappear based on current state
  • Error Handling: Handle route calculation and navigation errors gracefully
  • Console Logging: Search results are logged to browser console
  • Route Centering: Automatically center map on calculated routes

Use Cases

  • Trip Planning: Calculate routes and find nearby POIs
  • Gas Station Search: Find fuel stops along long routes
  • Rest Area Discovery: Locate rest stops during navigation
  • Restaurant Finder: Search for dining options along the way
  • Testing Navigation: Simulate navigation without GPS movement
  • Route Comparison: View alternative routes with different landmarks

Next Steps