Skip to content

Events

The SDK provides a complete event system that can listen to various map interactions and state changes.

Event Listening

Add Event Listeners

javascript
// 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

javascript
// 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

javascript
// Listen only once
sdk.once("loadComplete", () => {
  console.log("Map loaded (executed once)");
});

Map Lifecycle Events

load (Style Loading Complete)

javascript
sdk.on("load", () => {
  console.log("Map style loaded");
  // At this point, map styles can be safely manipulated
});

loadComplete (Initialization Complete)

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

javascript
sdk.on("reinit", (event) => {
  console.log("Map reinitialization complete");
  console.log("New period:", event.period);
});

error (Error Event)

javascript
sdk.on("error", (error) => {
  console.error("Map error:", error);
});

Interaction Events

Mouse Events

click (Click)

Basic click event listening:

javascript
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:

javascript
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 object e, 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:

javascript
// 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):

  • clickLayer automatically filters and returns only features from specified layers
  • clickLayer supports more flexible layer selection methods (array, regex, function)
  • clickLayer returns 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:

javascript
sdk.clickPopup(layers, htmlFunc, popupOptions)

Parameters:

  • layers (LayerRef): Layer identifier, same as clickLayer's layers parameter
  • htmlFunc (HtmlFunction): HTML generation function that receives two parameters:
    • feature: The clicked feature object
    • popup: Popup instance (can be used to dynamically update content)
    • Returns string or Promise<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 (default true)
    • closeOnClick: Whether to close on map click (default true)
    • 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:

javascript
// 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:

javascript
// 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)

javascript
sdk.on("dblclick", (e) => {
  // Double click to zoom in
  sdk.zoomIn();
});

contextmenu (Context Menu)

javascript
sdk.on("contextmenu", (e) => {
  e.preventDefault();
  showContextMenu(e.lngLat);
});

mouseenter (Mouse Enter)

javascript
sdk.on("mouseenter", "airport", (e) => {
  sdk.getCanvas().style.cursor = "pointer";
  highlightFeature(e.features[0]);
});

mouseleave (Mouse Leave)

javascript
sdk.on("mouseleave", "airport", () => {
  sdk.getCanvas().style.cursor = "";
  clearHighlight();
});

mousemove (Mouse Move)

javascript
sdk.on("mousemove", (e) => {
  updateMousePosition(e.lngLat);
});

Touch Events (Mobile)

touchstart (Touch Start)

javascript
sdk.on("touchstart", (e) => {
  console.log("Touch started");
});

touchmove (Touch Move)

javascript
sdk.on("touchmove", (e) => {
  console.log("Touch moving");
});

touchend (Touch End)

javascript
sdk.on("touchend", (e) => {
  console.log("Touch ended");
});

Map State Events

move (Move)

javascript
sdk.on("move", () => {
  const center = sdk.getCenter();
  console.log("Map center:", center);
});

moveend (Move End)

javascript
sdk.on("moveend", () => {
  const bounds = sdk.getBounds();
  console.log("Map bounds:", bounds);
  
  // Load data in current view
  loadDataInBounds(bounds);
});

movestart (Move Start)

javascript
sdk.on("movestart", () => {
  console.log("Map movement started");
});

zoom (Zoom)

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

javascript
sdk.on("zoomend", () => {
  const zoom = sdk.getZoom();
  console.log("Zoom ended, current level:", zoom);
});

zoomstart (Zoom Start)

javascript
sdk.on("zoomstart", () => {
  console.log("Zoom started");
});

rotate (Rotate)

javascript
sdk.on("rotate", () => {
  const bearing = sdk.getBearing();
  console.log("Rotation angle:", bearing);
});

rotatestart (Rotate Start)

javascript
sdk.on("rotatestart", () => {
  console.log("Rotation started");
});

rotateend (Rotate End)

javascript
sdk.on("rotateend", () => {
  console.log("Rotation ended");
});

pitch (Pitch)

javascript
sdk.on("pitch", () => {
  const pitch = sdk.getPitch();
  console.log("Pitch angle:", pitch);
});

Drag Events

dragstart (Drag Start)

javascript
sdk.on("dragstart", () => {
  console.log("Drag started");
});

drag (Dragging)

javascript
sdk.on("drag", () => {
  console.log("Dragging");
});

dragend (Drag End)

javascript
sdk.on("dragend", () => {
  console.log("Drag ended");
});

Data Events

data (Data Load)

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

javascript
sdk.on("sourcedata", (e) => {
  if (e.sourceId === "custom-source" && e.isSourceLoaded) {
    console.log("Source data loaded");
  }
});

styledata (Style Data Load)

javascript
sdk.on("styledata", (e) => {
  console.log("Style data loaded:", e.dataType);
});

Rendering Events

render (Render)

javascript
sdk.on("render", () => {
  // Triggered every time map renders
  // Note: This event fires very frequently, avoid complex operations
});

idle (Idle)

javascript
sdk.on("idle", () => {
  // Map is in idle state (no animation, no loading)
  console.log("Map idle");
});

Custom Events

Trigger Custom Events

javascript
// 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:

javascript
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:

javascript
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

javascript
// 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

javascript
// Use debouncing for frequently triggered events
let zoomTimeout;

sdk.on("zoom", () => {
  clearTimeout(zoomTimeout);
  zoomTimeout = setTimeout(() => {
    // Execute operation
    updateUI();
  }, 300);
});

3. Event Delegation

javascript
// 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

javascript
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

  1. Event Timing: Ensure interaction events are added after map loadComplete
  2. Performance Considerations: Avoid complex operations in frequently triggered events
  3. Memory Management: Promptly remove unnecessary event listeners to avoid memory leaks
  4. Event Order: Understanding event trigger order helps handle logic correctly
  5. Mobile Adaptation: Mobile devices should prioritize touch events over mouse events