Skip to main content

Search Location

|

This example demonstrates how to search for landmarks around a specific geographic location using coordinates.

Live Demo

Overview

This example shows how to:

  • Search for landmarks around specific latitude/longitude coordinates
  • Display search results with distance and address information
  • Highlight and center the map on selected results
  • Use SearchService.searchAroundPosition() for proximity-based search

Code Implementation

Imports

First, import the required modules from the SDK:

import {
GemKit,
GemMap,
Coordinates,
Landmark,
SearchPreferences,
SearchService,
GemError,
HighlightRenderSettings,
HighlightOptions,
AddressField,
} from '@magiclane/maps-sdk';

Setup UI and Map

Initialize the map and add a search button:

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

// Initialize UI
createSidebar();

// Search Location button
searchBtn = document.createElement('button');
searchBtn.innerHTML = `${ICONS.search} Search Location`;
styleMainButton(searchBtn);
searchBtn.onclick = () => toggleSidebar(true);
document.body.appendChild(searchBtn);
});

Create Search Sidebar

Build a sidebar with latitude/longitude inputs:

index.ts
function createSidebar() {
sidebarPanel = document.createElement('div');
sidebarPanel.style.cssText = `
position: fixed; top: 0; left: 0; bottom: 0; width: 360px;
background: #fff; z-index: 2500;
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;
`;

// --- Header ---
const header = document.createElement('div');
header.style.cssText = `
padding: 20px; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; align-items: center;
`;
header.innerHTML = `<h2 style="margin:0; font-size: 20px; color:#333;">Search Area</h2>`;

const closeBtn = document.createElement('button');
closeBtn.innerHTML = ICONS.close;
closeBtn.style.cssText = `background:none; border:none; cursor:pointer; color:#666; padding:5px;`;
closeBtn.onclick = () => toggleSidebar(false);
header.appendChild(closeBtn);
sidebarPanel.appendChild(header);

// --- Input Area ---
const inputContainer = document.createElement('div');
inputContainer.style.cssText = `padding: 20px; background: #f9f9f9; border-bottom: 1px solid #eee; display:flex; flex-direction:column; gap:12px;`;

// Latitude
latInput = document.createElement('input');
latInput.type = 'text';
latInput.placeholder = 'Latitude (e.g. 48.85)';
latInput.style.cssText = `width: 100%; padding: 12px 16px; border: 1px solid #ddd; border-radius: 8px; font-size: 16px; outline: none; box-sizing: border-box;`;

// Longitude
lngInput = document.createElement('input');
lngInput.type = 'text';
lngInput.placeholder = 'Longitude (e.g. 2.35)';
lngInput.style.cssText = `width: 100%; padding: 12px 16px; border: 1px solid #ddd; border-radius: 8px; font-size: 16px; outline: none; box-sizing: border-box;`;

const searchActionBtn = document.createElement('button');
searchActionBtn.textContent = 'Search';
searchActionBtn.style.cssText = `
width: 100%; padding: 12px; background: #673ab7; color: white;
border: none; border-radius: 8px; font-weight: 600; cursor: pointer;
display: flex; align-items: center; justify-content: center; gap: 8px;
`;
searchActionBtn.onclick = () => performSearch(latInput.value, lngInput.value);

inputContainer.appendChild(latInput);
inputContainer.appendChild(lngInput);
inputContainer.appendChild(searchActionBtn);
sidebarPanel.appendChild(inputContainer);

// --- Results Label ---
const resultsLabel = document.createElement('div');
resultsLabel.innerHTML = `<span style="font-weight:600; font-size:13px; text-transform:uppercase; color:#888;">Results</span>`;
resultsLabel.style.cssText = `padding: 15px 20px 5px 20px;`;
sidebarPanel.appendChild(resultsLabel);

// --- Results List ---
resultsContainer = document.createElement('div');
resultsContainer.style.cssText = `
flex: 1; overflow-y: auto; padding: 10px 20px; background: #fff;
`;
sidebarPanel.appendChild(resultsContainer);

document.body.appendChild(sidebarPanel);
}

Perform Search Around Position

Execute the search using coordinates:

index.ts
function performSearch(lat: string, lng: string) {
if (!map) return;
const latitude = parseFloat(lat);
const longitude = parseFloat(lng);

if (isNaN(latitude) || isNaN(longitude)) {
showMessage('Invalid latitude or longitude');
return;
}

showMessage('Searching...');
const coords = Coordinates.fromLatLong(latitude, longitude);

const preferences = SearchPreferences.create({
maxMatches: 40,
allowFuzzyResults: true,
});

SearchService.searchAroundPosition({
position: coords,
preferences,
onCompleteCallback: (err: GemError, results: Landmark[]) => {
if (err !== GemError.success) {
showMessage('No results found');
searchResults = [];
renderResults();
return;
}
searchResults = results;
renderResults();
}
});
}

Display Search Results

Show results with distance and address:

index.ts
function getFormattedDistance(landmark: Landmark): string {
try {
const dist = landmark.extraInfo?.getByKey?.('gmSearchResultDistance') || 0;
const km = (dist / 1000).toFixed(1);
return `${km} km`;
} catch {
return '';
}
}

function getAddress(landmark: Landmark): string {
try {
const addressInfo = landmark.address || {};
const street = addressInfo.getField(AddressField.streetName) || '';
const city = addressInfo.getField(AddressField.city) || '';

if (!street && !city) return 'Address not available';
return [street, city].filter(Boolean).join(', ');
} catch {
return '';
}
}

Select and Highlight Result

Handle result selection:

index.ts
function selectSearchResult(landmark: Landmark) {
if (!map) return;
// Highlight the landmark
try {
const renderSettings = new HighlightRenderSettings({
options: new Set([HighlightOptions.showLandmark])
});

// Ensure icon
try { landmark.setImageFromIcon(GemIcon.searchResultsPin); } catch(e){}

map.activateHighlight([landmark], { renderSettings });
} catch {
map.activateHighlight([landmark]);
}
// Center map on landmark
if (landmark.coordinates) {
map.centerOnCoordinates(landmark.coordinates, { zoomLevel: 70 });
}
// Show message
showMessage(`Selected: ${landmark.name}`);

// Close sidebar on mobile
if (window.innerWidth < 600) toggleSidebar(false);
}

Utility Functions

Display temporary messages:

index.ts
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

Search Around Position

The SearchService.searchAroundPosition() method searches for landmarks within proximity of given coordinates:

Parameters:

  • position: Coordinates object with latitude/longitude
  • preferences: SearchPreferences with max results and fuzzy search options
  • onCompleteCallback: Callback receiving error code and results array

Search Preferences:

  • maxMatches: Limit to 40 results
  • allowFuzzyResults: Enable approximate matching

Coordinate Input

The modal accepts:

  • Latitude: Decimal degrees (-90 to 90)
  • Longitude: Decimal degrees (-180 to 180)
  • Validation: Parse and check for valid numeric values

Distance Calculation

Extract distance from landmark extra info:

const dist = landmark.extraInfo?.getByKey?.('gmSearchResultDistance') || 0;
const km = Math.round(dist / 1000);

The distance is in meters, converted to kilometers for display.

Address Extraction

Use AddressField enum to get structured address components:

  • AddressField.streetName - Street name
  • AddressField.city - City name
  • AddressField.country - Country name

Result Highlighting

Configure highlight options:

const renderSettings = new HighlightRenderSettings({
options: new Set([HighlightOptions.showLandmark])
});

This ensures the landmark is visible on the map.

Implementation Details

  • Modal State: Single modal instance, recreated when results change
  • Error Handling: Validate input coordinates before search
  • Search Feedback: Show "No results found" or result count
  • Auto-refresh: Reopen modal with results after successful search
  • Cleanup: Clear results when modal is closed

Use Cases

  • Proximity Search: Find nearby POIs from any location
  • Coordinate-based Discovery: Explore areas by coordinates
  • Travel Planning: Preview landmarks before visiting
  • Remote Location Search: Search areas without current position

Next Steps