Skip to content

Interactive Features

Click a city to show a popup with details. Demonstrates layer-specific events, reactive state with @tracked, and standalone popups.

gts
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import type { MapMouseEvent, MapGeoJSONFeature } from 'maplibre-gl';
import MapLibreGL from 'ember-maplibre-gl/components/maplibre-gl';
import { NavigationControl } from 'maplibre-gl';

const nav = new NavigationControl();

const mapOptions = {
  style: 'https://tiles.openfreemap.org/styles/bright',
  center: [-98, 39],
  zoom: 3.5,
};

const source = {
  type: 'geojson',
  data: {
    type: 'FeatureCollection',
    features: [
      { type: 'Feature', geometry: { type: 'Point', coordinates: [-122.42, 37.78] }, properties: { name: 'San Francisco', state: 'California', pop: '874K' } },
      { type: 'Feature', geometry: { type: 'Point', coordinates: [-73.94, 40.67] }, properties: { name: 'New York', state: 'New York', pop: '8.3M' } },
      { type: 'Feature', geometry: { type: 'Point', coordinates: [-87.63, 41.88] }, properties: { name: 'Chicago', state: 'Illinois', pop: '2.7M' } },
      { type: 'Feature', geometry: { type: 'Point', coordinates: [-118.24, 34.05] }, properties: { name: 'Los Angeles', state: 'California', pop: '4.0M' } },
      { type: 'Feature', geometry: { type: 'Point', coordinates: [-95.37, 29.76] }, properties: { name: 'Houston', state: 'Texas', pop: '2.3M' } },
      { type: 'Feature', geometry: { type: 'Point', coordinates: [-104.99, 39.74] }, properties: { name: 'Denver', state: 'Colorado', pop: '716K' } },
      { type: 'Feature', geometry: { type: 'Point', coordinates: [-90.07, 29.95] }, properties: { name: 'New Orleans', state: 'Louisiana', pop: '383K' } },
      { type: 'Feature', geometry: { type: 'Point', coordinates: [-71.06, 42.36] }, properties: { name: 'Boston', state: 'Massachusetts', pop: '675K' } },
      { type: 'Feature', geometry: { type: 'Point', coordinates: [-122.33, 47.61] }, properties: { name: 'Seattle', state: 'Washington', pop: '737K' } },
      { type: 'Feature', geometry: { type: 'Point', coordinates: [-80.19, 25.76] }, properties: { name: 'Miami', state: 'Florida', pop: '442K' } },
    ],
  },
};

const circleLayer = {
  id: 'cities',
  type: 'circle',
  paint: {
    'circle-radius': 8,
    'circle-color': '#4264fb',
    'circle-stroke-color': '#fff',
    'circle-stroke-width': 2,
  },
};

const labelLayer = {
  type: 'symbol',
  layout: {
    'text-field': ['get', 'name'],
    'text-size': 12,
    'text-offset': [0, 1.5],
    'text-anchor': 'top',
  },
  paint: {
    'text-color': '#333',
    'text-halo-color': '#fff',
    'text-halo-width': 1.5,
  },
};

export default class InteractiveDemo extends Component {
  @tracked popupLngLat: [number, number] | null = null;
  @tracked popupName = '';
  @tracked popupState = '';
  @tracked popupPop = '';

  onCityClick = (e: MapMouseEvent & { features?: MapGeoJSONFeature[] }) => {
    const feature = e.features?.[0];
    if (!feature) return;
    this.popupLngLat = feature.geometry.coordinates.slice();
    this.popupName = feature.properties.name;
    this.popupState = feature.properties.state;
    this.popupPop = feature.properties.pop;
  };

  <template>
    <MapLibreGL
      @initOptions={{mapOptions}}
      style="height: 500px; width: 100%; border-radius: 8px;"
    as |map|>
      <map.control @control={{nav}} @position="top-right" />
      <map.source @options={{source}} as |source|>
        <source.layer @options={{circleLayer}} />
        <source.layer @options={{labelLayer}} />
      </map.source>
      <map.on @event="click" @layerId="cities" @action={{this.onCityClick}} />
      {{#if this.popupLngLat}}
        <map.popup @lngLat={{this.popupLngLat}}>
          <div style="padding: 8px 12px; font-family: system-ui;">
            <strong style="font-size: 15px;">{{this.popupName}}</strong>
            <div style="color: #666; margin-top: 4px;">{{this.popupState}}</div>
            <div style="margin-top: 4px;">Population: <strong>{{this.popupPop}}</strong></div>
          </div>
        </map.popup>
      {{/if}}
    </MapLibreGL>
  </template>
}