Interactive Visualization with Leafmap

Contents

20. Interactive Visualization with Leafmap#

20.1. Introduction#

20.2. Learning Objectives#

20.3. Installing and Setting Up Leafmap#

20.3.1. Installation Methods#

# %conda install -c conda-forge leafmap
# %pip install -U leafmap
import leafmap

20.3.2. Understanding Leafmap’s Backend Architecture#

# import leafmap.foliumap as leafmap  # For folium backend
# import leafmap.maplibregl as leafmap  # For MapLibre backend

20.4. Creating Interactive Maps#

20.4.1. Your First Interactive Map#

m = leafmap.Map()
m

20.4.2. Customizing Map Properties#

20.4.2.1. Setting Center Location and Zoom Level#

# Center the map on the continental United States
m = leafmap.Map(center=(40, -100), zoom=4, height="600px")
m

20.4.3. Managing Map Controls#

20.4.3.1. Understanding Default Controls#

20.4.3.2. Customizing Control Visibility#

# Create a minimal map with most controls disabled
m = leafmap.Map(
    center=(40, -100),
    zoom=4,
    zoom_control=False,  # Remove zoom buttons
    draw_control=False,  # Remove drawing tools
    scale_control=False,  # Remove scale indicator
    fullscreen_control=False,  # Remove fullscreen button
    attribution_control=False,  # Remove attribution text
    toolbar_control=False,  # Remove Leafmap toolbar
)
m

20.4.3.3. Adding Search Functionality#

# Create a new map for demonstration
m = leafmap.Map(center=(40, -100), zoom=4, draw_control=False, height="500px")

# Add search control using OpenStreetMap's Nominatim service
url = "https://nominatim.openstreetmap.org/search?format=json&q={s}"
m.add_search_control(url, zoom=10, position="topleft")
m

20.4.4. Working with Map Layers#

20.4.4.1. Accessing and Inspecting Layers#

# Create a simple map and examine its layers
m = leafmap.Map()
print(f"Number of layers: {len(m.layers)}")
print(f"Layer types: {[type(layer).__name__ for layer in m.layers]}")

20.4.4.2. Layer Management Operations#

# Remove the last layer (be careful with this operation)
if len(m.layers) > 1:  # Keep at least one layer
    m.remove(m.layers[-1])

# Check the remaining layers
print(f"Remaining layers: {len(m.layers)}")

20.4.4.3. Clearing Map Content#

# Create a map with default content
m = leafmap.Map()

# Remove all interactive controls
m.clear_controls()

# Remove all layers (this will result in a blank map)
m.clear_layers()

# Display the minimal map
m

20.5. Changing Basemaps#

20.5.1. Understanding Basemaps and Their Role#

20.5.2. Adding Predefined Basemaps#

m = leafmap.Map()
m.add_basemap("OpenTopoMap")
m

20.5.3. Interactive Basemap Selection#

m = leafmap.Map()
m.add_basemap_gui()
m

20.5.4. Adding Custom XYZ Tile Layers#

m = leafmap.Map()
m.add_tile_layer(
    url="https://mt1.google.com/vt/lyrs=y&x={x}&y={y}&z={z}",
    name="Google Satellite",
    attribution="Google",
)
m

20.5.5. Adding Web Map Service (WMS) Layers#

m = leafmap.Map(center=[40, -100], zoom=4)
url = "https://imagery.nationalmap.gov/arcgis/services/USGSNAIPPlus/ImageServer/WMSServer?"
m.add_wms_layer(
    url=url,
    layers="USGSNAIPPlus",
    name="NAIP",
    attribution="USGS",
    format="image/png",
    shown=True,
)
m

20.5.6. Adding Legends for Data Context#

m = leafmap.Map(center=[40, -100], zoom=4)
m.add_basemap("Esri.WorldImagery")
url = "https://www.mrlc.gov/geoserver/mrlc_display/NLCD_2021_Land_Cover_L48/wms?"
m.add_wms_layer(
    url=url,
    layers="NLCD_2021_Land_Cover_L48",
    name="NLCD 2021",
    attribution="MRLC",
    format="image/png",
    shown=True,
)
m.add_legend(title="NLCD Land Cover Type", builtin_legend="NLCD")
m

20.5.7. Adding Colorbars for Continuous Data#

m = leafmap.Map()
m.add_basemap("OpenTopoMap")
m.add_colormap(
    cmap="terrain",
    label="Elevation (meters)",
    orientation="horizontal",
    vmin=0,
    vmax=4000,
)
m
import leafmap.colormaps as cm
cm.plot_colormaps(width=8, height=0.3)

20.6. Visualizing Vector Data#

20.6.1. Working with Point Data: Markers#

20.6.1.1. Adding Individual Markers#

m = leafmap.Map()
location = [40, -100]  # [latitude, longitude]
m.add_marker(location, draggable=True)
m

20.6.1.2. Adding Multiple Markers Efficiently#

m = leafmap.Map()
# Coordinates for three different cities
locations = [
    [40, -100],  # Central US
    [45, -110],  # Northwestern US
    [50, -120],  # Southwestern Canada
]
m.add_markers(markers=locations)
m

20.6.1.3. Managing Large Point Datasets with Clustering#

m = leafmap.Map()
url = "https://github.com/opengeos/datasets/releases/download/world/world_cities.csv"
m.add_marker_cluster(url, x="longitude", y="latitude", layer_name="World cities")
m

20.6.1.4. Advanced Marker Customization#

m = leafmap.Map(center=[40, -100], zoom=4)
cities = "https://github.com/opengeos/datasets/releases/download/us/cities.csv"
regions = "https://github.com/opengeos/datasets/releases/download/us/us_regions.geojson"
m.add_geojson(regions, layer_name="US Regions")
m.add_points_from_xy(
    cities,
    x="longitude",
    y="latitude",
    color_column="region",
    icon_names=["gear", "map", "leaf", "globe"],
    spin=True,
    add_legend=True,
)
m

20.6.2. Visualizing Polylines#

m = leafmap.Map(center=[20, 0], zoom=2)
data = "https://github.com/opengeos/datasets/releases/download/vector/cables.geojson"
m.add_vector(data, layer_name="Cable lines", info_mode="on_hover")
m

20.6.2.1. Customizing Polyline Styles#

m = leafmap.Map(center=[20, 0], zoom=2)
m.add_basemap("CartoDB.DarkMatter")
data = "https://github.com/opengeos/datasets/releases/download/vector/cables.geojson"
callback = lambda feat: {"color": feat["properties"]["color"], "weight": 2}
m.add_vector(data, layer_name="Cable lines", style_callback=callback)
m

20.6.3. Visualizing Polygons#

m = leafmap.Map()
url = "https://github.com/opengeos/datasets/releases/download/places/nyc_buildings.geojson"
m.add_vector(url, layer_name="NYC Buildings", zoom_to_layer=True)
m

20.6.4. Visualizing GeoPandas GeoDataFrames#

url = "https://github.com/opengeos/datasets/releases/download/places/las_vegas_buildings.geojson"
gdf = leafmap.read_vector(url)
gdf.head()
gdf.explore()
m = leafmap.Map()
m.add_basemap("Esri.WorldImagery")
style = {"color": "red", "fillColor": "red", "fillOpacity": 0.1, "weight": 2}
m.add_gdf(gdf, style=style, layer_name="Las Vegas Buildings", zoom_to_layer=True)
m

20.7. Creating Choropleth Maps#

m = leafmap.Map()
data = "https://raw.githubusercontent.com/opengeos/leafmap/master/docs/data/countries.geojson"
m.add_data(
    data, column="POP_EST", scheme="Quantiles", cmap="Blues", legend_title="Population"
)
m
m = leafmap.Map()
m.add_data(
    data,
    column="POP_EST",
    scheme="EqualInterval",
    cmap="Blues",
    legend_title="Population",
)
m

20.8. Visualizing GeoParquet Data#

20.8.1. Loading and Visualizing Point Data#

url = "https://opengeos.org/data/duckdb/cities.parquet"
gdf = leafmap.read_vector(url)
gdf.head()
gdf.explore()
m = leafmap.Map()
m.add_points_from_xy(gdf, x="longitude", y="latitude")
m

20.8.2. Visualizing Polygon Data#

url = "https://data.source.coop/giswqs/nwi/wetlands/DC_Wetlands.parquet"
gdf = leafmap.read_vector(url)
gdf.head()
gdf.explore()
m = leafmap.Map()
m.add_basemap("Esri.WorldImagery", show=False)
m.add_nwi(gdf, col_name="WETLAND_TYPE", zoom_to_layer=True)
m

20.9. Visualizing PMTiles#

20.9.1. Retrieving Metadata from PMTiles#

url = "https://opengeos.org/data/pmtiles/protomaps_firenze.pmtiles"
metadata = leafmap.pmtiles_metadata(url)
print(f"layer names: {metadata['layer_names']}")
print(f"bounds: {metadata['bounds']}")

20.9.2. Visualizing PMTiles Data#

m = leafmap.Map()

style = {
    "version": 8,
    "sources": {
        "example_source": {
            "type": "vector",
            "url": "pmtiles://" + url,
            "attribution": "PMTiles",
        }
    },
    "layers": [
        {
            "id": "buildings",
            "source": "example_source",
            "source-layer": "landuse",
            "type": "fill",
            "paint": {"fill-color": "steelblue"},
        },
        {
            "id": "roads",
            "source": "example_source",
            "source-layer": "roads",
            "type": "line",
            "paint": {"line-color": "black"},
        },
    ],
}

# style = leafmap.pmtiles_style(url)  # Use default style
m.add_pmtiles(
    url, name="PMTiles", style=style, overlay=True, show=True, zoom_to_layer=True
)
m

20.9.3. Visualizing Open Buildings Data with PMTiles#

url = "https://data.source.coop/vida/google-microsoft-open-buildings/pmtiles/go_ms_building_footprints.pmtiles"
metadata = leafmap.pmtiles_metadata(url)
print(f"layer names: {metadata['layer_names']}")
print(f"bounds: {metadata['bounds']}")
m = leafmap.Map(center=[20, 0], zoom=2)
m.add_basemap("CartoDB.DarkMatter")
m.add_basemap("Esri.WorldImagery", show=False)

style = {
    "version": 8,
    "sources": {
        "example_source": {
            "type": "vector",
            "url": "pmtiles://" + url,
            "attribution": "PMTiles",
        }
    },
    "layers": [
        {
            "id": "buildings",
            "source": "example_source",
            "source-layer": "building_footprints",
            "type": "fill",
            "paint": {"fill-color": "#3388ff", "fill-opacity": 0.5},
        },
    ],
}

m.add_pmtiles(
    url, name="Buildings", style=style, overlay=True, show=True, zoom_to_layer=False
)
m

20.9.4. Visualizing Overture Maps Data#

release = leafmap.get_overture_latest_release()
release
theme = "buildings"
url = f"https://overturemaps-tiles-us-west-2-beta.s3.amazonaws.com/{release}/{theme}.pmtiles"
style = {
    "version": 8,
    "sources": {
        "example_source": {
            "type": "vector",
            "url": "pmtiles://" + url,
            "attribution": "PMTiles",
        }
    },
    "layers": [
        {
            "id": "Building",
            "source": "example_source",
            "source-layer": "building",
            "type": "fill",
            "paint": {
                "fill-color": "#ffff00",
                "fill-opacity": 0.7,
                "fill-outline-color": "#ff0000",
            },
        },
    ],
}
m = leafmap.Map(center=[47.65350739, -117.59664999], zoom=16)
m.add_basemap("Satellite")
m.add_pmtiles(url, style=style, layer_name="Buildings", zoom_to_layer=False)
m

20.10. Visualizing Raster Data#

20.10.1. Visualizing Cloud Optimized GeoTIFFs (COGs)#

20.10.1.1. Adding a Cloud Optimized GeoTIFF (COG)#

m = leafmap.Map(center=[39.494897, -108.507278], zoom=10)
url = "https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2018-02-16/pine-gulch-fire20/1030010076004E00.tif"
m.add_cog_layer(url, name="Fire (pre-event)")
m
leafmap.cog_info(url)
leafmap.cog_bands(url)
stats = leafmap.cog_stats(url)
# stats

20.10.1.2. Adding Multiple COGs#

url2 = "https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-14/pine-gulch-fire20/10300100AAC8DD00.tif"
m.add_cog_layer(url2, name="Fire (post-event)")
m

20.10.1.3. Creating a Split Map for Comparison#

m = leafmap.Map(center=[39.494897, -108.507278], zoom=10)
m.split_map(
    left_layer=url, right_layer=url2, left_label="Pre-event", right_label="Post-event"
)
m
m = leafmap.Map(center=[47.653149, -117.59825], zoom=16)
m.add_basemap("Satellite")
image1 = "https://github.com/opengeos/datasets/releases/download/places/wa_building_image.tif"
image2 = "https://github.com/opengeos/datasets/releases/download/places/wa_building_masks.tif"
m.split_map(
    image2,
    image1,
    left_label="Building Masks",
    right_label="Aerial Imagery",
    left_args={"colormap_name": "tab20", "nodata": 0, "opacity": 0.7},
)
m

20.10.1.4. Using a Custom Colormap#

url = "https://github.com/opengeos/datasets/releases/download/raster/nlcd_2021_land_cover_30m.tif"
colormap = {
    "11": "#466b9f",
    "12": "#d1def8",
    "21": "#dec5c5",
    "22": "#d99282",
    "23": "#eb0000",
    "24": "#ab0000",
    "31": "#b3ac9f",
    "41": "#68ab5f",
    "42": "#1c5f2c",
    "43": "#b5c58f",
    "51": "#af963c",
    "52": "#ccb879",
    "71": "#dfdfc2",
    "72": "#d1d182",
    "73": "#a3cc51",
    "74": "#82ba9e",
    "81": "#dcd939",
    "82": "#ab6c28",
    "90": "#b8d9eb",
    "95": "#6c9fb8",
}
m = leafmap.Map(center=[40, -100], zoom=4, height="650px")
m.add_basemap("Esri.WorldImagery")
m.add_cog_layer(url, colormap=colormap, name="NLCD Land Cover", nodata=0)
m.add_legend(title="NLCD Land Cover Type", builtin_legend="NLCD")
m.add_layer_manager()
m

20.10.2. Visualizing Local Raster Datasets#

20.10.2.1. Downloading and Visualizing a Local Raster#

dem_url = "https://github.com/opengeos/datasets/releases/download/raster/dem_90m.tif"
filename = "dem_90m.tif"
leafmap.download_file(dem_url, filename, quiet=True)
m = leafmap.Map()
m.add_raster(filename, colormap="terrain", layer_name="DEM")
m
leafmap.image_min_max(filename)
m.add_colormap(cmap="terrain", vmin=15, vmax=4338, label="Elevation (m)")

20.10.2.2. Visualizing a Multi-Band Raster#

import leafmap
landsat_url = "https://github.com/opengeos/datasets/releases/download/raster/cog.tif"
filename = "cog.tif"
leafmap.download_file(landsat_url, filename, quiet=True)
m = leafmap.Map()
m.add_raster(filename, indexes=[4, 3, 2], layer_name="RGB")
m

20.10.2.3. Inspecting Pixel Values#

m = leafmap.Map(center=[53.407089, 6.875480], zoom=13)
m.add_raster(filename, indexes=[4, 3, 2], layer_name="RGB")
m.add("inspector")
m

20.10.3. Visualizing SpatioTemporal Asset Catalog (STAC) Data#

20.10.3.1. Exploring STAC Bands#

url = "https://canada-spot-ortho.s3.amazonaws.com/canada_spot_orthoimages/canada_spot5_orthoimages/S5_2007/S5_11055_6057_20070622/S5_11055_6057_20070622.json"
leafmap.stac_bands(url)

20.10.3.2. Adding STAC Layers to the Map#

m = leafmap.Map(center=[60.95410, -110.90184], zoom=10)
m.add_stac_layer(url, bands=["pan"], name="Panchromatic")
m.add_stac_layer(url, bands=["B3", "B2", "B1"], name="False color")
m

20.11. Accessing and Visualizing Maxar Open Data#

20.11.1. Discovering Available Disaster Events#

leafmap.maxar_collections()

20.11.2. Selecting a Disaster Event#

collection = "Kahramanmaras-turkey-earthquake-23"
url = leafmap.maxar_collection_url(collection, dtype="geojson")
url
gdf = leafmap.read_vector(url)
print(f"Total number of images: {len(gdf)}")
gdf.head()

20.11.3. Visualizing Image Footprints#

m = leafmap.Map()
m.add_gdf(gdf, layer_name="Footprints", zoom_to_layer=True)
m

20.11.4. Temporal Analysis: Before and After the Earthquake#

pre_gdf = leafmap.maxar_search(collection, end_date="2023-02-06")
print(f"Total number of pre-event images: {len(pre_gdf)}")
pre_gdf.head()
post_gdf = leafmap.maxar_search(collection, start_date="2023-02-06")
print(f"Total number of post-event images: {len(post_gdf)}")
post_gdf.head()

20.11.5. Comparing Pre-Event and Post-Event Coverage#

m = leafmap.Map()
pre_style = {"color": "red", "fillColor": "red", "opacity": 1, "fillOpacity": 0.5}
m.add_gdf(
    pre_gdf,
    layer_name="Pre-event",
    style=pre_style,
    info_mode="on_click",
    zoom_to_layer=True,
)
m.add_gdf(post_gdf, layer_name="Post-event", info_mode="on_click", zoom_to_layer=True)
m

20.11.6. Selecting a Region of Interest#

bbox = m.user_roi_bounds()
if bbox is None:
    bbox = [36.8715, 37.5497, 36.9814, 37.6019]

20.11.7. Searching Within the Region of Interest#

pre_event = leafmap.maxar_search(collection, bbox=bbox, end_date="2023-02-06")
pre_event.head()
post_event = leafmap.maxar_search(collection, bbox=bbox, start_date="2023-02-06")
post_event.head()

20.11.8. Preparing Images for Visualization#

pre_tile = pre_event["catalog_id"].values[0]
pre_tile
post_tile = post_event["catalog_id"].values[0]
post_tile

20.11.9. Creating Web-Optimized Tile Services#

pre_stac = leafmap.maxar_tile_url(collection, pre_tile, dtype="json")
pre_stac
post_stac = leafmap.maxar_tile_url(collection, post_tile, dtype="json")
post_stac

20.11.10. Creating a Split-Map Comparison#

m = leafmap.Map()
m.split_map(
    left_layer=pre_stac,
    right_layer=post_stac,
    left_label="Pre-event",
    right_label="Post-event",
)
m.set_center(36.9265, 37.5762, 16)
m

20.11.11. Downloading Images for Offline Analysis#

pre_images = pre_event["visual"].tolist()
post_images = post_event["visual"].tolist()
leafmap.maxar_download(pre_images)
# leafmap.maxar_download(post_images)

20.12. Key Takeaways#

20.13. Exercises#

20.13.1. Exercise 1: Creating an Interactive Map#

20.13.2. Exercise 2: Adding XYZ and WMS Tile Layers#

20.13.3. Exercise 3: Adding Map Legends#

20.13.4. Exercise 4: Creating Marker Clusters#

20.13.5. Exercise 5: Visualizing Vector Data#

20.13.6. Exercise 6: Visualizing GeoParquet Data#

20.13.7. Exercise 7: Visualizing PMTiles#

20.13.8. Exercise 8: Visualizing Cloud Optimized GeoTIFFs (COGs)#

20.13.9. Exercise 9: Visualizing Local Raster Data#

20.13.10. Exercise 10: Creating a Split Map#