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)