Custom Markers with Clusters

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']],
      },
    }}
  >
    <MarkerLayer
      applyToClusters
      interactive
      on:click={(e) => (clickedFeature = e.detail.feature?.properties)}
      let:feature
    >
      <div class="rounded-full bg-orange-200 p-1">
        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
          ><path
            fill="currentColor"
            d="M14 11.5A2.5 2.5 0 0 0 16.5 9A2.5 2.5 0 0 0 14 6.5A2.5 2.5 0 0 0 11.5 9a2.5 2.5 0 0 0 2.5 2.5M14 2c3.86 0 7 3.13 7 7c0 5.25-7 13-7 13S7 14.25 7 9a7 7 0 0 1 7-7M5 9c0 4.5 5.08 10.66 6 11.81L10 22S3 14.25 3 9c0-3.17 2.11-5.85 5-6.71C6.16 3.94 5 6.33 5 9Z"
          /></svg
        >
      </div>
      <Popup {openOn} closeOnClickInside>
        <ClusterPopup {feature} />
      </Popup>
    </MarkerLayer>

    <MarkerLayer
      applyToClusters={false}
      on:click={(e) => (clickedFeature = e.detail.feature?.properties)}
      let:feature
    >
      <img src={feature.properties.tsunami ? tsunamiImageUrl : quakeImageUrl} alt="Earthquake" />
      <Popup {openOn} closeOnClickInside>
        {@const props = feature.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>
    </MarkerLayer>
  </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