Clusters and Popups

Data and layer configuration derived from MapLibre cluster Example.

Show popup on

Interactive map data

center: 0,0
zoom: 1
pitch: 0
bearing: 0
minZoom: 0
maxZoom: 22
bounds: undefined
svelte
<MapLibre
  style="https://basemaps.cartocdn.com/gl/positron-gl-style/style.json"
  class={mapClasses}
  standardControls
>
  <GeoJSON
    id="earthquakes"
    data={source}
    cluster={{
      radius: 500,
      maxZoom: 14,
      properties: {
        // Sum the `mag` property from all the points in each cluster.
        total_mag: ['+', ['get', 'mag']],
      },
    }}
  >
    <CircleLayer
      applyToClusters
      hoverCursor="pointer"
      paint={{
        // Use step expressions (https://maplibre.org/maplibre-gl-js-docs/style-spec/#expressions-step)
        // with three steps to implement three types of circles:
        //   * Blue, 20px circles when point count is less than 100
        //   * Yellow, 30px circles when point count is between 100 and 750
        //   * Pink, 40px circles when point count is greater than or equal to 750
        'circle-color': ['step', ['get', 'point_count'], '#51bbd6', 100, '#f1f075', 750, '#f28cb1'],
        'circle-radius': ['step', ['get', 'point_count'], 20, 100, 30, 750, 40],
        'circle-stroke-color': '#f00',
        'circle-stroke-width': 1,
        'circle-stroke-opacity': hoverStateFilter(0, 1),
      }}
      on:click={(e) => (clickedFeature = e.detail.features?.[0]?.properties)}
    >
      <Popup {openOn} closeOnClickInside let:features>
        <ClusterPopup feature={features?.[0]} />
      </Popup>
    </CircleLayer>

    <SymbolLayer
      applyToClusters
      layout={{
        'text-field': [
          'format',
          ['get', 'point_count_abbreviated'],
          {},
          '\n',
          {},
          [
            'number-format',
            ['/', ['get', 'total_mag'], ['get', 'point_count']],
            {
              'max-fraction-digits': 2,
            },
          ],
          { 'font-scale': 0.8 },
        ],
        'text-size': 12,
        'text-offset': [0, -0.1],
      }}
    />

    <CircleLayer
      applyToClusters={false}
      hoverCursor="pointer"
      paint={{
        'circle-color': '#11b4da',
        'circle-radius': 4,
        'circle-stroke-width': 1,
        'circle-stroke-color': '#fff',
      }}
      on:click={(e) => (clickedFeature = e.detail.features?.[0]?.properties)}
    >
      <Popup {openOn} closeOnClickInside let:features>
        {@const props = features?.[0]?.properties}
        <p>
          Date: <span class="font-medium text-gray-800"
            >{new Date(props.time).toLocaleDateString()}</span
          >
        </p>
        <p>Magnitude: <span class="font-medium text-gray-800">{props.mag}</span></p>
        <p>
          Tsunami: <span class="font-medium text-gray-800">{props.tsunami ? 'Yes' : 'No'}</span>
        </p>
      </Popup>
    </CircleLayer>
  </GeoJSON>
</MapLibre>

{#if clickedFeature}
  {#if clickedFeature.cluster}
    <p>
      Number of Earthquakes:
      <span class="font-bold text-gray-800">{clickedFeature['point_count']}</span>
    </p>
    <p>
      Average Magnitude:
      <span class="font-bold text-gray-800">
        {(clickedFeature.total_mag / clickedFeature.point_count).toFixed(2)}
      </span>
    </p>
  {:else}
    <p>Magnitude: <span class="font-bold text-gray-800">{clickedFeature.mag}</span></p>
  {/if}
{/if}
svelte
<!-- File: ClusterPopup.svelte -->
<script lang="ts">
  import { mapContext } from '$lib/context.js';
  import type { Feature } from 'geojson';

  const { map, source } = mapContext();

  export let feature: Feature | undefined;

  let innerFeatures: Feature[] = [];
  $: if ($map && $source && feature) {
    $map
      ?.getSource($source)
      ?.getClusterLeaves(feature.properties.cluster_id, 10000, 0, (err, features) => {
        innerFeatures = features;
      });
  }

  $: innerFeatures.sort((a, b) => {
    return b.properties.time - a.properties.time;
  });
</script>

<p>Most recent quakes</p>
{#each innerFeatures.slice(0, 10) as feat}
  <div class="text-sm">
    {new Date(feat.properties.time).toLocaleDateString()} - {feat.properties.mag}
  </div>
{/each}

Back to Examples

Github