Symbol Images
Data from MapLibre cluster Example.
Symbol images are embedded into the map and rendered via webGL. This makes them faster but less flexible. For more rendering flexibility such as using SVG, see the Custom Marker example.
<script lang="ts">
import MapLibre from 'svelte-maplibre/MapLibre.svelte';
import GeoJSON from 'svelte-maplibre/GeoJSON.svelte';
import CodeSample from '$site/CodeSample.svelte';
import code from './+page.svelte?raw';
import { mapClasses } from '../styles';
import CircleLayer from 'svelte-maplibre/CircleLayer.svelte';
import SymbolLayer from 'svelte-maplibre/SymbolLayer.svelte';
import { hoverStateFilter } from 'svelte-maplibre/filters';
import quakeImageUrl from '$site/earthquake.png';
import tsunamiImageUrl from '$site/tsunami.png';
import earthquakes from '$site/earthquakes.geojson?url';
import type { GeoJsonProperties } from 'geojson';
let clickedFeature: GeoJsonProperties | undefined = $state();
</script>
<p>
Data from <a href="https://maplibre.org/maplibre-gl-js-docs/example/cluster/"
>MapLibre cluster Example.</a
>
</p>
<MapLibre
style="https://basemaps.cartocdn.com/gl/positron-gl-style/style.json"
class={mapClasses}
standardControls
images={[
{ id: 'quake', url: quakeImageUrl },
{ id: 'tsunami', url: tsunamiImageUrl },
]}
>
{#snippet children({ allImagesLoaded })}
<GeoJSON
id="earthquakes"
data={earthquakes}
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),
}}
manageHoverState
onclick={(e) => (clickedFeature = e.features?.[0]?.properties)}
/>
<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],
}}
/>
<SymbolLayer
applyToClusters={false}
hoverCursor="pointer"
layout={{
'icon-image': ['case', ['==', ['get', 'tsunami'], 0], 'quake', 'tsunami'],
'icon-allow-overlap': true,
'text-field': '{mag}',
'text-offset': [0, -2],
'text-size': 12,
}}
onclick={(e) => (clickedFeature = e.features?.[0]?.properties)}
/>
</GeoJSON>
{/snippet}
</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}