Events
The SDK provides a complete event system that can listen to various map interactions and state changes.
Event Listening
Add Event Listeners
// Listen to events
sdk.on("click", (e) => {
console.log("Click event:", e);
});
// Listen to events on specific layer
sdk.on("click", "airport", (e) => {
console.log("Clicked airport layer:", e.features[0]);
});Remove Event Listeners
// Define event handler function
const handler = (e) => {
console.log("Click event:", e);
};
// Add listener
sdk.on("click", handler);
// Remove listener
sdk.off("click", handler);
// Remove all listeners
sdk.off("click");One-time Event Listeners
// Listen only once
sdk.once("loadComplete", () => {
console.log("Map loaded (executed once)");
});Map Lifecycle Events
load (Style Loading Complete)
sdk.on("load", () => {
console.log("Map style loaded");
// At this point, map styles can be safely manipulated
});loadComplete (Initialization Complete)
sdk.on("loadComplete", (event) => {
console.log("Map initialization complete");
console.log("NOTAM manager:", event.notam);
console.log("ADSB manager:", event.adsb);
// At this point, all features are ready
setupFeatures();
});reinit (Reinitialization)
sdk.on("reinit", (event) => {
console.log("Map reinitialization complete");
console.log("New period:", event.period);
});error (Error Event)
sdk.on("error", (error) => {
console.error("Map error:", error);
});Interaction Events
Mouse Events
click (Click)
Basic click event listening:
sdk.on("click", (e) => {
console.log("Click position:", e.lngLat);
console.log("Clicked features:", e.features);
console.log("Click pixel position:", e.point);
});
// Listen to specific layer click
sdk.on("click", "airport", (e) => {
const feature = e.features[0];
console.log("Clicked airport:", feature.properties);
});clickLayer (Click Layer)
The clickLayer method is specifically designed to listen for click events on specific layers, supporting multiple layer selection methods and automatically filtering to return only features from the specified layers.
Syntax:
sdk.clickLayer(layers, callback)Parameters:
layers(LayerRef): Layer identifier, supports the following types:string: Single layer ID, e.g.,"airport"Array<string>: Multiple layer IDs, e.g.,["airport", "airline"]RegExp: Regular expression to match layer IDs, e.g.,/^airport.*/Function: Function to filter layers, e.g.,(layer) => layer.id.startsWith("airport")
callback(Function): Callback function that receives event objecte, containing:e.lngLat: Geographic coordinates of the click{ lng, lat }e.point: Pixel coordinates of the click{ x, y }e.features: Array of clicked features (only includes features from specified layers)e.originalEvent: Original mouse event object
Returns: Returns an unregister function that can be called to remove the event listener.
Examples:
// Listen to single layer
const offHandler = sdk.clickLayer("airport", (e) => {
console.log("Clicked airport layer");
console.log("Feature:", e.features[0]);
console.log("Position:", e.lngLat);
});
// Listen to multiple layers
sdk.clickLayer(["airport", "airline", "airline_label"], (e) => {
const feature = e.features[0];
console.log("Clicked layer:", feature.layer.id);
console.log("Feature properties:", feature.properties);
});
// Use regular expression to match layers
sdk.clickLayer(/^airport.*/, (e) => {
console.log("Clicked layer starting with 'airport'");
});
// Use function to filter layers
sdk.clickLayer((layer) => {
return layer.type === "symbol" && layer.id.includes("label");
}, (e) => {
console.log("Clicked symbol label layer");
});
// Remove event listener
offHandler();Difference from sdk.on("click", layer, callback):
clickLayerautomatically filters and returns only features from specified layersclickLayersupports more flexible layer selection methods (array, regex, function)clickLayerreturns an unregister function for easier event lifecycle management
clickPopup (Click Popup)
The clickPopup method is used to automatically display a popup when clicking on layer features, supporting custom HTML content and asynchronous data loading.
Syntax:
sdk.clickPopup(layers, htmlFunc, popupOptions)Parameters:
layers(LayerRef): Layer identifier, same asclickLayer'slayersparameterhtmlFunc(HtmlFunction): HTML generation function that receives two parameters:feature: The clicked feature objectpopup: Popup instance (can be used to dynamically update content)- Returns
stringorPromise<string>(supports asynchronous loading)
popupOptions(PopupOptions, optional): Popup configuration options, such as:anchor: Popup anchor position ("center","top","bottom","left","right","top-left","top-right","bottom-left","bottom-right")closeButton: Whether to show close button (defaulttrue)closeOnClick: Whether to close on map click (defaulttrue)maxWidth: Maximum width- More options refer to MapLibre Popup documentation
Returns: Returns an unregister function that can be called to remove the event listener and popup.
Examples:
// Basic usage: Display static HTML
sdk.clickPopup("airport", (feature) => {
const props = feature.properties;
return `
<div style="padding: 10px;">
<h3>${props.name || "Airport"}</h3>
<p>ICAO: ${props.icao || "N/A"}</p>
<p>IATA: ${props.iata || "N/A"}</p>
</div>
`;
});
// Custom popup position and style
sdk.clickPopup("airport", (feature) => {
return `<div>${feature.properties.name}</div>`;
}, {
anchor: "left",
closeButton: true,
maxWidth: "300px"
});
// Asynchronous data loading
sdk.clickPopup("airport", async (feature) => {
const airportId = feature.properties.id;
// Loading state is automatically handled by SDK
const response = await fetch(`/api/airport/${airportId}`);
const data = await response.json();
return `
<div style="padding: 10px;">
<h3>${data.name}</h3>
<p>Status: ${data.status}</p>
<p>Runways: ${data.runways.length}</p>
</div>
`;
});
// Use popup instance to dynamically update content
sdk.clickPopup("airport", async (feature, popup) => {
// Initial content
popup.setHTML('<div>Loading...</div>');
// Asynchronous loading
const data = await loadAirportData(feature.properties.id);
// Update content
return `
<div>
<h3>${data.name}</h3>
<button onclick="alert('Details')">View Details</button>
</div>
`;
});
// Listen to multiple layers
sdk.clickPopup(["airport", "airline"], (feature) => {
const layerId = feature.layer.id;
if (layerId === "airport") {
return `<div>Airport: ${feature.properties.name}</div>`;
} else {
return `<div>Airline: ${feature.properties.name}</div>`;
}
});
// Remove popup listener
const offPopup = sdk.clickPopup("airport", (f) => `<div>${f.properties.name}</div>`);
// Remove later
offPopup();Error Handling: If htmlFunc throws an error or returns a rejected Promise, the popup will automatically display an error message.
Best Practices:
// 1. Set popup after loadComplete
sdk.on("loadComplete", () => {
sdk.clickPopup("airport", async (feature) => {
// Safely access map and features
return generatePopupHTML(feature);
});
});
// 2. Use debouncing for frequent clicks
let popupTimeout;
sdk.clickPopup("airport", (feature) => {
clearTimeout(popupTimeout);
return new Promise((resolve) => {
popupTimeout = setTimeout(async () => {
const html = await loadData(feature);
resolve(html);
}, 300);
});
});
// 3. Unified popup lifecycle management
const popupHandlers = [];
sdk.on("loadComplete", () => {
// Add multiple popups
popupHandlers.push(
sdk.clickPopup("airport", (f) => generateAirportPopup(f)),
sdk.clickPopup("airline", (f) => generateAirlinePopup(f))
);
});
// Clean up on page unload
window.addEventListener("beforeunload", () => {
popupHandlers.forEach(off => off());
});dblclick (Double Click)
sdk.on("dblclick", (e) => {
// Double click to zoom in
sdk.zoomIn();
});contextmenu (Context Menu)
sdk.on("contextmenu", (e) => {
e.preventDefault();
showContextMenu(e.lngLat);
});mouseenter (Mouse Enter)
sdk.on("mouseenter", "airport", (e) => {
sdk.getCanvas().style.cursor = "pointer";
highlightFeature(e.features[0]);
});mouseleave (Mouse Leave)
sdk.on("mouseleave", "airport", () => {
sdk.getCanvas().style.cursor = "";
clearHighlight();
});mousemove (Mouse Move)
sdk.on("mousemove", (e) => {
updateMousePosition(e.lngLat);
});Touch Events (Mobile)
touchstart (Touch Start)
sdk.on("touchstart", (e) => {
console.log("Touch started");
});touchmove (Touch Move)
sdk.on("touchmove", (e) => {
console.log("Touch moving");
});touchend (Touch End)
sdk.on("touchend", (e) => {
console.log("Touch ended");
});Map State Events
move (Move)
sdk.on("move", () => {
const center = sdk.getCenter();
console.log("Map center:", center);
});moveend (Move End)
sdk.on("moveend", () => {
const bounds = sdk.getBounds();
console.log("Map bounds:", bounds);
// Load data in current view
loadDataInBounds(bounds);
});movestart (Move Start)
sdk.on("movestart", () => {
console.log("Map movement started");
});zoom (Zoom)
sdk.on("zoom", () => {
const zoom = sdk.getZoom();
console.log("Zoom level:", zoom);
// Adjust layers based on zoom level
if (zoom > 12) {
sdk.showLayers(["detail-layer"]);
} else {
sdk.hideLayers(["detail-layer"]);
}
});zoomend (Zoom End)
sdk.on("zoomend", () => {
const zoom = sdk.getZoom();
console.log("Zoom ended, current level:", zoom);
});zoomstart (Zoom Start)
sdk.on("zoomstart", () => {
console.log("Zoom started");
});rotate (Rotate)
sdk.on("rotate", () => {
const bearing = sdk.getBearing();
console.log("Rotation angle:", bearing);
});rotatestart (Rotate Start)
sdk.on("rotatestart", () => {
console.log("Rotation started");
});rotateend (Rotate End)
sdk.on("rotateend", () => {
console.log("Rotation ended");
});pitch (Pitch)
sdk.on("pitch", () => {
const pitch = sdk.getPitch();
console.log("Pitch angle:", pitch);
});Drag Events
dragstart (Drag Start)
sdk.on("dragstart", () => {
console.log("Drag started");
});drag (Dragging)
sdk.on("drag", () => {
console.log("Dragging");
});dragend (Drag End)
sdk.on("dragend", () => {
console.log("Drag ended");
});Data Events
data (Data Load)
sdk.on("data", (e) => {
console.log("Data loaded:", e.dataType);
console.log("Source ID:", e.sourceId);
console.log("Is source loaded:", e.isSourceLoaded);
});sourcedata (Source Data Load)
sdk.on("sourcedata", (e) => {
if (e.sourceId === "custom-source" && e.isSourceLoaded) {
console.log("Source data loaded");
}
});styledata (Style Data Load)
sdk.on("styledata", (e) => {
console.log("Style data loaded:", e.dataType);
});Rendering Events
render (Render)
sdk.on("render", () => {
// Triggered every time map renders
// Note: This event fires very frequently, avoid complex operations
});idle (Idle)
sdk.on("idle", () => {
// Map is in idle state (no animation, no loading)
console.log("Map idle");
});Custom Events
Trigger Custom Events
// Trigger custom event
sdk.map.fire("custom-event", {
data: "Custom data",
});
// Listen to custom event
sdk.on("custom-event", (e) => {
console.log("Custom event:", e.data);
});Event Objects
MapMouseEvent
Mouse event objects contain the following properties:
sdk.on("click", (e) => {
console.log("Position:", e.lngLat); // { lng, lat }
console.log("Pixel position:", e.point); // { x, y }
console.log("Original event:", e.originalEvent);
console.log("Target:", e.target);
console.log("Type:", e.type);
});MapTouchEvent
Touch event objects:
sdk.on("touchstart", (e) => {
console.log("Touch point:", e.point);
console.log("Touch position:", e.lngLat);
console.log("Original event:", e.originalEvent);
});Event Handling Best Practices
1. Event Cleanup
// Save event handler references
const handlers = {
click: (e) => console.log("click", e),
zoom: () => console.log("zoom"),
};
// Add listeners
Object.entries(handlers).forEach(([event, handler]) => {
sdk.on(event, handler);
});
// Clean up on page unload
window.addEventListener("beforeunload", () => {
Object.entries(handlers).forEach(([event, handler]) => {
sdk.off(event, handler);
});
});2. Debouncing
// Use debouncing for frequently triggered events
let zoomTimeout;
sdk.on("zoom", () => {
clearTimeout(zoomTimeout);
zoomTimeout = setTimeout(() => {
// Execute operation
updateUI();
}, 300);
});3. Event Delegation
// Use event delegation to handle multiple layers
sdk.on("click", (e) => {
const features = e.features;
if (features.length > 0) {
const feature = features[0];
const layerId = feature.layer.id;
switch (layerId) {
case "airport":
handleAirportClick(feature);
break;
case "airline":
handleAirlineClick(feature);
break;
default:
handleDefaultClick(feature);
}
}
});Complete Example
let sdk;
async function initMap() {
sdk = new navMap.MapSDK({
container: "map",
center: [116.39, 39.9],
zoom: 10,
});
setupEventHandlers();
}
function setupEventHandlers() {
// 1. Lifecycle events
sdk.on("load", () => {
console.log("Map style loaded");
});
sdk.on("loadComplete", (event) => {
console.log("Map initialization complete");
setupInteractions();
});
// 2. Interaction events
sdk.on("click", (e) => {
console.log("Click position:", e.lngLat);
const features = sdk.queryRenderedFeatures(e.point);
if (features.length > 0) {
showFeatureInfo(features[0]);
}
});
// 3. State events
sdk.on("zoom", () => {
const zoom = sdk.getZoom();
updateZoomIndicator(zoom);
// Adjust layers based on zoom level
if (zoom > 12) {
sdk.showLayers(["detail-layer"]);
} else {
sdk.hideLayers(["detail-layer"]);
}
});
sdk.on("moveend", () => {
const bounds = sdk.getBounds();
loadDataInBounds(bounds);
});
// 4. Error handling
sdk.on("error", (error) => {
console.error("Map error:", error);
showErrorMessage(error);
});
}
function setupInteractions() {
// Layer-specific events
sdk.on("mouseenter", "airport", (e) => {
sdk.getCanvas().style.cursor = "pointer";
highlightFeature(e.features[0]);
});
sdk.on("mouseleave", "airport", () => {
sdk.getCanvas().style.cursor = "";
clearHighlight();
});
}
initMap();Notes
- Event Timing: Ensure interaction events are added after map
loadComplete - Performance Considerations: Avoid complex operations in frequently triggered events
- Memory Management: Promptly remove unnecessary event listeners to avoid memory leaks
- Event Order: Understanding event trigger order helps handle logic correctly
- Mobile Adaptation: Mobile devices should prioritize touch events over mouse events
