Earthquake Clusters

import sys

from maplibre import (
    Layer,
    LayerType,
    Map,
    MapOptions,
    output_maplibregl,
    render_maplibregl,
)
from maplibre.basemaps import Carto
from maplibre.controls import NavigationControl
from maplibre.sources import GeoJSONSource
from shiny import App, reactive, ui

EARTHQUAKE_SOURCE = "earthquakes"
EARTHQUAKE_CIRCLES = "earthquake-circles"
EARTHQUAKE_CLUSTERS = "earthquake-clusters"
EARTHQUAKE_LABELS = "earthquake-labels"

CENTER = (-118.0931, 33.78615)

earthquakes_source = GeoJSONSource(
    data="https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson",
    cluster=True,
    cluster_radius=50,
    cluster_min_points=2,
    cluster_max_zoom=14,
    cluster_properties={
        "maxMag": ["max", ["get", "mag"]],
        "minMag": ["min", ["get", "mag"]],
    },
)

earthquake_circles = Layer(
    type=LayerType.CIRCLE,
    id=EARTHQUAKE_CIRCLES,
    source=EARTHQUAKE_SOURCE,
    paint={"circle-color": "darkblue"},
    filter=["!", ["has", "point_count"]],
)

earthquake_clusters = Layer(
    type=LayerType.CIRCLE,
    id=EARTHQUAKE_CLUSTERS,
    source=EARTHQUAKE_SOURCE,
    filter=["has", "point_count"],
    paint={
        "circle-color": [
            "step",
            ["get", "point_count"],
            "#51bbd6",
            100,
            "#f1f075",
            750,
            "#f28cb1",
        ],
        "circle-radius": ["step", ["get", "point_count"], 20, 100, 30, 750, 40],
    },
)

earthquake_labels = Layer(
    type=LayerType.SYMBOL,
    id="text",
    source=EARTHQUAKE_SOURCE,
    filter=["has", "point_count"],
    layout={
        "text-field": ["get", "point_count_abbreviated"],
        "text-size": 12,
    },
)

map_options = MapOptions(style=Carto.POSITRON, center=CENTER, zoom=3, hash=True)


def create_map() -> Map:
    m = Map(map_options)
    m.add_control(NavigationControl())
    m.add_source(EARTHQUAKE_SOURCE, earthquakes_source)
    m.add_layer(earthquake_clusters)
    m.add_layer(earthquake_circles)
    m.add_tooltip(EARTHQUAKE_CLUSTERS, "maxMag")
    m.add_layer(earthquake_labels)
    return m


app_ui = ui.page_fluid(
    ui.panel_title("Earthquakes Cluster"),
    output_maplibregl("maplibre", height=500),
)


def server(input, output, session):
    @render_maplibregl
    def maplibre():
        return create_map()

    @reactive.Effect
    @reactive.event(input.maplibre)
    async def result():
        print(f"result: {input.maplibre()}")


app = App(app_ui, server)

if __name__ == "__main__":
    if len(sys.argv) == 2:
        file_name = sys.argv[1]
        with open(file_name, "w") as f:
            f.write(create_map().to_html())
    else:
        app.run()

Run example:

poetry run uvicorn docs.examples.earthquake_clusters.app:app --reload