Skip to main content

Highlight Selected Features

Select Places to highlight by holding Shift and dragging a selection box over them.

<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <link rel="stylesheet" href="https://maps-sdk.trimblemaps.com/v4/trimblemaps-4.2.3.css" />
        <script src="https://maps-sdk.trimblemaps.com/v4/trimblemaps-4.2.3.js"></script>
        <style>
            body { margin: 0; padding: 0; }

            #map {
                position: absolute;
                top: 0;
                bottom: 0;
                width: 100%;
            }

            .boxdraw {
                background: rgba(56, 135, 190, 0.1);
                border: 2px solid #3887be;
                position: absolute;
                top: 0;
                left: 0;
                width: 0;
                height: 0;
            }
      </style>
      </head>
      <body>
          <div id="map"></div>

      <script>
        TrimbleMaps.setAPIKey('YOUR_API_KEY_HERE');
  // Initialize the Trimble Map
  const map = new TrimbleMaps.Map({
    container: 'map',
    style: TrimbleMaps.Common.Style.TRANSPORTATION,
    center: [-98, 38.88],
    zoom: 3,
  });

  let selectedFeatureIds = [];

  map.on('load', async () => {
    // Load GeoJSON data (US States with geometry and properties)
    const response = await fetch('https://developer.trimblemaps.com/maps-sdk/assets/us_states.geojson');
    const data = await response.json();

    // Assign a unique ID to each feature for feature-state tracking
    data.features.forEach((feature, i) => {
      feature.id = i + 1;
    });

    // Add GeoJSON source to the map
    map.addSource('states', {
      type: 'geojson',
      data: data
    });

    // Fill Layer: default and selected state styling
    map.addLayer({
      id: 'state-fills',
      type: 'fill',
      source: 'states',
      paint: {
        'fill-color': [
          'case',
          ['boolean', ['feature-state', 'selected'], false],
          '#e55e5e',  // Selected state fill
          '#627BC1'   // Default state fill
        ],
        'fill-opacity': 0.6
      }
    });

    // Border Layer: state outlines
    map.addLayer({
      id: 'state-borders',
      type: 'line',
      source: 'states',
      paint: {
        'line-color': '#627BC1',
        'line-width': 2
      }
    });

    // ------------------- Box Selection Implementation -------------------

    const canvas = map.getCanvasContainer();
    let start, current, box;

    // Get mouse position relative to canvas
    function mousePos(e) {
      const rect = map.getCanvas().getBoundingClientRect();
      return {
        x: e.clientX - rect.left - map.getCanvas().clientLeft,
        y: e.clientY - rect.top - map.getCanvas().clientTop
      };
    }

    // Handle Shift + Mouse Down to begin box selection
    function mouseDown(e) {
      if (!(e.shiftKey && e.button === 0)) return;
      e.preventDefault();
      map.dragPan.disable();

      start = mousePos(e);
      document.addEventListener('mousemove', onMouseMove);
      document.addEventListener('mouseup', onMouseUp);
      document.addEventListener('keydown', onKeyDown);
    }

    // Draw the box as mouse moves
    function onMouseMove(e) {
      current = mousePos(e);
      if (!box) {
        box = document.createElement('div');
        box.classList.add('boxdraw');
        canvas.appendChild(box);
      }

      const minX = Math.min(start.x, current.x),
            maxX = Math.max(start.x, current.x),
            minY = Math.min(start.y, current.y),
            maxY = Math.max(start.y, current.y);

      box.style.transform = `translate(${minX}px, ${minY}px)`;
      box.style.width = `${maxX - minX}px`;
      box.style.height = `${maxY - minY}px`;
    }

    // Finalize selection on mouse up
    function onMouseUp(e) {
      finish([start, mousePos(e)]);
    }

    // Cancel box selection on Escape key
    function onKeyDown(e) {
      if (e.key === 'Escape') finish();
    }

    // Finalize and apply selected state styling
    function finish(bbox) {
      document.removeEventListener('mousemove', onMouseMove);
      document.removeEventListener('mouseup', onMouseUp);
      document.removeEventListener('keydown', onKeyDown);

      if (box) {
        box.parentNode.removeChild(box);
        box = null;
      }

      if (bbox) {
        const features = map.queryRenderedFeatures(bbox, {
          layers: ['state-fills']
        });

        // Reset previously selected states
        selectedFeatureIds.forEach(id => {
          map.setFeatureState({ source: 'states', id }, { selected: false });
        });
        selectedFeatureIds = [];

        // Highlight selected states
        features.forEach(f => {
          if (f.id !== undefined) {
            map.setFeatureState({ source: 'states', id: f.id }, { selected: true });
            selectedFeatureIds.push(f.id);
          }
        });
      }

      map.dragPan.enable();
    }

    // Register mousedown listener for selection
    canvas.addEventListener('mousedown', mouseDown, true);
  });
        </script>
    </body>
</html>
Last updated June 26, 2025.