Usage Guidelines
This page outlines the recommended usage guidelines for the Maps SDK for TypeScript. Adhering to these recommendations can enhance code reliability and help prevent issues that may arise from improper implementation.
Do Not Extend SDK Classes
The SDK is designed to deliver all necessary functionalities in an intuitive and straightforward manner. Avoid extending any of the Maps SDK classes. Use the callback methods and event handlers provided by the SDK for seamless integration.
Date and Time Parameters
Ensure that any Date objects passed to SDK methods are valid. The SDK expects UTC timestamps for time-based operations.
// Valid date usage
const validDate = new Date();
const utcTimestamp = validDate.getTime();
General Principles for Event Listeners
- Use the provided register methods or callback parameters to subscribe to events.
- Only one callback can be active per event type. If multiple callbacks are registered for the same event type, the most recently registered callback will override the previous one.
- To unsubscribe from events, pass
undefinedor an empty callback.
// Subscribe to map events
map.registerMapAngleUpdateCallback((angle: number) => {
console.log('Map angle changed:', angle);
});
// Unsubscribe from events
map.registerMapAngleUpdateCallback(undefined);
Avoid Using Internal Members
Members, methods, and fields marked as internal or prefixed with underscore (_) are intended for internal use and should not be accessed or used by API users.
Examples of Internal Members to Avoid:
- Fields starting with
_(e.g.,_internalState,_nativeHandle) - Methods marked as
@internalin documentation - Private constructors or factory methods not documented in the public API
Using these internal elements can lead to unexpected behavior and compatibility issues since they are not part of the public API contract and may change or be removed without notice.
Check Error Codes
Many asynchronous operations use callbacks that include error codes. Ensure that all failure cases are handled appropriately to maintain code robustness and prevent unexpected behavior.
The GemError enum defines the possible values corresponding to the outcome of an operation. The following values indicate successful outcomes:
GemError.success(0): The operation completed successfully.GemError.reducedResult(-16): The operation completed successfully, but only a subset of the results was returned.GemError.scheduled(-41): The operation has been scheduled for execution at a later time.
All other negative values indicate errors. Common error codes include:
GemError.general(-1): General errorGemError.cancel(-3): Operation canceledGemError.notFound(-11): Required item not foundGemError.invalidInput(-15): Invalid input providedGemError.noRoute(-18): No route possibleGemError.connection(-22): Connection failedGemError.networkTimeout(-29): Network operation timeout
For a complete list of error codes, refer to the API Reference.
import { SearchService, GemError } from '@magiclane/maps-sdk';
SearchService.search({
textFilter: 'restaurant',
referenceCoordinates: coordinates,
onCompleteCallback: (err: GemError, results: Landmark[]) => {
if (err === GemError.success) {
console.log('Search completed successfully:', results);
} else {
console.error('Search failed with error:', err);
}
}
});
Handle Errors with Promises
For better error handling, you can wrap callback-based operations in Promises:
function searchAsync(text: string, coordinates: Coordinates): Promise<Landmark[]> {
return new Promise((resolve, reject) => {
SearchService.search({
textFilter: text,
referenceCoordinates: coordinates,
onCompleteCallback: (err: GemError, results: Landmark[]) => {
if (err === GemError.success) {
resolve(results);
} else {
reject(new Error(`Search failed with error code: ${err}`));
}
}
});
});
}
// Usage
try {
const results = await searchAsync('restaurant', coordinates);
console.log('Results:', results);
} catch (error) {
console.error('Search error:', error);
}
Units of Measurement
The SDK primarily uses SI units for measurements, including:
- Meter (distance)
- Second (time)
- Kilogram (mass)
- Meters per second (speed)
- Watts for power (consumption)
The length value and measurement unit used in text-to-speech instructions are determined by the unit system configured through the SDK settings.
In certain cases, other units of measurement are used as they are better suited. For instance, truck profile dimensions may be measured in centimeters. These exceptions are clearly indicated in the API reference.
Working with Images
The SDK provides images in various contexts (landmark icons, navigation instructions, etc.). Images must be converted to displayable formats for web rendering.
Image Types
The SDK provides several image-related classes:
- Landmark Images: Icons for search results and map markers
- Navigation Images: Turn-by-turn instruction visuals
- Custom Images: User-provided images for markers or overlays
Getting Image Data
Use the getImage() method to retrieve image data:
import { Landmark, ImageFileFormat } from '@magiclane/maps-sdk';
// Get image data from a landmark
const imageData = landmark.getImage(
{ width: 40, height: 40 },
ImageFileFormat.png
);
if (imageData && imageData.byteLength > 0) {
// Image data is valid
const blob = new Blob(
[new Uint8Array(imageData.buffer as ArrayBuffer)],
{ type: 'image/png' }
);
const imageUrl = URL.createObjectURL(blob);
// Use the image
imgElement.src = imageUrl;
// Clean up when done
URL.revokeObjectURL(imageUrl);
}
Image Format Options
The SDK supports multiple image formats via the ImageFileFormat enum:
ImageFileFormat.png: PNG format (recommended for quality)ImageFileFormat.jpg: JPEG format (recommended for smaller file sizes)
// PNG format - better quality, larger size
const pngImage = landmark.getImage(
{ width: 100, height: 100 },
ImageFileFormat.png
);
// JPEG format - smaller size, compressed
const jpgImage = landmark.getImage(
{ width: 100, height: 100 },
ImageFileFormat.jpg
);
Best Practices for Image Handling
- Always Check Image Validity
function loadImage(landmark: Landmark, size: number): string | null {
try {
const imageData = landmark.getImage(
{ width: size, height: size },
ImageFileFormat.png
);
if (!imageData || imageData.byteLength === 0) {
console.warn('Invalid or empty image data');
return null;
}
const blob = new Blob(
[new Uint8Array(imageData.buffer as ArrayBuffer)],
{ type: 'image/png' }
);
return URL.createObjectURL(blob);
} catch (error) {
console.error('Failed to load image:', error);
return null;
}
}
- Clean Up Object URLs
Always revoke object URLs to prevent memory leaks:
// Store references for cleanup
const imageUrls: string[] = [];
function createImageUrl(imageData: ArrayBuffer): string {
const blob = new Blob([new Uint8Array(imageData)], { type: 'image/png' });
const url = URL.createObjectURL(blob);
imageUrls.push(url);
return url;
}
function cleanup() {
imageUrls.forEach(url => URL.revokeObjectURL(url));
imageUrls.length = 0;
}
// Call cleanup when component unmounts or images are no longer needed
cleanup();
- Use Appropriate Image Sizes
Request images at the size you need to avoid unnecessary processing:
// For thumbnail - small size
const thumbnail = landmark.getImage({ width: 40, height: 40 }, ImageFileFormat.png);
// For detail view - larger size
const detailImage = landmark.getImage({ width: 200, height: 200 }, ImageFileFormat.png);
- Handle Missing Images Gracefully
Provide fallback images or icons when SDK images are unavailable:
function displayLandmarkIcon(landmark: Landmark, container: HTMLElement) {
const imageData = landmark.getImage({ width: 40, height: 40 }, ImageFileFormat.png);
if (imageData && imageData.byteLength > 0) {
const blob = new Blob([new Uint8Array(imageData.buffer as ArrayBuffer)], { type: 'image/png' });
const img = document.createElement('img');
img.src = URL.createObjectURL(blob);
container.appendChild(img);
} else {
// Fallback to emoji or default icon
container.textContent = '📍';
container.style.fontSize = '24px';
}
}
- Optimize Image Loading for Lists
When displaying many images (e.g., search results), optimize loading:
function renderSearchResults(landmarks: Landmark[]) {
landmarks.forEach(landmark => {
// Create container
const item = document.createElement('div');
// Lazy load images
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
loadLandmarkImage(landmark, item);
observer.unobserve(entry.target);
}
});
});
observer.observe(item);
resultContainer.appendChild(item);
});
}
Common Image Scenarios
Displaying Landmark Icons in Search Results
function createSearchResultWithIcon(landmark: Landmark): HTMLElement {
const container = document.createElement('div');
const iconDiv = document.createElement('div');
iconDiv.style.cssText = 'width: 40px; height: 40px; flex-shrink: 0;';
try {
const imageData = landmark.getImage({ width: 40, height: 40 }, ImageFileFormat.png);
if (imageData && imageData.byteLength > 0) {
const blob = new Blob([new Uint8Array(imageData.buffer as ArrayBuffer)], { type: 'image/png' });
const img = document.createElement('img');
img.src = URL.createObjectURL(blob);
img.style.cssText = 'width: 100%; height: 100%; object-fit: contain;';
iconDiv.appendChild(img);
} else {
iconDiv.textContent = '📍';
}
} catch {
iconDiv.textContent = '📍';
}
container.appendChild(iconDiv);
return container;
}
Caching Images by UID
If images have unique identifiers, cache them to avoid redundant requests:
const imageCache = new Map<string, string>();
function getCachedImage(landmark: Landmark): string | null {
const cacheKey = `${landmark.name}_${landmark.coordinates?.latitude}_${landmark.coordinates?.longitude}`;
if (imageCache.has(cacheKey)) {
return imageCache.get(cacheKey)!;
}
const imageData = landmark.getImage({ width: 40, height: 40 }, ImageFileFormat.png);
if (imageData && imageData.byteLength > 0) {
const blob = new Blob([new Uint8Array(imageData.buffer as ArrayBuffer)], { type: 'image/png' });
const url = URL.createObjectURL(blob);
imageCache.set(cacheKey, url);
return url;
}
return null;
}
// Clear cache when needed
function clearImageCache() {
imageCache.forEach(url => URL.revokeObjectURL(url));
imageCache.clear();
}
Browser Compatibility
The TypeScript SDK is designed for modern web browsers and requires:
- WebGL Support: Required for map rendering
- WebAssembly Support: Required for SDK core functionality
- Modern JavaScript Features: ES6+ support
// Check for WebGL support
function checkWebGLSupport(): boolean {
try {
const canvas = document.createElement('canvas');
return !!(
canvas.getContext('webgl') ||
canvas.getContext('experimental-webgl')
);
} catch (e) {
return false;
}
}
if (!checkWebGLSupport()) {
console.error('WebGL is not supported in this browser');
}
Memory Management
Proper Resource Cleanup
Always clean up resources when they are no longer needed to prevent memory leaks:
// Release map resources when done
if (gemMap) {
gemMap.release();
gemMap = null;
}
// Clean up view containers
const container = document.getElementById('map-container');
if (container) {
container.innerHTML = '';
}
Avoid Memory Leaks
- Remove event listeners when components are destroyed
- Clear intervals and timeouts
- Release object URLs created with
URL.createObjectURL()
// Store blob URL for cleanup
const imageBlob = new Blob([imageData], { type: 'image/png' });
const imageUrl = URL.createObjectURL(imageBlob);
// Use the image
img.src = imageUrl;
// Clean up when done
URL.revokeObjectURL(imageUrl);
Async/Await Best Practices
The SDK initialization and some operations are asynchronous. Use async/await for cleaner code:
async function initializeApp() {
try {
// Initialize GemKit
const gemKit = await GemKit.initialize('your-api-token');
// Wait for position service
await PositionService.instance;
// Create map view
const wrapper = gemKit.createView(0, (gemMap: GemMap) => {
// Map is ready
console.log('Map initialized');
});
} catch (error) {
console.error('Initialization failed:', error);
}
}
// Call on DOM ready
window.addEventListener('DOMContentLoaded', initializeApp);
TypeScript Type Safety
Leverage TypeScript's type system for safer code:
import { Coordinates, Landmark, GemMap } from '@magiclane/maps-sdk';
// Use proper types for function parameters
function centerOnLandmark(map: GemMap, landmark: Landmark): void {
if (landmark.coordinates) {
map.centerOnCoordinates(landmark.coordinates, { zoomLevel: 70 });
}
}
// Use interfaces for configuration objects
interface SearchConfig {
text: string;
coordinates: Coordinates;
maxResults?: number;
}
function performSearch(config: SearchConfig): void {
// Implementation
}
Debugging and Logging
Enable Console Logging
Use browser console for debugging:
// Enable verbose logging during development
const DEBUG = true;
function log(message: string, data?: any) {
if (DEBUG) {
console.log(`[SDK] ${message}`, data || '');
}
}
// Usage
log('Map initialized', { viewId: 0 });
log('Search completed', results);
Browser Developer Tools
Use browser developer tools for:
- Network monitoring (API calls, resource loading)
- Performance profiling
- Memory leak detection
- Console error tracking
Error Reporting Best Practices
When reporting issues, please provide the following information:
- Clear and concise description of the bug or issue
- Steps to reproduce the issue, including a minimal code sample
- Expected behavior versus actual behavior
- Browser name and version (e.g., Chrome 120, Firefox 121, Safari 17)
- Operating system (e.g., Windows 11, macOS 14, Ubuntu 22.04)
- Console errors and warnings
- Network errors (check browser DevTools Network tab)
- SDK version you are using
- Screenshots or videos to help visualize the issue (if applicable)
- Geographical location where the issue occurred (for routing, navigation, or search issues)
Module Bundlers and Build Tools
The SDK works with modern bundlers like Webpack, Vite, and Rollup:
Vite Configuration
// vite.config.ts
import { defineConfig } from 'vite';
export default defineConfig({
optimizeDeps: {
exclude: ['@magiclane/maps-sdk']
},
server: {
headers: {
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp'
}
}
});
Webpack Configuration
// webpack.config.js
module.exports = {
resolve: {
extensions: ['.ts', '.js']
},
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
}
};
Avoid Name Conflicts
Import only what you need to avoid namespace pollution:
// Good - specific imports
import { GemKit, GemMap, Coordinates } from '@magiclane/maps-sdk';
// Avoid - entire module import
import * as SDK from '@magiclane/maps-sdk';
Cross-Origin Isolation
For optimal performance and security, serve your application with proper headers:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
These headers are required for SharedArrayBuffer and high-resolution timers.
Legal Requirements
Some features, such as police and speed camera reports, may be illegal in certain countries. Ensure that safety overlays and social reports are disabled where applicable based on local regulations.
The Magic Lane TypeScript SDK utilizes data from OpenStreetMap. Please ensure proper attribution is given in accordance with the OpenStreetMap license.
Any use or display of Wikipedia content must include appropriate attribution, as outlined in the Reusers' rights and obligations section of Wikipedia's copyright guidelines.
Performance Optimization
Debounce Frequent Updates
For operations triggered frequently (e.g., map movements), use debouncing:
function debounce<T extends (...args: any[]) => void>(
func: T,
wait: number
): (...args: Parameters<T>) => void {
let timeout: number | undefined;
return (...args: Parameters<T>) => {
clearTimeout(timeout);
timeout = window.setTimeout(() => func(...args), wait);
};
}
// Usage
const debouncedSearch = debounce((text: string) => {
performSearch(text);
}, 300);
searchInput.addEventListener('input', (e) => {
debouncedSearch((e.target as HTMLInputElement).value);
});
Optimize Image Loading
Load images efficiently to prevent memory issues:
function loadLandmarkImage(landmark: Landmark, size: number): string | null {
try {
const imageData = landmark.getImage(
{ width: size, height: size },
ImageFileFormat.png
);
if (imageData && imageData.byteLength > 0) {
const blob = new Blob(
[new Uint8Array(imageData.buffer as ArrayBuffer)],
{ type: 'image/png' }
);
return URL.createObjectURL(blob);
}
} catch (error) {
console.error('Failed to load image:', error);
}
return null;
}
Security Best Practices
Protect API Tokens
Never expose your API token in client-side code for production:
// Development only - use environment variables
const API_TOKEN = import.meta.env.VITE_GEM_TOKEN;
// Production - fetch token from your backend
async function getApiToken(): Promise<string> {
const response = await fetch('/api/get-token');
const data = await response.json();
return data.token;
}
Sanitize User Input
Always sanitize user input before using it in searches or displays:
function sanitizeInput(input: string): string {
return input.trim().replace(/[<>]/g, '');
}
const userInput = sanitizeInput(searchBox.value);
performSearch(userInput);