From 2b6691152e525dd2ea473af55a984f3be8e97fcc Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Sun, 16 Nov 2025 01:39:15 -0500 Subject: [PATCH 1/7] Add leafmap workshop notebook --- modules/02-interactive-viz/index.md | 958 ++++++++++++++++++++++++++++ 1 file changed, 958 insertions(+) diff --git a/modules/02-interactive-viz/index.md b/modules/02-interactive-viz/index.md index 16483bb..d86bb19 100644 --- a/modules/02-interactive-viz/index.md +++ b/modules/02-interactive-viz/index.md @@ -24,3 +24,961 @@ authors: webkitallowfullscreen="true"> ::: + +[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/opengeos/leafmap/blob/master/docs/workshops/AGU_2025.ipynb) + +## Introduction + +This notebook is for the workshop ([Open Source Geospatial Workflows in the Cloud](https://geojupyter.github.io/workshop-open-source-geospatial)) presented at the [AGU Fall Meeting 2025](https://agu.confex.com/agu/agu25/meetingapp.cgi/Session/252640). + +### Learning Objectives + +By the end of this workshop, you will be able to: + +- Create interactive maps with MapLibre using Python +- Add and customize various data layers (raster, vector, 3D) +- Visualize remote sensing data including COGs, STAC, and local rasters +- Work with PMTiles for efficient vector tile rendering +- Create 3D visualizations including terrain, buildings, and indoor maps +- Use TiTiler for dynamic raster tile serving +- Apply custom styling and legends to enhance map readability + +## Useful Resources + +- [MapLibre GL JS Documentation](https://maplibre.org/maplibre-gl-js/docs): Comprehensive documentation for MapLibre GL JS. +- [MapLibre Python Bindings](https://github.com/eoda-dev/py-maplibregl): Information on using MapLibre with Python. +- [MapLibre in Leafmap](https://leafmap.org/maplibre/overview): Examples and tutorials for MapLibre in Leafmap. +- [Video Tutorials](https://bit.ly/maplibre): Video guides for practical MapLibre skills. +- [MapLibre Demos](https://maps.gishub.org): Interactive demos showcasing MapLibre's capabilities. + +## Table of Contents + +- [👁️ 2 - Interactive visualization of raster and vector data](#️-2---interactive-visualization-of-raster-and-vector-data) + - [Introduction](#introduction) + - [Learning Objectives](#learning-objectives) + - [Useful Resources](#useful-resources) + - [Table of Contents](#table-of-contents) + - [Installation and Setup](#installation-and-setup) + - [Creating Interactive Maps](#creating-interactive-maps) + - [Basic Map Setup](#basic-map-setup) + - [Customizing the Map's Center and Zoom Level](#customizing-the-maps-center-and-zoom-level) + - [Choosing a Basemap Style](#choosing-a-basemap-style) + - [Adding Map Controls](#adding-map-controls) + - [Available Controls](#available-controls) + - [Adding Geolocate Control](#adding-geolocate-control) + - [Adding Fullscreen Control](#adding-fullscreen-control) + - [Adding Navigation Control](#adding-navigation-control) + - [Adding Draw Control](#adding-draw-control) + - [Adding Layers](#adding-layers) + - [Adding Basemaps](#adding-basemaps) + - [Adding XYZ Tile Layer](#adding-xyz-tile-layer) + - [Adding WMS Layer](#adding-wms-layer) + - [3D Terrain](#3d-terrain) + - [3D Buildings](#3d-buildings) + - [3D Indoor Mapping](#3d-indoor-mapping) + - [Visualizing Vector Data](#visualizing-vector-data) + - [Point Data](#point-data) + - [Line Data](#line-data) + - [Polygon Data](#polygon-data) + - [Visualizing Remote Sensing Data](#visualizing-remote-sensing-data) + - [Local Raster Data](#local-raster-data) + - [Cloud Optimized GeoTIFF (COG)](#cloud-optimized-geotiff-cog) + - [STAC Layer](#stac-layer) + - [Adding HTML](#adding-html) + - [Adding Components to the Map](#adding-components-to-the-map) + - [Adding Color bar](#adding-color-bar) + - [Adding Legend](#adding-legend) + - [Adding Video](#adding-video) + - [PMTiles](#pmtiles) + - [Building Footprint Data](#building-footprint-data) + - [Fields of The World](#fields-of-the-world) + - [3D PMTiles](#3d-pmtiles) + - [H3 Hexagonal Grid](#h3-hexagonal-grid) + - [Summary and Best Practices](#summary-and-best-practices) + - [Key Takeaways](#key-takeaways) + - [Next Steps](#next-steps) + +## Installation and Setup + +To install the required packages, uncomment and run the line below. + +```{code-cell} ipython3 +# %pip install -U leafmap +``` + +Once installed, import the `maplibregl` backend from the `leafmap` package: + +```{code-cell} ipython3 +import leafmap.maplibregl as leafmap +``` + +## Creating Interactive Maps + +### Basic Map Setup + +Let’s start by creating a simple interactive map with default settings. This basic setup provides simple map with the `dark-matter` style on which you can add data layers, controls, and other customizations. + +```{code-cell} ipython3 +m = leafmap.Map() +m +``` + +### Customizing the Map's Center and Zoom Level + +You can specify the map’s center (latitude and longitude), zoom level, pitch, and bearing for a more focused view. These parameters help direct the user's attention to specific areas. + +```{code-cell} ipython3 +m = leafmap.Map(center=[-100, 40], zoom=2, pitch=0, bearing=0, projection="globe") +m +``` + +### Choosing a Basemap Style + +MapLibre supports several pre-defined basemap styles such as `dark-matter`, `positron`, `voyager`, `liberty`, `demotiles`. You can also use custom basemap URLs for unique styling. + +```{code-cell} ipython3 +m = leafmap.Map(style="positron") +m +``` + +[OpenFreeMap](https://openfreemap.org) provides a variety of basemap styles that you can use in your interactive maps. These styles include `liberty`, `bright`, and `positron`. + +```{code-cell} ipython3 +m = leafmap.Map(style="liberty") +m +``` + +## Adding Map Controls + +Map controls enhance the usability of the map by allowing users to interact in various ways, adding elements like scale bars, zoom tools, and drawing options. + +### Available Controls + +- **Geolocate**: Centers the map based on the user’s current location, if available. +- **Fullscreen**: Expands the map to a full-screen view for better focus. +- **Navigation**: Provides zoom controls and a compass for reorientation. +- **Draw**: Allows users to draw and edit shapes on the map. + +### Adding Geolocate Control + +The Geolocate control centers the map based on the user’s current location, a helpful feature for location-based applications. + +```{code-cell} ipython3 +m = leafmap.Map() +m.add_control("geolocate", position="top-right") +m +``` + +### Adding Fullscreen Control + +Fullscreen control enables users to expand the map to full screen, enhancing focus and visual clarity. This is especially useful when viewing complex or large datasets. + +```{code-cell} ipython3 +m = leafmap.Map(center=[11.255, 43.77], zoom=13, style="positron", controls={}) +m.add_control("fullscreen", position="top-right") +m +``` + +### Adding Navigation Control + +The Navigation control provides buttons for zooming and reorienting the map, improving the user's ability to navigate efficiently. + +```{code-cell} ipython3 +m = leafmap.Map(center=[11.255, 43.77], zoom=13, style="positron", controls={}) +m.add_control("navigation", position="top-right") +m +``` + +### Adding Draw Control + +The Draw control enables users to interact with the map by adding shapes such as points, lines, and polygons. This control is essential for tasks requiring spatial data input directly on the map. + +```{code-cell} ipython3 +m = leafmap.Map(center=[-100, 40], zoom=3, style="positron") +m.add_draw_control(position="top-right") +m +``` + +Two key methods for accessing drawn features: + +- **Selected Features**: Accesses only the currently selected features. +- **All Features**: Accesses all features added, regardless of selection, giving you full control over the spatial data on the map. + +```{code-cell} ipython3 +m.draw_features_selected +``` + +```{code-cell} ipython3 +m.draw_feature_collection_all +``` + +## Adding Layers + +Adding layers to a map enhances the data it presents, allowing different types of basemaps, tile layers, and thematic overlays to be combined for in-depth analysis. + +### Adding Basemaps + +Basemaps provide a geographical context for the map. Using the `add_basemap` method, you can select from various basemaps, including `OpenTopoMap` and `Esri.WorldImagery`. Adding a layer control allows users to switch between multiple basemaps interactively. + +```{code-cell} ipython3 +m = leafmap.Map() +m.add_basemap("OpenTopoMap") +m +``` + +```{code-cell} ipython3 +m.add_basemap("Esri.WorldImagery") +``` + +You can also add basemaps interactively, which provides flexibility for selecting the best background for your map content. + +```{code-cell} ipython3 +m = leafmap.Map() +m +``` + +```{code-cell} ipython3 +m.add_basemap() +``` + +### Adding XYZ Tile Layer + +XYZ tile layers allow integration of specific tile services like topographic, satellite, or other thematic imagery from XYZ tile servers. By specifying the URL and parameters such as `opacity` and `visibility`, XYZ layers can be customized and styled to fit the needs of your map. + +```{code-cell} ipython3 +m = leafmap.Map() +url = "https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}" +m.add_tile_layer(url, name="USGS TOpo", attribution="USGS", opacity=1.0, visible=True) +m +``` + +### Adding WMS Layer + +Web Map Service (WMS) layers provide access to external datasets served by web servers, such as thematic maps or detailed satellite imagery. Adding a WMS layer involves specifying the WMS URL and layer names, which allows for overlaying various data types such as natural imagery or land cover classifications. + +```{code-cell} ipython3 +m = leafmap.Map(center=[-74.5447, 40.6892], zoom=8, style="liberty") +url = "https://img.nj.gov/imagerywms/Natural2015" +layers = "Natural2015" +m.add_wms_layer(url, layers=layers, before_id="aeroway_fill") + +m +``` + +```{code-cell} ipython3 +m = leafmap.Map(center=[-100.307965, 46.98692], zoom=13, pitch=45, style="liberty") +m.add_basemap("Esri.WorldImagery") +url = "https://fwspublicservices.wim.usgs.gov/wetlandsmapservice/services/Wetlands/MapServer/WMSServer" +m.add_wms_layer(url, layers="1", name="NWI", opacity=0.6) +m.add_legend(builtin_legend="NWI", title="Wetland Type") +m +``` + +## 3D Terrain + +To visualize 3D terrain, you can use the `set_terrain` method. + +```{code-cell} ipython3 +m = leafmap.Map( + center=[-122.1874314, 46.2022386], + zoom=13, + pitch=60, + bearing=220, + projection="globe", +) +m.add_basemap("Esri.WorldImagery") +m.set_terrain() +m +``` + +### 3D Buildings + +Adding 3D buildings enhances urban visualizations, showing buildings with height variations. The setup involves specifying the MapTiler API key for vector tiles and adding building data as a 3D extrusion layer. The extrusion height and color can be set based on data attributes to visualize structures with varying heights, which can be useful in city planning and urban analysis. + +```{code-cell} ipython3 +m = leafmap.Map( + center=[-74.01201, 40.70473], + zoom=16, + pitch=60, + bearing=35, +) +m.add_basemap("Esri.WorldImagery", visible=False) +m.add_overture_3d_buildings() +m +``` + +### 3D Indoor Mapping + +Indoor mapping data can be visualized by loading a GeoJSON file and applying the `add_geojson` method. This setup allows for displaying floorplans with attributes such as color, height, and opacity. It provides a realistic indoor perspective, which is useful for visualizing complex structures or navigating interior spaces. + +```{code-cell} ipython3 +data = "https://maplibre.org/maplibre-gl-js/docs/assets/indoor-3d-map.geojson" +gdf = leafmap.geojson_to_gdf(data) +gdf.explore() +``` + +```{code-cell} ipython3 +gdf.head() +``` + +```{code-cell} ipython3 +m = leafmap.Map( + center=(-87.61694, 41.86625), zoom=17, pitch=40, bearing=20, style="positron" +) +m.add_basemap("OpenStreetMap.Mapnik") +m.add_geojson( + data, + layer_type="fill-extrusion", + name="floorplan", + paint={ + "fill-extrusion-color": ["get", "color"], + "fill-extrusion-height": ["get", "height"], + "fill-extrusion-base": ["get", "base_height"], + "fill-extrusion-opacity": 0.5, + }, +) +m +``` + +## Visualizing Vector Data + +Leafmap supports visualizing vector data using the `add_vector` method. This method allows you to add vector data to the map, which can be used to display features like points, lines, and polygons. + ++++ + +### Point Data + +```{code-cell} ipython3 +url = ( + "https://github.com/opengeos/datasets/releases/download/world/world_cities.geojson" +) +m = leafmap.Map(style="liberty", projection="globe") +m.add_vector(url, name="cities") +m.add_popup("cities") +m +``` + +### Line Data + +```{code-cell} ipython3 +url = ( + "https://data.gishub.org/duckdb/cables.geojson" +) +m = leafmap.Map(style="liberty", projection="globe") +m.add_vector(url, name="cables") +m.add_popup("cables") +m +``` + +### Polygon Data + +```{code-cell} ipython3 +m = leafmap.Map(style="liberty", projection="globe") +data = "https://github.com/opengeos/datasets/releases/download/vector/countries.geojson" +m.add_data( + data, + column="POP_EST", + scheme="Quantiles", + cmap="Blues", + legend_title="Population", + name="Population", + before_id=m.first_symbol_layer_id, + extrude=True, + scale_factor=1000, +) +m +``` + +## Visualizing Remote Sensing Data + +### Local Raster Data + +To visualize local raster files, use the `add_raster` method. In the example, a Landsat image is downloaded and displayed using two different band combinations: + +- **Band Combination 3-2-1 (True Color)**: Simulates natural colors in the RGB channels. +- **Band Combination 4-3-2**: Enhances vegetation, displaying it in red for better visual contrast. + These layers are added to the map along with controls to toggle them. You can adjust brightness and contrast with the `vmin` and `vmax` arguments to improve clarity. + +```{code-cell} ipython3 +url = "https://github.com/opengeos/datasets/releases/download/raster/landsat.tif" +filepath = "landsat.tif" +leafmap.download_file(url, filepath, quiet=True) +``` + +```{code-cell} ipython3 +m = leafmap.Map(style="streets") +m.add_raster(filepath, indexes=[3, 2, 1], vmin=0, vmax=100, name="Landsat-321") +m.add_raster(filepath, indexes=[4, 3, 2], vmin=0, vmax=100, name="Landsat-432") +m +``` + +A Digital Elevation Model (DEM) is also downloaded and visualized with a terrain color scheme. Leafmap’s `layer_interact` method allows interactive adjustments. + +```{code-cell} ipython3 +url = "https://github.com/opengeos/datasets/releases/download/raster/srtm90.tif" +filepath = "srtm90.tif" +leafmap.download_file(url, filepath, quiet=True) +``` + +```{code-cell} ipython3 +m = leafmap.Map(style="liberty") +m.add_raster(filepath, colormap="terrain", name="DEM") +m +``` + +### Cloud Optimized GeoTIFF (COG) + +Cloud Optimized GeoTIFFs (COG) are large raster files stored on cloud platforms, allowing efficient streaming and loading. This example loads satellite imagery of Libya before and after an event, showing the change over time. Each image is loaded with `add_cog_layer`, and layers can be toggled for comparison. Using `fit_bounds`, the map centers on the COG layer to fit its boundaries. + +**Best Practices for Working with COGs:** + +- Use COGs for large raster datasets to enable fast, partial data loading +- Store COGs on cloud storage (AWS S3, Google Cloud Storage, Azure) for optimal performance +- Apply appropriate `rescale` values to optimize visualization contrast +- Use `nodata` parameter to handle missing data values correctly +- Leverage `fit_bounds=True` to automatically zoom to your data extent + +```{code-cell} ipython3 +m = leafmap.Map(style="liberty") +before = ( + "https://github.com/opengeos/datasets/releases/download/raster/Libya-2023-07-01.tif" +) +after = ( + "https://github.com/opengeos/datasets/releases/download/raster/Libya-2023-09-13.tif" +) +m.add_cog_layer(before, name="Before", attribution="Maxar") +m.add_cog_layer(after, name="After", attribution="Maxar", fit_bounds=True) +m +``` + +### STAC Layer + +The SpatioTemporal Asset Catalog (STAC) standard organizes large satellite data collections. With `add_stac_layer`, this example loads Canadian satellite data, displaying both a panchromatic and an RGB layer from the same source. This approach allows easy switching between views. + +```{code-cell} ipython3 +m = leafmap.Map(style="streets") +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" +m.add_stac_layer(url, bands=["pan"], name="Panchromatic", vmin=0, vmax=150) +m.add_stac_layer(url, bands=["B4", "B3", "B2"], name="RGB", vmin=0, vmax=150) +m +``` + +### Adding HTML + +Embed custom HTML content to display various HTML elements, such as emojis or stylized text. You can also adjust the font size and background transparency for better integration into the map design. + +```{code-cell} ipython3 +m = leafmap.Map(center=[-100, 40], zoom=3, style="positron") +html = """ + + + + +🚀 +

I will display 🚁

+

I will display 🚂

+ + + +""" +m.add_html(html, bg_color="transparent") +m +``` + ++++ + +## Adding Components to the Map + +### Adding Color bar + +Adding a color bar enhances data interpretation. In the example: + +1. A Digital Elevation Model (DEM) is displayed with a color ramp from 0 to 1500 meters. +2. `add_colorbar` method is used to create a color bar with labels, adjusting its position, opacity, and orientation for optimal readability. + +```{code-cell} ipython3 +import numpy as np +``` + +```{code-cell} ipython3 +m = leafmap.Map(style="liberty") +dem = "https://github.com/opengeos/datasets/releases/download/raster/dem.tif" +m.add_cog_layer( + dem, + name="DEM", + colormap_name="terrain", + rescale="0, 1500", + fit_bounds=True, + nodata=np.nan, +) +m.add_colorbar( + cmap="terrain", vmin=0, vmax=1500, label="Elevation (m)", position="bottom-right" +) + +m +``` + +Make the color bar background transparent to blend seamlessly with the map. + +```{code-cell} ipython3 +m = leafmap.Map(style="liberty") +m.add_cog_layer( + dem, + name="DEM", + colormap_name="terrain", + rescale="0, 1500", + nodata=np.nan, + fit_bounds=True, +) +m.add_colorbar( + cmap="terrain", + vmin=0, + vmax=1500, + label="Elevation (m)", + position="bottom-right", + transparent=True, +) +m +``` + +Make the color bar vertical for a different layout. + +```{code-cell} ipython3 +m = leafmap.Map(style="liberty") +m.add_cog_layer( + dem, + name="DEM", + colormap_name="terrain", + rescale="0, 1500", + nodata=np.nan, + fit_bounds=True, +) +m.add_colorbar( + cmap="terrain", + vmin=0, + vmax=1500, + label="Elevation (m)", + position="bottom-right", + width=0.2, + height=3, + orientation="vertical", +) +m +``` + +### Adding Legend + +Custom legends help users understand data classifications. Two methods are shown: + +1. Using built-in legends, such as for NLCD (National Land Cover Database) or wetland types. +2. Custom legends are built with a dictionary of land cover types and colors. This legend provides descriptive color-coding for various land cover types, with configurable background opacity to blend with the map. + +```{code-cell} ipython3 +m = leafmap.Map(center=[-100, 40], zoom=3, style="positron") +m.add_basemap("Esri.WorldImagery") +url = "https://www.mrlc.gov/geoserver/mrlc_display/NLCD_2021_Land_Cover_L48/wms" +layers = "NLCD_2021_Land_Cover_L48" +m.add_wms_layer(url, layers=layers, name="NLCD 2021") +m.add_legend( + title="NLCD Land Cover Type", + builtin_legend="NLCD", + bg_color="rgba(255, 255, 255, 0.5)", + position="bottom-left", +) +m +``` + +```{code-cell} ipython3 +m = leafmap.Map(center=[-100, 40], zoom=3, style="positron") +m.add_basemap("Esri.WorldImagery") +url = "https://fwspublicservices.wim.usgs.gov/wetlandsmapservice/services/Wetlands/MapServer/WMSServer" +m.add_wms_layer(url, layers="1", name="NWI", opacity=0.6) +m.add_legend(builtin_legend="NWI", title="Wetland Type") +m +``` + +```{code-cell} ipython3 +m = leafmap.Map(center=[-100, 40], zoom=3, style="positron") +m.add_basemap("Esri.WorldImagery") +url = "https://www.mrlc.gov/geoserver/mrlc_display/NLCD_2021_Land_Cover_L48/wms" +layers = "NLCD_2021_Land_Cover_L48" +m.add_wms_layer(url, layers=layers, name="NLCD 2021") + +legend_dict = { + "11 Open Water": "466b9f", + "12 Perennial Ice/Snow": "d1def8", + "21 Developed, Open Space": "dec5c5", + "22 Developed, Low Intensity": "d99282", + "23 Developed, Medium Intensity": "eb0000", + "24 Developed High Intensity": "ab0000", + "31 Barren Land (Rock/Sand/Clay)": "b3ac9f", + "41 Deciduous Forest": "68ab5f", + "42 Evergreen Forest": "1c5f2c", + "43 Mixed Forest": "b5c58f", + "51 Dwarf Scrub": "af963c", + "52 Shrub/Scrub": "ccb879", + "71 Grassland/Herbaceous": "dfdfc2", + "72 Sedge/Herbaceous": "d1d182", + "73 Lichens": "a3cc51", + "74 Moss": "82ba9e", + "81 Pasture/Hay": "dcd939", + "82 Cultivated Crops": "ab6c28", + "90 Woody Wetlands": "b8d9eb", + "95 Emergent Herbaceous Wetlands": "6c9fb8", +} +m.add_legend( + title="NLCD Land Cover Type", + legend_dict=legend_dict, + bg_color="rgba(255, 255, 255, 0.5)", + position="bottom-left", +) +m +``` + +### Adding Video + +Videos can be added with geographic context by specifying corner coordinates. Videos must be listed in multiple formats to ensure compatibility across browsers. The coordinates array should define the video’s location on the map in the order: top-left, top-right, bottom-right, and bottom-left. This is demonstrated by adding drone footage to a satellite map view, enhancing the user experience with real-world visuals. + +```{code-cell} ipython3 +m = leafmap.Map(center=[-122.514426, 37.562984], zoom=17, bearing=-96, style="liberty") +m.add_basemap("Esri.WorldImagery") +urls = [ + "https://static-assets.mapbox.com/mapbox-gl-js/drone.mp4", + "https://static-assets.mapbox.com/mapbox-gl-js/drone.webm", +] +coordinates = [ + [-122.51596391201019, 37.56238816766053], + [-122.51467645168304, 37.56410183312965], + [-122.51309394836426, 37.563391708549425], + [-122.51423120498657, 37.56161849366671], +] +m.add_video(urls, coordinates) +m +``` + +```{code-cell} ipython3 +m = leafmap.Map(center=[-115, 25], zoom=4, style="liberty") +m.add_basemap("Esri.WorldImagery") +urls = [ + "https://data.opengeos.org/patricia_nasa.mp4", + "https://data.opengeos.org/patricia_nasa.webm", +] +coordinates = [ + [-130, 32], + [-100, 32], + [-100, 13], + [-130, 13], +] +m.add_video(urls, coordinates) +m +``` + +## PMTiles + +Leafmap supports visualizing [PMTiles](https://protomaps.com/docs/pmtiles/), which enables efficient storage and fast rendering of vector tiles directly in the browser. + +**Why PMTiles?** + +- Single-file archive format for vector tiles (no server-side processing needed) +- Efficient range request support for cloud-native applications +- Significantly faster than traditional tile servers for static datasets +- Can be hosted on any static file server or cloud storage +- Ideal for large-scale building footprints, field boundaries, and other vector data + ++++ + +### Building Footprint Data + +Visualize the [Google-Microsoft Open Buildings dataset](https://beta.source.coop/repositories/vida/google-microsoft-open-buildings/description), managed by VIDA, in PMTiles format. Fetch metadata to identify available layers, apply custom styles to the building footprints, and render them with semi-transparent colors for a clear visualization. + +```{code-cell} ipython3 +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']}") +``` + +```{code-cell} ipython3 +m = leafmap.Map(center=[0, 20], zoom=2) +m.add_basemap("Esri.WorldImagery", visible=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}, + }, + ], +} + +# style = leafmap.pmtiles_style(url) # Use default style + +m.add_pmtiles( + url, + style=style, + visible=True, + opacity=1.0, + tooltip=True, +) +m +``` + +### Fields of The World + +Visualize the Agricultural Field Boundary dataset - Fields of The World ([FTW](https://fieldsofthe.world)). The dataset is available on Source Cooperative at https://source.coop/repositories/kerner-lab/fields-of-the-world/description. + +```{code-cell} ipython3 +url = "https://data.source.coop/kerner-lab/fields-of-the-world/ftw-sources.pmtiles" +metadata = leafmap.pmtiles_metadata(url) +print(f"layer names: {metadata['layer_names']}") +print(f"bounds: {metadata['bounds']}") +``` + +```{code-cell} ipython3 +m = leafmap.Map() +# Define colors for each last digit (0-9) +style = { + "layers": [ + { + "id": "Field Polygon", + "source": "example_source", + "source-layer": "ftw-sources", + "type": "fill", + "paint": { + "fill-color": [ + "case", + ["==", ["%", ["to-number", ["get", "id"]], 10], 0], + "#FF5733", # Color for last digit 0 + ["==", ["%", ["to-number", ["get", "id"]], 10], 1], + "#33FF57", # Color for last digit 1 + ["==", ["%", ["to-number", ["get", "id"]], 10], 2], + "#3357FF", # Color for last digit 2 + ["==", ["%", ["to-number", ["get", "id"]], 10], 3], + "#FF33A1", # Color for last digit 3 + ["==", ["%", ["to-number", ["get", "id"]], 10], 4], + "#FF8C33", # Color for last digit 4 + ["==", ["%", ["to-number", ["get", "id"]], 10], 5], + "#33FFF6", # Color for last digit 5 + ["==", ["%", ["to-number", ["get", "id"]], 10], 6], + "#A833FF", # Color for last digit 6 + ["==", ["%", ["to-number", ["get", "id"]], 10], 7], + "#FF333D", # Color for last digit 7 + ["==", ["%", ["to-number", ["get", "id"]], 10], 8], + "#33FFBD", # Color for last digit 8 + ["==", ["%", ["to-number", ["get", "id"]], 10], 9], + "#FF9933", # Color for last digit 9 + "#FF0000", # Fallback color if no match + ], + "fill-opacity": 0.5, + }, + }, + { + "id": "Field Outline", + "source": "example_source", + "source-layer": "ftw-sources", + "type": "line", + "paint": {"line-color": "#ffffff", "line-width": 1, "line-opacity": 1}, + }, + ], +} + +m.add_basemap("Satellite") +m.add_pmtiles(url, style=style, name="FTW", zoom_to_layer=False) +m +``` + +```{code-cell} ipython3 +m = leafmap.Map() +style = { + "layers": [ + { + "id": "Field Polygon", + "source": "example_source", + "source-layer": "ftw-sources", + "type": "fill", + "paint": { + "fill-color": "#ffff00", + "fill-opacity": 0.2, + }, + }, + { + "id": "Field Outline", + "source": "example_source", + "source-layer": "ftw-sources", + "type": "line", + "paint": {"line-color": "#ff0000", "line-width": 1, "line-opacity": 1}, + }, + ], +} + +m.add_basemap("Satellite") +m.add_pmtiles(url, style=style, name="FTW", zoom_to_layer=False) +m +``` + +### 3D PMTiles + +Render global building data in 3D for a realistic, textured experience. Set building colors and extrusion heights to create visually compelling cityscapes. For example, apply color gradients and height scaling based on building attributes to differentiate buildings by their heights. + +```{code-cell} ipython3 +url = "https://data.source.coop/cholmes/overture/overture-buildings.pmtiles" +metadata = leafmap.pmtiles_metadata(url) +print(f"layer names: {metadata['layer_names']}") +print(f"bounds: {metadata['bounds']}") +``` + +```{code-cell} ipython3 +m = leafmap.Map( + center=[-74.0095, 40.7046], zoom=16, pitch=60, bearing=-17, style="positron" +) +m.add_basemap("OpenStreetMap.Mapnik") +m.add_basemap("Esri.WorldImagery", visible=False) + +style = { + "layers": [ + { + "id": "buildings", + "source": "example_source", + "source-layer": "buildings", + "type": "fill-extrusion", + "filter": [ + ">", + ["get", "height"], + 0, + ], # only show buildings with height info + "paint": { + "fill-extrusion-color": [ + "interpolate", + ["linear"], + ["get", "height"], + 0, + "lightgray", + 200, + "royalblue", + 400, + "lightblue", + ], + "fill-extrusion-height": ["*", ["get", "height"], 1], + }, + }, + ], +} + +m.add_pmtiles( + url, + style=style, + visible=True, + opacity=1.0, + tooltip=True, + template="Height: {{height}}
Country: {{country_iso}}", + fit_bounds=False, +) + +m +``` + +## H3 Hexagonal Grid + +[H3](https://h3geo.org) is a hexagonal grid system that provides a way to represent and analyze geospatial data in a hexagonal grid. It is a popular choice for visualizing and analyzing geospatial data, especially for large datasets. + +```{code-cell} ipython3 +url = "https://data.gishub.org/duckdb/h3_res4_geo.parquet" +gdf = leafmap.read_vector(url) +gdf +``` + +```{code-cell} ipython3 +m = leafmap.Map() +m.add_basemap("Esri.WorldImagery", before_id=m.first_symbol_layer_id, visible=False) +m.add_data( + gdf, + column="building_count", + scheme="JenksCaspall", + cmap="inferno", + outline_color="rgba(255, 255, 255, 0)", + name="H3 Hexagon", + before_id=m.first_symbol_layer_id, +) +m +``` + +```{code-cell} ipython3 +m = leafmap.Map(center=[7.062832, -4.144790], pitch=45.5, zoom=1.57) +m.add_basemap("Esri.WorldImagery", before_id=m.first_symbol_layer_id, visible=False) +m.add_data( + gdf, + column="building_count", + scheme="JenksCaspall", + cmap="inferno", + outline_color="rgba(255, 255, 255, 0)", + name="H3 Hexagon", + before_id=m.first_symbol_layer_id, + extrude=True, + fit_bounds=False, +) +m +``` + +## Summary and Best Practices + +Congratulations! You've learned how to create interactive geospatial visualizations using Leafmap with MapLibre GL JS. This workshop covered: + +- **Map Creation**: Setting up interactive maps with customizable styles and projections +- **Data Visualization**: Working with raster data (local files, COGs, STAC), vector data (GeoJSON, PMTiles), and 3D visualizations +- **Dynamic Tile Serving**: Using TiTiler for on-demand raster tile generation +- **Advanced Features**: Adding controls, legends, color bars, and custom HTML elements + +### Key Takeaways + +1. **Choose the Right Format**: + + - Use COGs for large raster datasets in cloud storage + - Use PMTiles for vector data (buildings, boundaries, points of interest) + - Use TiTiler when you need dynamic rendering with custom parameters + - Use STAC for organized collections of geospatial assets + +2. **Performance Optimization**: + + - Leverage cloud-native formats (COG, PMTiles) for better performance + - Use appropriate zoom levels and bounds to limit data loading + - Apply data rescaling and color mapping efficiently + - Consider using `before_id` parameter to control layer ordering + +3. **Visualization Best Practices**: + + - Add legends and color bars to make your maps interpretable + - Use appropriate color schemes for your data type (sequential, diverging, categorical) + - Leverage 3D visualizations (terrain, buildings) when appropriate + - Combine multiple basemaps and layers for comprehensive analysis + +4. **Interactive Features**: + - Add drawing controls for user interaction + - Enable tooltips to display attribute information + - Use layer controls to toggle between datasets + - Implement fullscreen and navigation controls for better UX + +### Next Steps + +To continue learning: + +- Explore the [Leafmap documentation](https://leafmap.org) for more examples +- Check out [MapLibre GL JS documentation](https://maplibre.org/maplibre-gl-js/docs) for advanced styling +- Visit [Source Cooperative](https://source.coop) for more open geospatial datasets +- Experiment with custom styling using MapLibre style specifications + +Happy mapping! From 5645ca1af7e4d95aa14dda788a7058c44ff5e09f Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Sun, 16 Nov 2025 01:42:59 -0500 Subject: [PATCH 2/7] Fix typos --- modules/02-interactive-viz/index.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/modules/02-interactive-viz/index.md b/modules/02-interactive-viz/index.md index d86bb19..e982d8f 100644 --- a/modules/02-interactive-viz/index.md +++ b/modules/02-interactive-viz/index.md @@ -116,7 +116,7 @@ import leafmap.maplibregl as leafmap ### Basic Map Setup -Let’s start by creating a simple interactive map with default settings. This basic setup provides simple map with the `dark-matter` style on which you can add data layers, controls, and other customizations. +Let’s start by creating a simple interactive map with default settings. This basic setup provides a simple map with the `dark-matter` style on which you can add data layers, controls, and other customizations. ```{code-cell} ipython3 m = leafmap.Map() @@ -248,7 +248,7 @@ XYZ tile layers allow integration of specific tile services like topographic, sa ```{code-cell} ipython3 m = leafmap.Map() url = "https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}" -m.add_tile_layer(url, name="USGS TOpo", attribution="USGS", opacity=1.0, visible=True) +m.add_tile_layer(url, name="USGS Topo", attribution="USGS", opacity=1.0, visible=True) m ``` @@ -361,9 +361,7 @@ m ### Line Data ```{code-cell} ipython3 -url = ( - "https://data.gishub.org/duckdb/cables.geojson" -) +url = "https://data.gishub.org/duckdb/cables.geojson" m = leafmap.Map(style="liberty", projection="globe") m.add_vector(url, name="cables") m.add_popup("cables") @@ -489,8 +487,6 @@ m.add_html(html, bg_color="transparent") m ``` -+++ - ## Adding Components to the Map ### Adding Color bar From 1c74700bbc81087c580425affbcae574fba37622 Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Sun, 16 Nov 2025 12:42:26 -0500 Subject: [PATCH 3/7] Remove Google Slides iframe from index.md Removed embedded Google Slides iframe from the interactive visualization module. --- modules/02-interactive-viz/index.md | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/modules/02-interactive-viz/index.md b/modules/02-interactive-viz/index.md index e982d8f..020b930 100644 --- a/modules/02-interactive-viz/index.md +++ b/modules/02-interactive-viz/index.md @@ -10,21 +10,6 @@ authors: # 👁️ 2 - Interactive visualization of raster and vector data -:::{note} 🛝 Slides -:icon: false -:class: dropdown - - -::: - [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/opengeos/leafmap/blob/master/docs/workshops/AGU_2025.ipynb) ## Introduction From 18d6d139fa7dc85b883a7c991f65abae91ff39cf Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Sun, 16 Nov 2025 13:52:24 -0500 Subject: [PATCH 4/7] Change md to ipynb --- modules/02-interactive-viz/index.ipynb | 1473 ++++++++++++++++++++++++ modules/02-interactive-viz/index.md | 965 ---------------- 2 files changed, 1473 insertions(+), 965 deletions(-) create mode 100644 modules/02-interactive-viz/index.ipynb delete mode 100644 modules/02-interactive-viz/index.md diff --git a/modules/02-interactive-viz/index.ipynb b/modules/02-interactive-viz/index.ipynb new file mode 100644 index 0000000..4f940d1 --- /dev/null +++ b/modules/02-interactive-viz/index.ipynb @@ -0,0 +1,1473 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "7f98bb07", + "metadata": {}, + "source": [ + "# 👁️ 2 - Interactive visualization of raster and vector data\n", + "\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/opengeos/leafmap/blob/master/docs/workshops/AGU_2025.ipynb)\n", + "\n", + "## Introduction\n", + "\n", + "This notebook is for the workshop ([Open Source Geospatial Workflows in the Cloud](https://geojupyter.github.io/workshop-open-source-geospatial)) presented at the [AGU Fall Meeting 2025](https://agu.confex.com/agu/agu25/meetingapp.cgi/Session/252640).\n", + "\n", + "### Learning Objectives\n", + "\n", + "By the end of this workshop, you will be able to:\n", + "\n", + "- Create interactive maps with MapLibre using Python\n", + "- Add and customize various data layers (raster, vector, 3D)\n", + "- Visualize remote sensing data including COGs, STAC, and local rasters\n", + "- Work with PMTiles for efficient vector tile rendering\n", + "- Create 3D visualizations including terrain, buildings, and indoor maps\n", + "- Use TiTiler for dynamic raster tile serving\n", + "- Apply custom styling and legends to enhance map readability\n", + "\n", + "## Useful Resources\n", + "\n", + "- [MapLibre GL JS Documentation](https://maplibre.org/maplibre-gl-js/docs): Comprehensive documentation for MapLibre GL JS.\n", + "- [MapLibre Python Bindings](https://github.com/eoda-dev/py-maplibregl): Information on using MapLibre with Python.\n", + "- [MapLibre in Leafmap](https://leafmap.org/maplibre/overview): Examples and tutorials for MapLibre in Leafmap.\n", + "- [Video Tutorials](https://bit.ly/maplibre): Video guides for practical MapLibre skills.\n", + "- [MapLibre Demos](https://maps.gishub.org): Interactive demos showcasing MapLibre's capabilities.\n", + "\n", + "## Installation and Setup\n", + "\n", + "To install the required packages, uncomment and run the line below." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a1b08dd0", + "metadata": {}, + "outputs": [], + "source": [ + "# %pip install -U leafmap" + ] + }, + { + "cell_type": "markdown", + "id": "a75a541d", + "metadata": {}, + "source": [ + "Once installed, import the `maplibregl` backend from the `leafmap` package:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "07c5e588", + "metadata": {}, + "outputs": [], + "source": [ + "import leafmap.maplibregl as leafmap" + ] + }, + { + "cell_type": "markdown", + "id": "2d3c6693", + "metadata": {}, + "source": [ + "## Creating Interactive Maps\n", + "\n", + "### Basic Map Setup\n", + "\n", + "Let’s start by creating a simple interactive map with default settings. This basic setup provides a simple map with the `dark-matter` style on which you can add data layers, controls, and other customizations." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24d41f90", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map()\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "b5dbc8cc", + "metadata": {}, + "source": [ + "### Customizing the Map's Center and Zoom Level\n", + "\n", + "You can specify the map’s center (latitude and longitude), zoom level, pitch, and bearing for a more focused view. These parameters help direct the user's attention to specific areas." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d78613ed", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-100, 40], zoom=2, pitch=0, bearing=0, projection=\"globe\")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "bcf88fe0", + "metadata": {}, + "source": [ + "### Choosing a Basemap Style\n", + "\n", + "MapLibre supports several pre-defined basemap styles such as `dark-matter`, `positron`, `voyager`, `liberty`, `demotiles`. You can also use custom basemap URLs for unique styling." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e0be7ed9", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(style=\"positron\")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "dd110f00", + "metadata": {}, + "source": [ + "[OpenFreeMap](https://openfreemap.org) provides a variety of basemap styles that you can use in your interactive maps. These styles include `liberty`, `bright`, and `positron`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "25ecbe2d", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(style=\"liberty\")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "59ff994a", + "metadata": {}, + "source": [ + "## Adding Map Controls\n", + "\n", + "Map controls enhance the usability of the map by allowing users to interact in various ways, adding elements like scale bars, zoom tools, and drawing options.\n", + "\n", + "### Available Controls\n", + "\n", + "- **Geolocate**: Centers the map based on the user’s current location, if available.\n", + "- **Fullscreen**: Expands the map to a full-screen view for better focus.\n", + "- **Navigation**: Provides zoom controls and a compass for reorientation.\n", + "- **Draw**: Allows users to draw and edit shapes on the map.\n", + "\n", + "### Adding Geolocate Control\n", + "\n", + "The Geolocate control centers the map based on the user’s current location, a helpful feature for location-based applications." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cc8533d7", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map()\n", + "m.add_control(\"geolocate\", position=\"top-right\")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "c5f280bb", + "metadata": {}, + "source": [ + "### Adding Fullscreen Control\n", + "\n", + "Fullscreen control enables users to expand the map to full screen, enhancing focus and visual clarity. This is especially useful when viewing complex or large datasets." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "426cf8b9", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[11.255, 43.77], zoom=13, style=\"positron\", controls={})\n", + "m.add_control(\"fullscreen\", position=\"top-right\")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "7e4a1a99", + "metadata": {}, + "source": [ + "### Adding Navigation Control\n", + "\n", + "The Navigation control provides buttons for zooming and reorienting the map, improving the user's ability to navigate efficiently." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "df177557", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[11.255, 43.77], zoom=13, style=\"positron\", controls={})\n", + "m.add_control(\"navigation\", position=\"top-right\")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "c4eb425d", + "metadata": {}, + "source": [ + "### Adding Draw Control\n", + "\n", + "The Draw control enables users to interact with the map by adding shapes such as points, lines, and polygons. This control is essential for tasks requiring spatial data input directly on the map." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2b6535ae", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-100, 40], zoom=3, style=\"positron\")\n", + "m.add_draw_control(position=\"top-right\")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "246d50d5", + "metadata": {}, + "source": [ + "Two key methods for accessing drawn features:\n", + "\n", + "- **Selected Features**: Accesses only the currently selected features.\n", + "- **All Features**: Accesses all features added, regardless of selection, giving you full control over the spatial data on the map." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1c0af6a4", + "metadata": {}, + "outputs": [], + "source": [ + "m.draw_features_selected" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f0419d57", + "metadata": {}, + "outputs": [], + "source": [ + "m.draw_feature_collection_all" + ] + }, + { + "cell_type": "markdown", + "id": "fed30cd5", + "metadata": {}, + "source": [ + "## Adding Layers\n", + "\n", + "Adding layers to a map enhances the data it presents, allowing different types of basemaps, tile layers, and thematic overlays to be combined for in-depth analysis.\n", + "\n", + "### Adding Basemaps\n", + "\n", + "Basemaps provide a geographical context for the map. Using the `add_basemap` method, you can select from various basemaps, including `OpenTopoMap` and `Esri.WorldImagery`. Adding a layer control allows users to switch between multiple basemaps interactively." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "511d87e3", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map()\n", + "m.add_basemap(\"OpenTopoMap\")\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3dcd77ff", + "metadata": {}, + "outputs": [], + "source": [ + "m.add_basemap(\"Esri.WorldImagery\")" + ] + }, + { + "cell_type": "markdown", + "id": "fb33e356", + "metadata": {}, + "source": [ + "You can also add basemaps interactively, which provides flexibility for selecting the best background for your map content." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a7bf173b", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map()\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7c8c84e1", + "metadata": {}, + "outputs": [], + "source": [ + "m.add_basemap()" + ] + }, + { + "cell_type": "markdown", + "id": "361e776b", + "metadata": {}, + "source": [ + "### Adding XYZ Tile Layer\n", + "\n", + "XYZ tile layers allow integration of specific tile services like topographic, satellite, or other thematic imagery from XYZ tile servers. By specifying the URL and parameters such as `opacity` and `visibility`, XYZ layers can be customized and styled to fit the needs of your map." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2901996a", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map()\n", + "url = \"https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}\"\n", + "m.add_tile_layer(url, name=\"USGS Topo\", attribution=\"USGS\", opacity=1.0, visible=True)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "6d738aea", + "metadata": {}, + "source": [ + "### Adding WMS Layer\n", + "\n", + "Web Map Service (WMS) layers provide access to external datasets served by web servers, such as thematic maps or detailed satellite imagery. Adding a WMS layer involves specifying the WMS URL and layer names, which allows for overlaying various data types such as natural imagery or land cover classifications." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "43ce78c6", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-74.5447, 40.6892], zoom=8, style=\"liberty\")\n", + "url = \"https://img.nj.gov/imagerywms/Natural2015\"\n", + "layers = \"Natural2015\"\n", + "m.add_wms_layer(url, layers=layers, before_id=\"aeroway_fill\")\n", + "\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e5c3ce89", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-100.307965, 46.98692], zoom=13, pitch=45, style=\"liberty\")\n", + "m.add_basemap(\"Esri.WorldImagery\")\n", + "url = \"https://fwspublicservices.wim.usgs.gov/wetlandsmapservice/services/Wetlands/MapServer/WMSServer\"\n", + "m.add_wms_layer(url, layers=\"1\", name=\"NWI\", opacity=0.6)\n", + "m.add_legend(builtin_legend=\"NWI\", title=\"Wetland Type\")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "e9869278", + "metadata": {}, + "source": [ + "## 3D Terrain\n", + "\n", + "To visualize 3D terrain, you can use the `set_terrain` method." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7687ef8e", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(\n", + " center=[-122.1874314, 46.2022386],\n", + " zoom=13,\n", + " pitch=60,\n", + " bearing=220,\n", + " projection=\"globe\",\n", + ")\n", + "m.add_basemap(\"Esri.WorldImagery\")\n", + "m.set_terrain()\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "9acdfe19", + "metadata": {}, + "source": [ + "### 3D Buildings\n", + "\n", + "Adding 3D buildings enhances urban visualizations, showing buildings with height variations. The setup involves specifying the MapTiler API key for vector tiles and adding building data as a 3D extrusion layer. The extrusion height and color can be set based on data attributes to visualize structures with varying heights, which can be useful in city planning and urban analysis." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "02de109f", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(\n", + " center=[-74.01201, 40.70473],\n", + " zoom=16,\n", + " pitch=60,\n", + " bearing=35,\n", + ")\n", + "m.add_basemap(\"Esri.WorldImagery\", visible=False)\n", + "m.add_overture_3d_buildings()\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "e22d8a9e", + "metadata": {}, + "source": [ + "### 3D Indoor Mapping\n", + "\n", + "Indoor mapping data can be visualized by loading a GeoJSON file and applying the `add_geojson` method. This setup allows for displaying floorplans with attributes such as color, height, and opacity. It provides a realistic indoor perspective, which is useful for visualizing complex structures or navigating interior spaces." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4876b90b", + "metadata": {}, + "outputs": [], + "source": [ + "data = \"https://maplibre.org/maplibre-gl-js/docs/assets/indoor-3d-map.geojson\"\n", + "gdf = leafmap.geojson_to_gdf(data)\n", + "gdf.explore()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0214d4f9", + "metadata": {}, + "outputs": [], + "source": [ + "gdf.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "96e76cd3", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(\n", + " center=(-87.61694, 41.86625), zoom=17, pitch=40, bearing=20, style=\"positron\"\n", + ")\n", + "m.add_basemap(\"OpenStreetMap.Mapnik\")\n", + "m.add_geojson(\n", + " data,\n", + " layer_type=\"fill-extrusion\",\n", + " name=\"floorplan\",\n", + " paint={\n", + " \"fill-extrusion-color\": [\"get\", \"color\"],\n", + " \"fill-extrusion-height\": [\"get\", \"height\"],\n", + " \"fill-extrusion-base\": [\"get\", \"base_height\"],\n", + " \"fill-extrusion-opacity\": 0.5,\n", + " },\n", + ")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "62ea70f3", + "metadata": {}, + "source": [ + "## Visualizing Vector Data\n", + "\n", + "Leafmap supports visualizing vector data using the `add_vector` method. This method allows you to add vector data to the map, which can be used to display features like points, lines, and polygons." + ] + }, + { + "cell_type": "markdown", + "id": "543c3275", + "metadata": {}, + "source": [ + "### Point Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "035a8033", + "metadata": {}, + "outputs": [], + "source": [ + "url = (\n", + " \"https://github.com/opengeos/datasets/releases/download/world/world_cities.geojson\"\n", + ")\n", + "m = leafmap.Map(style=\"liberty\", projection=\"globe\")\n", + "m.add_vector(url, name=\"cities\")\n", + "m.add_popup(\"cities\")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "3600568e", + "metadata": {}, + "source": [ + "### Line Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c3c6798d", + "metadata": {}, + "outputs": [], + "source": [ + "url = \"https://data.gishub.org/duckdb/cables.geojson\"\n", + "m = leafmap.Map(style=\"liberty\", projection=\"globe\")\n", + "m.add_vector(url, name=\"cables\")\n", + "m.add_popup(\"cables\")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "0df3860d", + "metadata": {}, + "source": [ + "### Polygon Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "446529a2", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(style=\"liberty\", projection=\"globe\")\n", + "data = \"https://github.com/opengeos/datasets/releases/download/vector/countries.geojson\"\n", + "m.add_data(\n", + " data,\n", + " column=\"POP_EST\",\n", + " scheme=\"Quantiles\",\n", + " cmap=\"Blues\",\n", + " legend_title=\"Population\",\n", + " name=\"Population\",\n", + " before_id=m.first_symbol_layer_id,\n", + " extrude=True,\n", + " scale_factor=1000,\n", + ")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "53ad27b0", + "metadata": {}, + "source": [ + "## Visualizing Remote Sensing Data\n", + "\n", + "### Local Raster Data\n", + "\n", + "To visualize local raster files, use the `add_raster` method. In the example, a Landsat image is downloaded and displayed using two different band combinations:\n", + "\n", + "- **Band Combination 3-2-1 (True Color)**: Simulates natural colors in the RGB channels.\n", + "- **Band Combination 4-3-2**: Enhances vegetation, displaying it in red for better visual contrast.\n", + " These layers are added to the map along with controls to toggle them. You can adjust brightness and contrast with the `vmin` and `vmax` arguments to improve clarity." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "deb87e56", + "metadata": {}, + "outputs": [], + "source": [ + "url = \"https://github.com/opengeos/datasets/releases/download/raster/landsat.tif\"\n", + "filepath = \"landsat.tif\"\n", + "leafmap.download_file(url, filepath, quiet=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "93277577", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(style=\"streets\")\n", + "m.add_raster(filepath, indexes=[3, 2, 1], vmin=0, vmax=100, name=\"Landsat-321\")\n", + "m.add_raster(filepath, indexes=[4, 3, 2], vmin=0, vmax=100, name=\"Landsat-432\")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "5c0af561", + "metadata": {}, + "source": [ + "A Digital Elevation Model (DEM) is also downloaded and visualized with a terrain color scheme. Leafmap’s `layer_interact` method allows interactive adjustments." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "461b4068", + "metadata": {}, + "outputs": [], + "source": [ + "url = \"https://github.com/opengeos/datasets/releases/download/raster/srtm90.tif\"\n", + "filepath = \"srtm90.tif\"\n", + "leafmap.download_file(url, filepath, quiet=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e1ce5ca9", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(style=\"liberty\")\n", + "m.add_raster(filepath, colormap=\"terrain\", name=\"DEM\")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "f544b8c8", + "metadata": {}, + "source": [ + "### Cloud Optimized GeoTIFF (COG)\n", + "\n", + "Cloud Optimized GeoTIFFs (COG) are large raster files stored on cloud platforms, allowing efficient streaming and loading. This example loads satellite imagery of Libya before and after an event, showing the change over time. Each image is loaded with `add_cog_layer`, and layers can be toggled for comparison. Using `fit_bounds`, the map centers on the COG layer to fit its boundaries.\n", + "\n", + "**Best Practices for Working with COGs:**\n", + "\n", + "- Use COGs for large raster datasets to enable fast, partial data loading\n", + "- Store COGs on cloud storage (AWS S3, Google Cloud Storage, Azure) for optimal performance\n", + "- Apply appropriate `rescale` values to optimize visualization contrast\n", + "- Use `nodata` parameter to handle missing data values correctly\n", + "- Leverage `fit_bounds=True` to automatically zoom to your data extent" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d1d1c629", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(style=\"liberty\")\n", + "before = (\n", + " \"https://github.com/opengeos/datasets/releases/download/raster/Libya-2023-07-01.tif\"\n", + ")\n", + "after = (\n", + " \"https://github.com/opengeos/datasets/releases/download/raster/Libya-2023-09-13.tif\"\n", + ")\n", + "m.add_cog_layer(before, name=\"Before\", attribution=\"Maxar\")\n", + "m.add_cog_layer(after, name=\"After\", attribution=\"Maxar\", fit_bounds=True)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "06be2283", + "metadata": {}, + "source": [ + "### STAC Layer\n", + "\n", + "The SpatioTemporal Asset Catalog (STAC) standard organizes large satellite data collections. With `add_stac_layer`, this example loads Canadian satellite data, displaying both a panchromatic and an RGB layer from the same source. This approach allows easy switching between views." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d5fbe001", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(style=\"streets\")\n", + "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\"\n", + "m.add_stac_layer(url, bands=[\"pan\"], name=\"Panchromatic\", vmin=0, vmax=150)\n", + "m.add_stac_layer(url, bands=[\"B4\", \"B3\", \"B2\"], name=\"RGB\", vmin=0, vmax=150)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "d016bfa6", + "metadata": {}, + "source": [ + "### Adding HTML\n", + "\n", + "Embed custom HTML content to display various HTML elements, such as emojis or stylized text. You can also adjust the font size and background transparency for better integration into the map design." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2b45b8bc", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-100, 40], zoom=3, style=\"positron\")\n", + "html = \"\"\"\n", + "\n", + "\n", + "\n", + "\n", + "🚀\n", + "

I will display 🚁

\n", + "

I will display 🚂

\n", + "\n", + "\n", + "\n", + "\"\"\"\n", + "m.add_html(html, bg_color=\"transparent\")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "6f7010e4", + "metadata": {}, + "source": [ + "## Adding Components to the Map\n", + "\n", + "### Adding Color bar\n", + "\n", + "Adding a color bar enhances data interpretation. In the example:\n", + "\n", + "1. A Digital Elevation Model (DEM) is displayed with a color ramp from 0 to 1500 meters.\n", + "2. `add_colorbar` method is used to create a color bar with labels, adjusting its position, opacity, and orientation for optimal readability." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b437777b", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0a9d1d62", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(style=\"liberty\")\n", + "dem = \"https://github.com/opengeos/datasets/releases/download/raster/dem.tif\"\n", + "m.add_cog_layer(\n", + " dem,\n", + " name=\"DEM\",\n", + " colormap_name=\"terrain\",\n", + " rescale=\"0, 1500\",\n", + " fit_bounds=True,\n", + " nodata=np.nan,\n", + ")\n", + "m.add_colorbar(\n", + " cmap=\"terrain\", vmin=0, vmax=1500, label=\"Elevation (m)\", position=\"bottom-right\"\n", + ")\n", + "\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "35d677bc", + "metadata": {}, + "source": [ + "Make the color bar background transparent to blend seamlessly with the map." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a8257314", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(style=\"liberty\")\n", + "m.add_cog_layer(\n", + " dem,\n", + " name=\"DEM\",\n", + " colormap_name=\"terrain\",\n", + " rescale=\"0, 1500\",\n", + " nodata=np.nan,\n", + " fit_bounds=True,\n", + ")\n", + "m.add_colorbar(\n", + " cmap=\"terrain\",\n", + " vmin=0,\n", + " vmax=1500,\n", + " label=\"Elevation (m)\",\n", + " position=\"bottom-right\",\n", + " transparent=True,\n", + ")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "c0c9821f", + "metadata": {}, + "source": [ + "Make the color bar vertical for a different layout." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "07af7fa9", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(style=\"liberty\")\n", + "m.add_cog_layer(\n", + " dem,\n", + " name=\"DEM\",\n", + " colormap_name=\"terrain\",\n", + " rescale=\"0, 1500\",\n", + " nodata=np.nan,\n", + " fit_bounds=True,\n", + ")\n", + "m.add_colorbar(\n", + " cmap=\"terrain\",\n", + " vmin=0,\n", + " vmax=1500,\n", + " label=\"Elevation (m)\",\n", + " position=\"bottom-right\",\n", + " width=0.2,\n", + " height=3,\n", + " orientation=\"vertical\",\n", + ")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "c6e4ca7d", + "metadata": {}, + "source": [ + "### Adding Legend\n", + "\n", + "Custom legends help users understand data classifications. Two methods are shown:\n", + "\n", + "1. Using built-in legends, such as for NLCD (National Land Cover Database) or wetland types.\n", + "2. Custom legends are built with a dictionary of land cover types and colors. This legend provides descriptive color-coding for various land cover types, with configurable background opacity to blend with the map." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c9f65a8e", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-100, 40], zoom=3, style=\"positron\")\n", + "m.add_basemap(\"Esri.WorldImagery\")\n", + "url = \"https://www.mrlc.gov/geoserver/mrlc_display/NLCD_2021_Land_Cover_L48/wms\"\n", + "layers = \"NLCD_2021_Land_Cover_L48\"\n", + "m.add_wms_layer(url, layers=layers, name=\"NLCD 2021\")\n", + "m.add_legend(\n", + " title=\"NLCD Land Cover Type\",\n", + " builtin_legend=\"NLCD\",\n", + " bg_color=\"rgba(255, 255, 255, 0.5)\",\n", + " position=\"bottom-left\",\n", + ")\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "667dd2dc", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-100, 40], zoom=3, style=\"positron\")\n", + "m.add_basemap(\"Esri.WorldImagery\")\n", + "url = \"https://fwspublicservices.wim.usgs.gov/wetlandsmapservice/services/Wetlands/MapServer/WMSServer\"\n", + "m.add_wms_layer(url, layers=\"1\", name=\"NWI\", opacity=0.6)\n", + "m.add_legend(builtin_legend=\"NWI\", title=\"Wetland Type\")\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c1987438", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-100, 40], zoom=3, style=\"positron\")\n", + "m.add_basemap(\"Esri.WorldImagery\")\n", + "url = \"https://www.mrlc.gov/geoserver/mrlc_display/NLCD_2021_Land_Cover_L48/wms\"\n", + "layers = \"NLCD_2021_Land_Cover_L48\"\n", + "m.add_wms_layer(url, layers=layers, name=\"NLCD 2021\")\n", + "\n", + "legend_dict = {\n", + " \"11 Open Water\": \"466b9f\",\n", + " \"12 Perennial Ice/Snow\": \"d1def8\",\n", + " \"21 Developed, Open Space\": \"dec5c5\",\n", + " \"22 Developed, Low Intensity\": \"d99282\",\n", + " \"23 Developed, Medium Intensity\": \"eb0000\",\n", + " \"24 Developed High Intensity\": \"ab0000\",\n", + " \"31 Barren Land (Rock/Sand/Clay)\": \"b3ac9f\",\n", + " \"41 Deciduous Forest\": \"68ab5f\",\n", + " \"42 Evergreen Forest\": \"1c5f2c\",\n", + " \"43 Mixed Forest\": \"b5c58f\",\n", + " \"51 Dwarf Scrub\": \"af963c\",\n", + " \"52 Shrub/Scrub\": \"ccb879\",\n", + " \"71 Grassland/Herbaceous\": \"dfdfc2\",\n", + " \"72 Sedge/Herbaceous\": \"d1d182\",\n", + " \"73 Lichens\": \"a3cc51\",\n", + " \"74 Moss\": \"82ba9e\",\n", + " \"81 Pasture/Hay\": \"dcd939\",\n", + " \"82 Cultivated Crops\": \"ab6c28\",\n", + " \"90 Woody Wetlands\": \"b8d9eb\",\n", + " \"95 Emergent Herbaceous Wetlands\": \"6c9fb8\",\n", + "}\n", + "m.add_legend(\n", + " title=\"NLCD Land Cover Type\",\n", + " legend_dict=legend_dict,\n", + " bg_color=\"rgba(255, 255, 255, 0.5)\",\n", + " position=\"bottom-left\",\n", + ")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "6f1e1fc8", + "metadata": {}, + "source": [ + "### Adding Video\n", + "\n", + "Videos can be added with geographic context by specifying corner coordinates. Videos must be listed in multiple formats to ensure compatibility across browsers. The coordinates array should define the video’s location on the map in the order: top-left, top-right, bottom-right, and bottom-left. This is demonstrated by adding drone footage to a satellite map view, enhancing the user experience with real-world visuals." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "46ed6adb", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-122.514426, 37.562984], zoom=17, bearing=-96, style=\"liberty\")\n", + "m.add_basemap(\"Esri.WorldImagery\")\n", + "urls = [\n", + " \"https://static-assets.mapbox.com/mapbox-gl-js/drone.mp4\",\n", + " \"https://static-assets.mapbox.com/mapbox-gl-js/drone.webm\",\n", + "]\n", + "coordinates = [\n", + " [-122.51596391201019, 37.56238816766053],\n", + " [-122.51467645168304, 37.56410183312965],\n", + " [-122.51309394836426, 37.563391708549425],\n", + " [-122.51423120498657, 37.56161849366671],\n", + "]\n", + "m.add_video(urls, coordinates)\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e53089dc", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-115, 25], zoom=4, style=\"liberty\")\n", + "m.add_basemap(\"Esri.WorldImagery\")\n", + "urls = [\n", + " \"https://data.opengeos.org/patricia_nasa.mp4\",\n", + " \"https://data.opengeos.org/patricia_nasa.webm\",\n", + "]\n", + "coordinates = [\n", + " [-130, 32],\n", + " [-100, 32],\n", + " [-100, 13],\n", + " [-130, 13],\n", + "]\n", + "m.add_video(urls, coordinates)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "91ef8883", + "metadata": {}, + "source": [ + "## PMTiles\n", + "\n", + "Leafmap supports visualizing [PMTiles](https://protomaps.com/docs/pmtiles/), which enables efficient storage and fast rendering of vector tiles directly in the browser.\n", + "\n", + "**Why PMTiles?**\n", + "\n", + "- Single-file archive format for vector tiles (no server-side processing needed)\n", + "- Efficient range request support for cloud-native applications\n", + "- Significantly faster than traditional tile servers for static datasets\n", + "- Can be hosted on any static file server or cloud storage\n", + "- Ideal for large-scale building footprints, field boundaries, and other vector data" + ] + }, + { + "cell_type": "markdown", + "id": "f3709a88", + "metadata": {}, + "source": [ + "### Building Footprint Data\n", + "\n", + "Visualize the [Google-Microsoft Open Buildings dataset](https://beta.source.coop/repositories/vida/google-microsoft-open-buildings/description), managed by VIDA, in PMTiles format. Fetch metadata to identify available layers, apply custom styles to the building footprints, and render them with semi-transparent colors for a clear visualization." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "34778d70", + "metadata": {}, + "outputs": [], + "source": [ + "url = \"https://data.source.coop/vida/google-microsoft-open-buildings/pmtiles/go_ms_building_footprints.pmtiles\"\n", + "metadata = leafmap.pmtiles_metadata(url)\n", + "print(f\"layer names: {metadata['layer_names']}\")\n", + "print(f\"bounds: {metadata['bounds']}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a4f203de", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[0, 20], zoom=2)\n", + "m.add_basemap(\"Esri.WorldImagery\", visible=False)\n", + "\n", + "style = {\n", + " \"version\": 8,\n", + " \"sources\": {\n", + " \"example_source\": {\n", + " \"type\": \"vector\",\n", + " \"url\": \"pmtiles://\" + url,\n", + " \"attribution\": \"PMTiles\",\n", + " }\n", + " },\n", + " \"layers\": [\n", + " {\n", + " \"id\": \"buildings\",\n", + " \"source\": \"example_source\",\n", + " \"source-layer\": \"building_footprints\",\n", + " \"type\": \"fill\",\n", + " \"paint\": {\"fill-color\": \"#3388ff\", \"fill-opacity\": 0.5},\n", + " },\n", + " ],\n", + "}\n", + "\n", + "# style = leafmap.pmtiles_style(url) # Use default style\n", + "\n", + "m.add_pmtiles(\n", + " url,\n", + " style=style,\n", + " visible=True,\n", + " opacity=1.0,\n", + " tooltip=True,\n", + ")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "bc9f2b70", + "metadata": {}, + "source": [ + "### Fields of The World\n", + "\n", + "Visualize the Agricultural Field Boundary dataset - Fields of The World ([FTW](https://fieldsofthe.world)). The dataset is available on Source Cooperative at https://source.coop/repositories/kerner-lab/fields-of-the-world/description." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c09642b8", + "metadata": {}, + "outputs": [], + "source": [ + "url = \"https://data.source.coop/kerner-lab/fields-of-the-world/ftw-sources.pmtiles\"\n", + "metadata = leafmap.pmtiles_metadata(url)\n", + "print(f\"layer names: {metadata['layer_names']}\")\n", + "print(f\"bounds: {metadata['bounds']}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a1080be9", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map()\n", + "# Define colors for each last digit (0-9)\n", + "style = {\n", + " \"layers\": [\n", + " {\n", + " \"id\": \"Field Polygon\",\n", + " \"source\": \"example_source\",\n", + " \"source-layer\": \"ftw-sources\",\n", + " \"type\": \"fill\",\n", + " \"paint\": {\n", + " \"fill-color\": [\n", + " \"case\",\n", + " [\"==\", [\"%\", [\"to-number\", [\"get\", \"id\"]], 10], 0],\n", + " \"#FF5733\", # Color for last digit 0\n", + " [\"==\", [\"%\", [\"to-number\", [\"get\", \"id\"]], 10], 1],\n", + " \"#33FF57\", # Color for last digit 1\n", + " [\"==\", [\"%\", [\"to-number\", [\"get\", \"id\"]], 10], 2],\n", + " \"#3357FF\", # Color for last digit 2\n", + " [\"==\", [\"%\", [\"to-number\", [\"get\", \"id\"]], 10], 3],\n", + " \"#FF33A1\", # Color for last digit 3\n", + " [\"==\", [\"%\", [\"to-number\", [\"get\", \"id\"]], 10], 4],\n", + " \"#FF8C33\", # Color for last digit 4\n", + " [\"==\", [\"%\", [\"to-number\", [\"get\", \"id\"]], 10], 5],\n", + " \"#33FFF6\", # Color for last digit 5\n", + " [\"==\", [\"%\", [\"to-number\", [\"get\", \"id\"]], 10], 6],\n", + " \"#A833FF\", # Color for last digit 6\n", + " [\"==\", [\"%\", [\"to-number\", [\"get\", \"id\"]], 10], 7],\n", + " \"#FF333D\", # Color for last digit 7\n", + " [\"==\", [\"%\", [\"to-number\", [\"get\", \"id\"]], 10], 8],\n", + " \"#33FFBD\", # Color for last digit 8\n", + " [\"==\", [\"%\", [\"to-number\", [\"get\", \"id\"]], 10], 9],\n", + " \"#FF9933\", # Color for last digit 9\n", + " \"#FF0000\", # Fallback color if no match\n", + " ],\n", + " \"fill-opacity\": 0.5,\n", + " },\n", + " },\n", + " {\n", + " \"id\": \"Field Outline\",\n", + " \"source\": \"example_source\",\n", + " \"source-layer\": \"ftw-sources\",\n", + " \"type\": \"line\",\n", + " \"paint\": {\"line-color\": \"#ffffff\", \"line-width\": 1, \"line-opacity\": 1},\n", + " },\n", + " ],\n", + "}\n", + "\n", + "m.add_basemap(\"Satellite\")\n", + "m.add_pmtiles(url, style=style, name=\"FTW\", zoom_to_layer=False)\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "558cf2ce", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map()\n", + "style = {\n", + " \"layers\": [\n", + " {\n", + " \"id\": \"Field Polygon\",\n", + " \"source\": \"example_source\",\n", + " \"source-layer\": \"ftw-sources\",\n", + " \"type\": \"fill\",\n", + " \"paint\": {\n", + " \"fill-color\": \"#ffff00\",\n", + " \"fill-opacity\": 0.2,\n", + " },\n", + " },\n", + " {\n", + " \"id\": \"Field Outline\",\n", + " \"source\": \"example_source\",\n", + " \"source-layer\": \"ftw-sources\",\n", + " \"type\": \"line\",\n", + " \"paint\": {\"line-color\": \"#ff0000\", \"line-width\": 1, \"line-opacity\": 1},\n", + " },\n", + " ],\n", + "}\n", + "\n", + "m.add_basemap(\"Satellite\")\n", + "m.add_pmtiles(url, style=style, name=\"FTW\", zoom_to_layer=False)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "a86b32df", + "metadata": {}, + "source": [ + "### 3D PMTiles\n", + "\n", + "Render global building data in 3D for a realistic, textured experience. Set building colors and extrusion heights to create visually compelling cityscapes. For example, apply color gradients and height scaling based on building attributes to differentiate buildings by their heights." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "778ff6c1", + "metadata": {}, + "outputs": [], + "source": [ + "url = \"https://data.source.coop/cholmes/overture/overture-buildings.pmtiles\"\n", + "metadata = leafmap.pmtiles_metadata(url)\n", + "print(f\"layer names: {metadata['layer_names']}\")\n", + "print(f\"bounds: {metadata['bounds']}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ef645705", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(\n", + " center=[-74.0095, 40.7046], zoom=16, pitch=60, bearing=-17, style=\"positron\"\n", + ")\n", + "m.add_basemap(\"OpenStreetMap.Mapnik\")\n", + "m.add_basemap(\"Esri.WorldImagery\", visible=False)\n", + "\n", + "style = {\n", + " \"layers\": [\n", + " {\n", + " \"id\": \"buildings\",\n", + " \"source\": \"example_source\",\n", + " \"source-layer\": \"buildings\",\n", + " \"type\": \"fill-extrusion\",\n", + " \"filter\": [\n", + " \">\",\n", + " [\"get\", \"height\"],\n", + " 0,\n", + " ], # only show buildings with height info\n", + " \"paint\": {\n", + " \"fill-extrusion-color\": [\n", + " \"interpolate\",\n", + " [\"linear\"],\n", + " [\"get\", \"height\"],\n", + " 0,\n", + " \"lightgray\",\n", + " 200,\n", + " \"royalblue\",\n", + " 400,\n", + " \"lightblue\",\n", + " ],\n", + " \"fill-extrusion-height\": [\"*\", [\"get\", \"height\"], 1],\n", + " },\n", + " },\n", + " ],\n", + "}\n", + "\n", + "m.add_pmtiles(\n", + " url,\n", + " style=style,\n", + " visible=True,\n", + " opacity=1.0,\n", + " tooltip=True,\n", + " template=\"Height: {{height}}
Country: {{country_iso}}\",\n", + " fit_bounds=False,\n", + ")\n", + "\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "29b1a04b", + "metadata": {}, + "source": [ + "## H3 Hexagonal Grid\n", + "\n", + "[H3](https://h3geo.org) is a hexagonal grid system that provides a way to represent and analyze geospatial data in a hexagonal grid. It is a popular choice for visualizing and analyzing geospatial data, especially for large datasets." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f68d582e", + "metadata": {}, + "outputs": [], + "source": [ + "url = \"https://data.gishub.org/duckdb/h3_res4_geo.parquet\"\n", + "gdf = leafmap.read_vector(url)\n", + "gdf" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0bc298f6", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map()\n", + "m.add_basemap(\"Esri.WorldImagery\", before_id=m.first_symbol_layer_id, visible=False)\n", + "m.add_data(\n", + " gdf,\n", + " column=\"building_count\",\n", + " scheme=\"JenksCaspall\",\n", + " cmap=\"inferno\",\n", + " outline_color=\"rgba(255, 255, 255, 0)\",\n", + " name=\"H3 Hexagon\",\n", + " before_id=m.first_symbol_layer_id,\n", + ")\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bdb91626", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[7.062832, -4.144790], pitch=45.5, zoom=1.57)\n", + "m.add_basemap(\"Esri.WorldImagery\", before_id=m.first_symbol_layer_id, visible=False)\n", + "m.add_data(\n", + " gdf,\n", + " column=\"building_count\",\n", + " scheme=\"JenksCaspall\",\n", + " cmap=\"inferno\",\n", + " outline_color=\"rgba(255, 255, 255, 0)\",\n", + " name=\"H3 Hexagon\",\n", + " before_id=m.first_symbol_layer_id,\n", + " extrude=True,\n", + " fit_bounds=False,\n", + ")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "d6def81e", + "metadata": {}, + "source": [ + "## Summary and Best Practices\n", + "\n", + "Congratulations! You've learned how to create interactive geospatial visualizations using Leafmap with MapLibre GL JS. This workshop covered:\n", + "\n", + "- **Map Creation**: Setting up interactive maps with customizable styles and projections\n", + "- **Data Visualization**: Working with raster data (local files, COGs, STAC), vector data (GeoJSON, PMTiles), and 3D visualizations\n", + "- **Dynamic Tile Serving**: Using TiTiler for on-demand raster tile generation\n", + "- **Advanced Features**: Adding controls, legends, color bars, and custom HTML elements\n", + "\n", + "### Key Takeaways\n", + "\n", + "1. **Choose the Right Format**:\n", + "\n", + " - Use COGs for large raster datasets in cloud storage\n", + " - Use PMTiles for vector data (buildings, boundaries, points of interest)\n", + " - Use TiTiler when you need dynamic rendering with custom parameters\n", + " - Use STAC for organized collections of geospatial assets\n", + "\n", + "2. **Performance Optimization**:\n", + "\n", + " - Leverage cloud-native formats (COG, PMTiles) for better performance\n", + " - Use appropriate zoom levels and bounds to limit data loading\n", + " - Apply data rescaling and color mapping efficiently\n", + " - Consider using `before_id` parameter to control layer ordering\n", + "\n", + "3. **Visualization Best Practices**:\n", + "\n", + " - Add legends and color bars to make your maps interpretable\n", + " - Use appropriate color schemes for your data type (sequential, diverging, categorical)\n", + " - Leverage 3D visualizations (terrain, buildings) when appropriate\n", + " - Combine multiple basemaps and layers for comprehensive analysis\n", + "\n", + "4. **Interactive Features**:\n", + " - Add drawing controls for user interaction\n", + " - Enable tooltips to display attribute information\n", + " - Use layer controls to toggle between datasets\n", + " - Implement fullscreen and navigation controls for better UX\n", + "\n", + "### Next Steps\n", + "\n", + "To continue learning:\n", + "\n", + "- Explore the [Leafmap documentation](https://leafmap.org) for more examples\n", + "- Check out [MapLibre GL JS documentation](https://maplibre.org/maplibre-gl-js/docs) for advanced styling\n", + "- Visit [Source Cooperative](https://source.coop) for more open geospatial datasets\n", + "- Experiment with custom styling using MapLibre style specifications\n", + "\n", + "Happy mapping!" + ] + } + ], + "metadata": { + "authors": [ + { + "affiliations": [ + "University of Tennessee, Knoxville" + ], + "email": "qwu18@utk.edu", + "github": "giswqs", + "name": "Qiusheng Wu", + "orcid": "0000-0001-5437-4073" + } + ], + "jupytext": { + "default_lexer": "ipython3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/modules/02-interactive-viz/index.md b/modules/02-interactive-viz/index.md deleted file mode 100644 index 020b930..0000000 --- a/modules/02-interactive-viz/index.md +++ /dev/null @@ -1,965 +0,0 @@ ---- -authors: - - name: "Qiusheng Wu" - affiliations: - - "University of Tennessee, Knoxville" - email: "qwu18@utk.edu" - orcid: "0000-0001-5437-4073" - github: "giswqs" ---- - -# 👁️ 2 - Interactive visualization of raster and vector data - -[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/opengeos/leafmap/blob/master/docs/workshops/AGU_2025.ipynb) - -## Introduction - -This notebook is for the workshop ([Open Source Geospatial Workflows in the Cloud](https://geojupyter.github.io/workshop-open-source-geospatial)) presented at the [AGU Fall Meeting 2025](https://agu.confex.com/agu/agu25/meetingapp.cgi/Session/252640). - -### Learning Objectives - -By the end of this workshop, you will be able to: - -- Create interactive maps with MapLibre using Python -- Add and customize various data layers (raster, vector, 3D) -- Visualize remote sensing data including COGs, STAC, and local rasters -- Work with PMTiles for efficient vector tile rendering -- Create 3D visualizations including terrain, buildings, and indoor maps -- Use TiTiler for dynamic raster tile serving -- Apply custom styling and legends to enhance map readability - -## Useful Resources - -- [MapLibre GL JS Documentation](https://maplibre.org/maplibre-gl-js/docs): Comprehensive documentation for MapLibre GL JS. -- [MapLibre Python Bindings](https://github.com/eoda-dev/py-maplibregl): Information on using MapLibre with Python. -- [MapLibre in Leafmap](https://leafmap.org/maplibre/overview): Examples and tutorials for MapLibre in Leafmap. -- [Video Tutorials](https://bit.ly/maplibre): Video guides for practical MapLibre skills. -- [MapLibre Demos](https://maps.gishub.org): Interactive demos showcasing MapLibre's capabilities. - -## Table of Contents - -- [👁️ 2 - Interactive visualization of raster and vector data](#️-2---interactive-visualization-of-raster-and-vector-data) - - [Introduction](#introduction) - - [Learning Objectives](#learning-objectives) - - [Useful Resources](#useful-resources) - - [Table of Contents](#table-of-contents) - - [Installation and Setup](#installation-and-setup) - - [Creating Interactive Maps](#creating-interactive-maps) - - [Basic Map Setup](#basic-map-setup) - - [Customizing the Map's Center and Zoom Level](#customizing-the-maps-center-and-zoom-level) - - [Choosing a Basemap Style](#choosing-a-basemap-style) - - [Adding Map Controls](#adding-map-controls) - - [Available Controls](#available-controls) - - [Adding Geolocate Control](#adding-geolocate-control) - - [Adding Fullscreen Control](#adding-fullscreen-control) - - [Adding Navigation Control](#adding-navigation-control) - - [Adding Draw Control](#adding-draw-control) - - [Adding Layers](#adding-layers) - - [Adding Basemaps](#adding-basemaps) - - [Adding XYZ Tile Layer](#adding-xyz-tile-layer) - - [Adding WMS Layer](#adding-wms-layer) - - [3D Terrain](#3d-terrain) - - [3D Buildings](#3d-buildings) - - [3D Indoor Mapping](#3d-indoor-mapping) - - [Visualizing Vector Data](#visualizing-vector-data) - - [Point Data](#point-data) - - [Line Data](#line-data) - - [Polygon Data](#polygon-data) - - [Visualizing Remote Sensing Data](#visualizing-remote-sensing-data) - - [Local Raster Data](#local-raster-data) - - [Cloud Optimized GeoTIFF (COG)](#cloud-optimized-geotiff-cog) - - [STAC Layer](#stac-layer) - - [Adding HTML](#adding-html) - - [Adding Components to the Map](#adding-components-to-the-map) - - [Adding Color bar](#adding-color-bar) - - [Adding Legend](#adding-legend) - - [Adding Video](#adding-video) - - [PMTiles](#pmtiles) - - [Building Footprint Data](#building-footprint-data) - - [Fields of The World](#fields-of-the-world) - - [3D PMTiles](#3d-pmtiles) - - [H3 Hexagonal Grid](#h3-hexagonal-grid) - - [Summary and Best Practices](#summary-and-best-practices) - - [Key Takeaways](#key-takeaways) - - [Next Steps](#next-steps) - -## Installation and Setup - -To install the required packages, uncomment and run the line below. - -```{code-cell} ipython3 -# %pip install -U leafmap -``` - -Once installed, import the `maplibregl` backend from the `leafmap` package: - -```{code-cell} ipython3 -import leafmap.maplibregl as leafmap -``` - -## Creating Interactive Maps - -### Basic Map Setup - -Let’s start by creating a simple interactive map with default settings. This basic setup provides a simple map with the `dark-matter` style on which you can add data layers, controls, and other customizations. - -```{code-cell} ipython3 -m = leafmap.Map() -m -``` - -### Customizing the Map's Center and Zoom Level - -You can specify the map’s center (latitude and longitude), zoom level, pitch, and bearing for a more focused view. These parameters help direct the user's attention to specific areas. - -```{code-cell} ipython3 -m = leafmap.Map(center=[-100, 40], zoom=2, pitch=0, bearing=0, projection="globe") -m -``` - -### Choosing a Basemap Style - -MapLibre supports several pre-defined basemap styles such as `dark-matter`, `positron`, `voyager`, `liberty`, `demotiles`. You can also use custom basemap URLs for unique styling. - -```{code-cell} ipython3 -m = leafmap.Map(style="positron") -m -``` - -[OpenFreeMap](https://openfreemap.org) provides a variety of basemap styles that you can use in your interactive maps. These styles include `liberty`, `bright`, and `positron`. - -```{code-cell} ipython3 -m = leafmap.Map(style="liberty") -m -``` - -## Adding Map Controls - -Map controls enhance the usability of the map by allowing users to interact in various ways, adding elements like scale bars, zoom tools, and drawing options. - -### Available Controls - -- **Geolocate**: Centers the map based on the user’s current location, if available. -- **Fullscreen**: Expands the map to a full-screen view for better focus. -- **Navigation**: Provides zoom controls and a compass for reorientation. -- **Draw**: Allows users to draw and edit shapes on the map. - -### Adding Geolocate Control - -The Geolocate control centers the map based on the user’s current location, a helpful feature for location-based applications. - -```{code-cell} ipython3 -m = leafmap.Map() -m.add_control("geolocate", position="top-right") -m -``` - -### Adding Fullscreen Control - -Fullscreen control enables users to expand the map to full screen, enhancing focus and visual clarity. This is especially useful when viewing complex or large datasets. - -```{code-cell} ipython3 -m = leafmap.Map(center=[11.255, 43.77], zoom=13, style="positron", controls={}) -m.add_control("fullscreen", position="top-right") -m -``` - -### Adding Navigation Control - -The Navigation control provides buttons for zooming and reorienting the map, improving the user's ability to navigate efficiently. - -```{code-cell} ipython3 -m = leafmap.Map(center=[11.255, 43.77], zoom=13, style="positron", controls={}) -m.add_control("navigation", position="top-right") -m -``` - -### Adding Draw Control - -The Draw control enables users to interact with the map by adding shapes such as points, lines, and polygons. This control is essential for tasks requiring spatial data input directly on the map. - -```{code-cell} ipython3 -m = leafmap.Map(center=[-100, 40], zoom=3, style="positron") -m.add_draw_control(position="top-right") -m -``` - -Two key methods for accessing drawn features: - -- **Selected Features**: Accesses only the currently selected features. -- **All Features**: Accesses all features added, regardless of selection, giving you full control over the spatial data on the map. - -```{code-cell} ipython3 -m.draw_features_selected -``` - -```{code-cell} ipython3 -m.draw_feature_collection_all -``` - -## Adding Layers - -Adding layers to a map enhances the data it presents, allowing different types of basemaps, tile layers, and thematic overlays to be combined for in-depth analysis. - -### Adding Basemaps - -Basemaps provide a geographical context for the map. Using the `add_basemap` method, you can select from various basemaps, including `OpenTopoMap` and `Esri.WorldImagery`. Adding a layer control allows users to switch between multiple basemaps interactively. - -```{code-cell} ipython3 -m = leafmap.Map() -m.add_basemap("OpenTopoMap") -m -``` - -```{code-cell} ipython3 -m.add_basemap("Esri.WorldImagery") -``` - -You can also add basemaps interactively, which provides flexibility for selecting the best background for your map content. - -```{code-cell} ipython3 -m = leafmap.Map() -m -``` - -```{code-cell} ipython3 -m.add_basemap() -``` - -### Adding XYZ Tile Layer - -XYZ tile layers allow integration of specific tile services like topographic, satellite, or other thematic imagery from XYZ tile servers. By specifying the URL and parameters such as `opacity` and `visibility`, XYZ layers can be customized and styled to fit the needs of your map. - -```{code-cell} ipython3 -m = leafmap.Map() -url = "https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}" -m.add_tile_layer(url, name="USGS Topo", attribution="USGS", opacity=1.0, visible=True) -m -``` - -### Adding WMS Layer - -Web Map Service (WMS) layers provide access to external datasets served by web servers, such as thematic maps or detailed satellite imagery. Adding a WMS layer involves specifying the WMS URL and layer names, which allows for overlaying various data types such as natural imagery or land cover classifications. - -```{code-cell} ipython3 -m = leafmap.Map(center=[-74.5447, 40.6892], zoom=8, style="liberty") -url = "https://img.nj.gov/imagerywms/Natural2015" -layers = "Natural2015" -m.add_wms_layer(url, layers=layers, before_id="aeroway_fill") - -m -``` - -```{code-cell} ipython3 -m = leafmap.Map(center=[-100.307965, 46.98692], zoom=13, pitch=45, style="liberty") -m.add_basemap("Esri.WorldImagery") -url = "https://fwspublicservices.wim.usgs.gov/wetlandsmapservice/services/Wetlands/MapServer/WMSServer" -m.add_wms_layer(url, layers="1", name="NWI", opacity=0.6) -m.add_legend(builtin_legend="NWI", title="Wetland Type") -m -``` - -## 3D Terrain - -To visualize 3D terrain, you can use the `set_terrain` method. - -```{code-cell} ipython3 -m = leafmap.Map( - center=[-122.1874314, 46.2022386], - zoom=13, - pitch=60, - bearing=220, - projection="globe", -) -m.add_basemap("Esri.WorldImagery") -m.set_terrain() -m -``` - -### 3D Buildings - -Adding 3D buildings enhances urban visualizations, showing buildings with height variations. The setup involves specifying the MapTiler API key for vector tiles and adding building data as a 3D extrusion layer. The extrusion height and color can be set based on data attributes to visualize structures with varying heights, which can be useful in city planning and urban analysis. - -```{code-cell} ipython3 -m = leafmap.Map( - center=[-74.01201, 40.70473], - zoom=16, - pitch=60, - bearing=35, -) -m.add_basemap("Esri.WorldImagery", visible=False) -m.add_overture_3d_buildings() -m -``` - -### 3D Indoor Mapping - -Indoor mapping data can be visualized by loading a GeoJSON file and applying the `add_geojson` method. This setup allows for displaying floorplans with attributes such as color, height, and opacity. It provides a realistic indoor perspective, which is useful for visualizing complex structures or navigating interior spaces. - -```{code-cell} ipython3 -data = "https://maplibre.org/maplibre-gl-js/docs/assets/indoor-3d-map.geojson" -gdf = leafmap.geojson_to_gdf(data) -gdf.explore() -``` - -```{code-cell} ipython3 -gdf.head() -``` - -```{code-cell} ipython3 -m = leafmap.Map( - center=(-87.61694, 41.86625), zoom=17, pitch=40, bearing=20, style="positron" -) -m.add_basemap("OpenStreetMap.Mapnik") -m.add_geojson( - data, - layer_type="fill-extrusion", - name="floorplan", - paint={ - "fill-extrusion-color": ["get", "color"], - "fill-extrusion-height": ["get", "height"], - "fill-extrusion-base": ["get", "base_height"], - "fill-extrusion-opacity": 0.5, - }, -) -m -``` - -## Visualizing Vector Data - -Leafmap supports visualizing vector data using the `add_vector` method. This method allows you to add vector data to the map, which can be used to display features like points, lines, and polygons. - -+++ - -### Point Data - -```{code-cell} ipython3 -url = ( - "https://github.com/opengeos/datasets/releases/download/world/world_cities.geojson" -) -m = leafmap.Map(style="liberty", projection="globe") -m.add_vector(url, name="cities") -m.add_popup("cities") -m -``` - -### Line Data - -```{code-cell} ipython3 -url = "https://data.gishub.org/duckdb/cables.geojson" -m = leafmap.Map(style="liberty", projection="globe") -m.add_vector(url, name="cables") -m.add_popup("cables") -m -``` - -### Polygon Data - -```{code-cell} ipython3 -m = leafmap.Map(style="liberty", projection="globe") -data = "https://github.com/opengeos/datasets/releases/download/vector/countries.geojson" -m.add_data( - data, - column="POP_EST", - scheme="Quantiles", - cmap="Blues", - legend_title="Population", - name="Population", - before_id=m.first_symbol_layer_id, - extrude=True, - scale_factor=1000, -) -m -``` - -## Visualizing Remote Sensing Data - -### Local Raster Data - -To visualize local raster files, use the `add_raster` method. In the example, a Landsat image is downloaded and displayed using two different band combinations: - -- **Band Combination 3-2-1 (True Color)**: Simulates natural colors in the RGB channels. -- **Band Combination 4-3-2**: Enhances vegetation, displaying it in red for better visual contrast. - These layers are added to the map along with controls to toggle them. You can adjust brightness and contrast with the `vmin` and `vmax` arguments to improve clarity. - -```{code-cell} ipython3 -url = "https://github.com/opengeos/datasets/releases/download/raster/landsat.tif" -filepath = "landsat.tif" -leafmap.download_file(url, filepath, quiet=True) -``` - -```{code-cell} ipython3 -m = leafmap.Map(style="streets") -m.add_raster(filepath, indexes=[3, 2, 1], vmin=0, vmax=100, name="Landsat-321") -m.add_raster(filepath, indexes=[4, 3, 2], vmin=0, vmax=100, name="Landsat-432") -m -``` - -A Digital Elevation Model (DEM) is also downloaded and visualized with a terrain color scheme. Leafmap’s `layer_interact` method allows interactive adjustments. - -```{code-cell} ipython3 -url = "https://github.com/opengeos/datasets/releases/download/raster/srtm90.tif" -filepath = "srtm90.tif" -leafmap.download_file(url, filepath, quiet=True) -``` - -```{code-cell} ipython3 -m = leafmap.Map(style="liberty") -m.add_raster(filepath, colormap="terrain", name="DEM") -m -``` - -### Cloud Optimized GeoTIFF (COG) - -Cloud Optimized GeoTIFFs (COG) are large raster files stored on cloud platforms, allowing efficient streaming and loading. This example loads satellite imagery of Libya before and after an event, showing the change over time. Each image is loaded with `add_cog_layer`, and layers can be toggled for comparison. Using `fit_bounds`, the map centers on the COG layer to fit its boundaries. - -**Best Practices for Working with COGs:** - -- Use COGs for large raster datasets to enable fast, partial data loading -- Store COGs on cloud storage (AWS S3, Google Cloud Storage, Azure) for optimal performance -- Apply appropriate `rescale` values to optimize visualization contrast -- Use `nodata` parameter to handle missing data values correctly -- Leverage `fit_bounds=True` to automatically zoom to your data extent - -```{code-cell} ipython3 -m = leafmap.Map(style="liberty") -before = ( - "https://github.com/opengeos/datasets/releases/download/raster/Libya-2023-07-01.tif" -) -after = ( - "https://github.com/opengeos/datasets/releases/download/raster/Libya-2023-09-13.tif" -) -m.add_cog_layer(before, name="Before", attribution="Maxar") -m.add_cog_layer(after, name="After", attribution="Maxar", fit_bounds=True) -m -``` - -### STAC Layer - -The SpatioTemporal Asset Catalog (STAC) standard organizes large satellite data collections. With `add_stac_layer`, this example loads Canadian satellite data, displaying both a panchromatic and an RGB layer from the same source. This approach allows easy switching between views. - -```{code-cell} ipython3 -m = leafmap.Map(style="streets") -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" -m.add_stac_layer(url, bands=["pan"], name="Panchromatic", vmin=0, vmax=150) -m.add_stac_layer(url, bands=["B4", "B3", "B2"], name="RGB", vmin=0, vmax=150) -m -``` - -### Adding HTML - -Embed custom HTML content to display various HTML elements, such as emojis or stylized text. You can also adjust the font size and background transparency for better integration into the map design. - -```{code-cell} ipython3 -m = leafmap.Map(center=[-100, 40], zoom=3, style="positron") -html = """ - - - - -🚀 -

I will display 🚁

-

I will display 🚂

- - - -""" -m.add_html(html, bg_color="transparent") -m -``` - -## Adding Components to the Map - -### Adding Color bar - -Adding a color bar enhances data interpretation. In the example: - -1. A Digital Elevation Model (DEM) is displayed with a color ramp from 0 to 1500 meters. -2. `add_colorbar` method is used to create a color bar with labels, adjusting its position, opacity, and orientation for optimal readability. - -```{code-cell} ipython3 -import numpy as np -``` - -```{code-cell} ipython3 -m = leafmap.Map(style="liberty") -dem = "https://github.com/opengeos/datasets/releases/download/raster/dem.tif" -m.add_cog_layer( - dem, - name="DEM", - colormap_name="terrain", - rescale="0, 1500", - fit_bounds=True, - nodata=np.nan, -) -m.add_colorbar( - cmap="terrain", vmin=0, vmax=1500, label="Elevation (m)", position="bottom-right" -) - -m -``` - -Make the color bar background transparent to blend seamlessly with the map. - -```{code-cell} ipython3 -m = leafmap.Map(style="liberty") -m.add_cog_layer( - dem, - name="DEM", - colormap_name="terrain", - rescale="0, 1500", - nodata=np.nan, - fit_bounds=True, -) -m.add_colorbar( - cmap="terrain", - vmin=0, - vmax=1500, - label="Elevation (m)", - position="bottom-right", - transparent=True, -) -m -``` - -Make the color bar vertical for a different layout. - -```{code-cell} ipython3 -m = leafmap.Map(style="liberty") -m.add_cog_layer( - dem, - name="DEM", - colormap_name="terrain", - rescale="0, 1500", - nodata=np.nan, - fit_bounds=True, -) -m.add_colorbar( - cmap="terrain", - vmin=0, - vmax=1500, - label="Elevation (m)", - position="bottom-right", - width=0.2, - height=3, - orientation="vertical", -) -m -``` - -### Adding Legend - -Custom legends help users understand data classifications. Two methods are shown: - -1. Using built-in legends, such as for NLCD (National Land Cover Database) or wetland types. -2. Custom legends are built with a dictionary of land cover types and colors. This legend provides descriptive color-coding for various land cover types, with configurable background opacity to blend with the map. - -```{code-cell} ipython3 -m = leafmap.Map(center=[-100, 40], zoom=3, style="positron") -m.add_basemap("Esri.WorldImagery") -url = "https://www.mrlc.gov/geoserver/mrlc_display/NLCD_2021_Land_Cover_L48/wms" -layers = "NLCD_2021_Land_Cover_L48" -m.add_wms_layer(url, layers=layers, name="NLCD 2021") -m.add_legend( - title="NLCD Land Cover Type", - builtin_legend="NLCD", - bg_color="rgba(255, 255, 255, 0.5)", - position="bottom-left", -) -m -``` - -```{code-cell} ipython3 -m = leafmap.Map(center=[-100, 40], zoom=3, style="positron") -m.add_basemap("Esri.WorldImagery") -url = "https://fwspublicservices.wim.usgs.gov/wetlandsmapservice/services/Wetlands/MapServer/WMSServer" -m.add_wms_layer(url, layers="1", name="NWI", opacity=0.6) -m.add_legend(builtin_legend="NWI", title="Wetland Type") -m -``` - -```{code-cell} ipython3 -m = leafmap.Map(center=[-100, 40], zoom=3, style="positron") -m.add_basemap("Esri.WorldImagery") -url = "https://www.mrlc.gov/geoserver/mrlc_display/NLCD_2021_Land_Cover_L48/wms" -layers = "NLCD_2021_Land_Cover_L48" -m.add_wms_layer(url, layers=layers, name="NLCD 2021") - -legend_dict = { - "11 Open Water": "466b9f", - "12 Perennial Ice/Snow": "d1def8", - "21 Developed, Open Space": "dec5c5", - "22 Developed, Low Intensity": "d99282", - "23 Developed, Medium Intensity": "eb0000", - "24 Developed High Intensity": "ab0000", - "31 Barren Land (Rock/Sand/Clay)": "b3ac9f", - "41 Deciduous Forest": "68ab5f", - "42 Evergreen Forest": "1c5f2c", - "43 Mixed Forest": "b5c58f", - "51 Dwarf Scrub": "af963c", - "52 Shrub/Scrub": "ccb879", - "71 Grassland/Herbaceous": "dfdfc2", - "72 Sedge/Herbaceous": "d1d182", - "73 Lichens": "a3cc51", - "74 Moss": "82ba9e", - "81 Pasture/Hay": "dcd939", - "82 Cultivated Crops": "ab6c28", - "90 Woody Wetlands": "b8d9eb", - "95 Emergent Herbaceous Wetlands": "6c9fb8", -} -m.add_legend( - title="NLCD Land Cover Type", - legend_dict=legend_dict, - bg_color="rgba(255, 255, 255, 0.5)", - position="bottom-left", -) -m -``` - -### Adding Video - -Videos can be added with geographic context by specifying corner coordinates. Videos must be listed in multiple formats to ensure compatibility across browsers. The coordinates array should define the video’s location on the map in the order: top-left, top-right, bottom-right, and bottom-left. This is demonstrated by adding drone footage to a satellite map view, enhancing the user experience with real-world visuals. - -```{code-cell} ipython3 -m = leafmap.Map(center=[-122.514426, 37.562984], zoom=17, bearing=-96, style="liberty") -m.add_basemap("Esri.WorldImagery") -urls = [ - "https://static-assets.mapbox.com/mapbox-gl-js/drone.mp4", - "https://static-assets.mapbox.com/mapbox-gl-js/drone.webm", -] -coordinates = [ - [-122.51596391201019, 37.56238816766053], - [-122.51467645168304, 37.56410183312965], - [-122.51309394836426, 37.563391708549425], - [-122.51423120498657, 37.56161849366671], -] -m.add_video(urls, coordinates) -m -``` - -```{code-cell} ipython3 -m = leafmap.Map(center=[-115, 25], zoom=4, style="liberty") -m.add_basemap("Esri.WorldImagery") -urls = [ - "https://data.opengeos.org/patricia_nasa.mp4", - "https://data.opengeos.org/patricia_nasa.webm", -] -coordinates = [ - [-130, 32], - [-100, 32], - [-100, 13], - [-130, 13], -] -m.add_video(urls, coordinates) -m -``` - -## PMTiles - -Leafmap supports visualizing [PMTiles](https://protomaps.com/docs/pmtiles/), which enables efficient storage and fast rendering of vector tiles directly in the browser. - -**Why PMTiles?** - -- Single-file archive format for vector tiles (no server-side processing needed) -- Efficient range request support for cloud-native applications -- Significantly faster than traditional tile servers for static datasets -- Can be hosted on any static file server or cloud storage -- Ideal for large-scale building footprints, field boundaries, and other vector data - -+++ - -### Building Footprint Data - -Visualize the [Google-Microsoft Open Buildings dataset](https://beta.source.coop/repositories/vida/google-microsoft-open-buildings/description), managed by VIDA, in PMTiles format. Fetch metadata to identify available layers, apply custom styles to the building footprints, and render them with semi-transparent colors for a clear visualization. - -```{code-cell} ipython3 -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']}") -``` - -```{code-cell} ipython3 -m = leafmap.Map(center=[0, 20], zoom=2) -m.add_basemap("Esri.WorldImagery", visible=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}, - }, - ], -} - -# style = leafmap.pmtiles_style(url) # Use default style - -m.add_pmtiles( - url, - style=style, - visible=True, - opacity=1.0, - tooltip=True, -) -m -``` - -### Fields of The World - -Visualize the Agricultural Field Boundary dataset - Fields of The World ([FTW](https://fieldsofthe.world)). The dataset is available on Source Cooperative at https://source.coop/repositories/kerner-lab/fields-of-the-world/description. - -```{code-cell} ipython3 -url = "https://data.source.coop/kerner-lab/fields-of-the-world/ftw-sources.pmtiles" -metadata = leafmap.pmtiles_metadata(url) -print(f"layer names: {metadata['layer_names']}") -print(f"bounds: {metadata['bounds']}") -``` - -```{code-cell} ipython3 -m = leafmap.Map() -# Define colors for each last digit (0-9) -style = { - "layers": [ - { - "id": "Field Polygon", - "source": "example_source", - "source-layer": "ftw-sources", - "type": "fill", - "paint": { - "fill-color": [ - "case", - ["==", ["%", ["to-number", ["get", "id"]], 10], 0], - "#FF5733", # Color for last digit 0 - ["==", ["%", ["to-number", ["get", "id"]], 10], 1], - "#33FF57", # Color for last digit 1 - ["==", ["%", ["to-number", ["get", "id"]], 10], 2], - "#3357FF", # Color for last digit 2 - ["==", ["%", ["to-number", ["get", "id"]], 10], 3], - "#FF33A1", # Color for last digit 3 - ["==", ["%", ["to-number", ["get", "id"]], 10], 4], - "#FF8C33", # Color for last digit 4 - ["==", ["%", ["to-number", ["get", "id"]], 10], 5], - "#33FFF6", # Color for last digit 5 - ["==", ["%", ["to-number", ["get", "id"]], 10], 6], - "#A833FF", # Color for last digit 6 - ["==", ["%", ["to-number", ["get", "id"]], 10], 7], - "#FF333D", # Color for last digit 7 - ["==", ["%", ["to-number", ["get", "id"]], 10], 8], - "#33FFBD", # Color for last digit 8 - ["==", ["%", ["to-number", ["get", "id"]], 10], 9], - "#FF9933", # Color for last digit 9 - "#FF0000", # Fallback color if no match - ], - "fill-opacity": 0.5, - }, - }, - { - "id": "Field Outline", - "source": "example_source", - "source-layer": "ftw-sources", - "type": "line", - "paint": {"line-color": "#ffffff", "line-width": 1, "line-opacity": 1}, - }, - ], -} - -m.add_basemap("Satellite") -m.add_pmtiles(url, style=style, name="FTW", zoom_to_layer=False) -m -``` - -```{code-cell} ipython3 -m = leafmap.Map() -style = { - "layers": [ - { - "id": "Field Polygon", - "source": "example_source", - "source-layer": "ftw-sources", - "type": "fill", - "paint": { - "fill-color": "#ffff00", - "fill-opacity": 0.2, - }, - }, - { - "id": "Field Outline", - "source": "example_source", - "source-layer": "ftw-sources", - "type": "line", - "paint": {"line-color": "#ff0000", "line-width": 1, "line-opacity": 1}, - }, - ], -} - -m.add_basemap("Satellite") -m.add_pmtiles(url, style=style, name="FTW", zoom_to_layer=False) -m -``` - -### 3D PMTiles - -Render global building data in 3D for a realistic, textured experience. Set building colors and extrusion heights to create visually compelling cityscapes. For example, apply color gradients and height scaling based on building attributes to differentiate buildings by their heights. - -```{code-cell} ipython3 -url = "https://data.source.coop/cholmes/overture/overture-buildings.pmtiles" -metadata = leafmap.pmtiles_metadata(url) -print(f"layer names: {metadata['layer_names']}") -print(f"bounds: {metadata['bounds']}") -``` - -```{code-cell} ipython3 -m = leafmap.Map( - center=[-74.0095, 40.7046], zoom=16, pitch=60, bearing=-17, style="positron" -) -m.add_basemap("OpenStreetMap.Mapnik") -m.add_basemap("Esri.WorldImagery", visible=False) - -style = { - "layers": [ - { - "id": "buildings", - "source": "example_source", - "source-layer": "buildings", - "type": "fill-extrusion", - "filter": [ - ">", - ["get", "height"], - 0, - ], # only show buildings with height info - "paint": { - "fill-extrusion-color": [ - "interpolate", - ["linear"], - ["get", "height"], - 0, - "lightgray", - 200, - "royalblue", - 400, - "lightblue", - ], - "fill-extrusion-height": ["*", ["get", "height"], 1], - }, - }, - ], -} - -m.add_pmtiles( - url, - style=style, - visible=True, - opacity=1.0, - tooltip=True, - template="Height: {{height}}
Country: {{country_iso}}", - fit_bounds=False, -) - -m -``` - -## H3 Hexagonal Grid - -[H3](https://h3geo.org) is a hexagonal grid system that provides a way to represent and analyze geospatial data in a hexagonal grid. It is a popular choice for visualizing and analyzing geospatial data, especially for large datasets. - -```{code-cell} ipython3 -url = "https://data.gishub.org/duckdb/h3_res4_geo.parquet" -gdf = leafmap.read_vector(url) -gdf -``` - -```{code-cell} ipython3 -m = leafmap.Map() -m.add_basemap("Esri.WorldImagery", before_id=m.first_symbol_layer_id, visible=False) -m.add_data( - gdf, - column="building_count", - scheme="JenksCaspall", - cmap="inferno", - outline_color="rgba(255, 255, 255, 0)", - name="H3 Hexagon", - before_id=m.first_symbol_layer_id, -) -m -``` - -```{code-cell} ipython3 -m = leafmap.Map(center=[7.062832, -4.144790], pitch=45.5, zoom=1.57) -m.add_basemap("Esri.WorldImagery", before_id=m.first_symbol_layer_id, visible=False) -m.add_data( - gdf, - column="building_count", - scheme="JenksCaspall", - cmap="inferno", - outline_color="rgba(255, 255, 255, 0)", - name="H3 Hexagon", - before_id=m.first_symbol_layer_id, - extrude=True, - fit_bounds=False, -) -m -``` - -## Summary and Best Practices - -Congratulations! You've learned how to create interactive geospatial visualizations using Leafmap with MapLibre GL JS. This workshop covered: - -- **Map Creation**: Setting up interactive maps with customizable styles and projections -- **Data Visualization**: Working with raster data (local files, COGs, STAC), vector data (GeoJSON, PMTiles), and 3D visualizations -- **Dynamic Tile Serving**: Using TiTiler for on-demand raster tile generation -- **Advanced Features**: Adding controls, legends, color bars, and custom HTML elements - -### Key Takeaways - -1. **Choose the Right Format**: - - - Use COGs for large raster datasets in cloud storage - - Use PMTiles for vector data (buildings, boundaries, points of interest) - - Use TiTiler when you need dynamic rendering with custom parameters - - Use STAC for organized collections of geospatial assets - -2. **Performance Optimization**: - - - Leverage cloud-native formats (COG, PMTiles) for better performance - - Use appropriate zoom levels and bounds to limit data loading - - Apply data rescaling and color mapping efficiently - - Consider using `before_id` parameter to control layer ordering - -3. **Visualization Best Practices**: - - - Add legends and color bars to make your maps interpretable - - Use appropriate color schemes for your data type (sequential, diverging, categorical) - - Leverage 3D visualizations (terrain, buildings) when appropriate - - Combine multiple basemaps and layers for comprehensive analysis - -4. **Interactive Features**: - - Add drawing controls for user interaction - - Enable tooltips to display attribute information - - Use layer controls to toggle between datasets - - Implement fullscreen and navigation controls for better UX - -### Next Steps - -To continue learning: - -- Explore the [Leafmap documentation](https://leafmap.org) for more examples -- Check out [MapLibre GL JS documentation](https://maplibre.org/maplibre-gl-js/docs) for advanced styling -- Visit [Source Cooperative](https://source.coop) for more open geospatial datasets -- Experiment with custom styling using MapLibre style specifications - -Happy mapping! From 97a0271729bc1c5658c258d8935e2d43adb626fc Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Sun, 16 Nov 2025 13:58:35 -0500 Subject: [PATCH 5/7] Set jupyter execute to false --- modules/02-interactive-viz/index.ipynb | 183 +++++++++++++------------ 1 file changed, 92 insertions(+), 91 deletions(-) diff --git a/modules/02-interactive-viz/index.ipynb b/modules/02-interactive-viz/index.ipynb index 4f940d1..6051e24 100644 --- a/modules/02-interactive-viz/index.ipynb +++ b/modules/02-interactive-viz/index.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "7f98bb07", + "id": "d0df9b99", "metadata": {}, "source": [ "# 👁️ 2 - Interactive visualization of raster and vector data\n", @@ -41,7 +41,7 @@ { "cell_type": "code", "execution_count": null, - "id": "a1b08dd0", + "id": "f03bddb9", "metadata": {}, "outputs": [], "source": [ @@ -50,7 +50,7 @@ }, { "cell_type": "markdown", - "id": "a75a541d", + "id": "daae7b2b", "metadata": {}, "source": [ "Once installed, import the `maplibregl` backend from the `leafmap` package:" @@ -59,7 +59,7 @@ { "cell_type": "code", "execution_count": null, - "id": "07c5e588", + "id": "b3a6e886", "metadata": {}, "outputs": [], "source": [ @@ -68,7 +68,7 @@ }, { "cell_type": "markdown", - "id": "2d3c6693", + "id": "d70593cd", "metadata": {}, "source": [ "## Creating Interactive Maps\n", @@ -81,7 +81,7 @@ { "cell_type": "code", "execution_count": null, - "id": "24d41f90", + "id": "440b9a82", "metadata": {}, "outputs": [], "source": [ @@ -91,7 +91,7 @@ }, { "cell_type": "markdown", - "id": "b5dbc8cc", + "id": "2afaffb4", "metadata": {}, "source": [ "### Customizing the Map's Center and Zoom Level\n", @@ -102,7 +102,7 @@ { "cell_type": "code", "execution_count": null, - "id": "d78613ed", + "id": "806c9129", "metadata": {}, "outputs": [], "source": [ @@ -112,7 +112,7 @@ }, { "cell_type": "markdown", - "id": "bcf88fe0", + "id": "82d03eb1", "metadata": {}, "source": [ "### Choosing a Basemap Style\n", @@ -123,7 +123,7 @@ { "cell_type": "code", "execution_count": null, - "id": "e0be7ed9", + "id": "1d79e0ab", "metadata": {}, "outputs": [], "source": [ @@ -133,7 +133,7 @@ }, { "cell_type": "markdown", - "id": "dd110f00", + "id": "36fb63af", "metadata": {}, "source": [ "[OpenFreeMap](https://openfreemap.org) provides a variety of basemap styles that you can use in your interactive maps. These styles include `liberty`, `bright`, and `positron`." @@ -142,7 +142,7 @@ { "cell_type": "code", "execution_count": null, - "id": "25ecbe2d", + "id": "1e51c725", "metadata": {}, "outputs": [], "source": [ @@ -152,7 +152,7 @@ }, { "cell_type": "markdown", - "id": "59ff994a", + "id": "e57b7d74", "metadata": {}, "source": [ "## Adding Map Controls\n", @@ -174,7 +174,7 @@ { "cell_type": "code", "execution_count": null, - "id": "cc8533d7", + "id": "0924ee41", "metadata": {}, "outputs": [], "source": [ @@ -185,7 +185,7 @@ }, { "cell_type": "markdown", - "id": "c5f280bb", + "id": "62f468bb", "metadata": {}, "source": [ "### Adding Fullscreen Control\n", @@ -196,7 +196,7 @@ { "cell_type": "code", "execution_count": null, - "id": "426cf8b9", + "id": "77aecbf1", "metadata": {}, "outputs": [], "source": [ @@ -207,7 +207,7 @@ }, { "cell_type": "markdown", - "id": "7e4a1a99", + "id": "edc84cf1", "metadata": {}, "source": [ "### Adding Navigation Control\n", @@ -218,7 +218,7 @@ { "cell_type": "code", "execution_count": null, - "id": "df177557", + "id": "7c182eb9", "metadata": {}, "outputs": [], "source": [ @@ -229,7 +229,7 @@ }, { "cell_type": "markdown", - "id": "c4eb425d", + "id": "6f41bd3f", "metadata": {}, "source": [ "### Adding Draw Control\n", @@ -240,7 +240,7 @@ { "cell_type": "code", "execution_count": null, - "id": "2b6535ae", + "id": "7ec58ca0", "metadata": {}, "outputs": [], "source": [ @@ -251,7 +251,7 @@ }, { "cell_type": "markdown", - "id": "246d50d5", + "id": "d4835c41", "metadata": {}, "source": [ "Two key methods for accessing drawn features:\n", @@ -263,7 +263,7 @@ { "cell_type": "code", "execution_count": null, - "id": "1c0af6a4", + "id": "a5522c70", "metadata": {}, "outputs": [], "source": [ @@ -273,7 +273,7 @@ { "cell_type": "code", "execution_count": null, - "id": "f0419d57", + "id": "b5a5a92d", "metadata": {}, "outputs": [], "source": [ @@ -282,7 +282,7 @@ }, { "cell_type": "markdown", - "id": "fed30cd5", + "id": "c35d95c2", "metadata": {}, "source": [ "## Adding Layers\n", @@ -297,7 +297,7 @@ { "cell_type": "code", "execution_count": null, - "id": "511d87e3", + "id": "521b23ac", "metadata": {}, "outputs": [], "source": [ @@ -309,7 +309,7 @@ { "cell_type": "code", "execution_count": null, - "id": "3dcd77ff", + "id": "9129f2e6", "metadata": {}, "outputs": [], "source": [ @@ -318,7 +318,7 @@ }, { "cell_type": "markdown", - "id": "fb33e356", + "id": "76ff6fc4", "metadata": {}, "source": [ "You can also add basemaps interactively, which provides flexibility for selecting the best background for your map content." @@ -327,7 +327,7 @@ { "cell_type": "code", "execution_count": null, - "id": "a7bf173b", + "id": "34888b09", "metadata": {}, "outputs": [], "source": [ @@ -338,7 +338,7 @@ { "cell_type": "code", "execution_count": null, - "id": "7c8c84e1", + "id": "1849e832", "metadata": {}, "outputs": [], "source": [ @@ -347,7 +347,7 @@ }, { "cell_type": "markdown", - "id": "361e776b", + "id": "458439f5", "metadata": {}, "source": [ "### Adding XYZ Tile Layer\n", @@ -358,7 +358,7 @@ { "cell_type": "code", "execution_count": null, - "id": "2901996a", + "id": "51ab69cf", "metadata": {}, "outputs": [], "source": [ @@ -370,7 +370,7 @@ }, { "cell_type": "markdown", - "id": "6d738aea", + "id": "334022aa", "metadata": {}, "source": [ "### Adding WMS Layer\n", @@ -381,7 +381,7 @@ { "cell_type": "code", "execution_count": null, - "id": "43ce78c6", + "id": "280951c0", "metadata": {}, "outputs": [], "source": [ @@ -396,7 +396,7 @@ { "cell_type": "code", "execution_count": null, - "id": "e5c3ce89", + "id": "4ba36fed", "metadata": {}, "outputs": [], "source": [ @@ -410,7 +410,7 @@ }, { "cell_type": "markdown", - "id": "e9869278", + "id": "32e3113e", "metadata": {}, "source": [ "## 3D Terrain\n", @@ -421,7 +421,7 @@ { "cell_type": "code", "execution_count": null, - "id": "7687ef8e", + "id": "fb11d81e", "metadata": {}, "outputs": [], "source": [ @@ -439,7 +439,7 @@ }, { "cell_type": "markdown", - "id": "9acdfe19", + "id": "bdbd1bc9", "metadata": {}, "source": [ "### 3D Buildings\n", @@ -450,7 +450,7 @@ { "cell_type": "code", "execution_count": null, - "id": "02de109f", + "id": "bcfe829b", "metadata": {}, "outputs": [], "source": [ @@ -467,7 +467,7 @@ }, { "cell_type": "markdown", - "id": "e22d8a9e", + "id": "2ce0651b", "metadata": {}, "source": [ "### 3D Indoor Mapping\n", @@ -478,7 +478,7 @@ { "cell_type": "code", "execution_count": null, - "id": "4876b90b", + "id": "633c72db", "metadata": {}, "outputs": [], "source": [ @@ -490,7 +490,7 @@ { "cell_type": "code", "execution_count": null, - "id": "0214d4f9", + "id": "27ff64d3", "metadata": {}, "outputs": [], "source": [ @@ -500,7 +500,7 @@ { "cell_type": "code", "execution_count": null, - "id": "96e76cd3", + "id": "67175b87", "metadata": {}, "outputs": [], "source": [ @@ -524,7 +524,7 @@ }, { "cell_type": "markdown", - "id": "62ea70f3", + "id": "6f996662", "metadata": {}, "source": [ "## Visualizing Vector Data\n", @@ -534,7 +534,7 @@ }, { "cell_type": "markdown", - "id": "543c3275", + "id": "57ae0d28", "metadata": {}, "source": [ "### Point Data" @@ -543,7 +543,7 @@ { "cell_type": "code", "execution_count": null, - "id": "035a8033", + "id": "035e28d0", "metadata": {}, "outputs": [], "source": [ @@ -558,7 +558,7 @@ }, { "cell_type": "markdown", - "id": "3600568e", + "id": "44e86868", "metadata": {}, "source": [ "### Line Data" @@ -567,7 +567,7 @@ { "cell_type": "code", "execution_count": null, - "id": "c3c6798d", + "id": "54be390c", "metadata": {}, "outputs": [], "source": [ @@ -580,7 +580,7 @@ }, { "cell_type": "markdown", - "id": "0df3860d", + "id": "274113cd", "metadata": {}, "source": [ "### Polygon Data" @@ -589,7 +589,7 @@ { "cell_type": "code", "execution_count": null, - "id": "446529a2", + "id": "32594ffa", "metadata": {}, "outputs": [], "source": [ @@ -611,7 +611,7 @@ }, { "cell_type": "markdown", - "id": "53ad27b0", + "id": "367b720c", "metadata": {}, "source": [ "## Visualizing Remote Sensing Data\n", @@ -628,7 +628,7 @@ { "cell_type": "code", "execution_count": null, - "id": "deb87e56", + "id": "4c04b17a", "metadata": {}, "outputs": [], "source": [ @@ -640,7 +640,7 @@ { "cell_type": "code", "execution_count": null, - "id": "93277577", + "id": "81b6d912", "metadata": {}, "outputs": [], "source": [ @@ -652,7 +652,7 @@ }, { "cell_type": "markdown", - "id": "5c0af561", + "id": "7502d03a", "metadata": {}, "source": [ "A Digital Elevation Model (DEM) is also downloaded and visualized with a terrain color scheme. Leafmap’s `layer_interact` method allows interactive adjustments." @@ -661,7 +661,7 @@ { "cell_type": "code", "execution_count": null, - "id": "461b4068", + "id": "c82bed4c", "metadata": {}, "outputs": [], "source": [ @@ -673,7 +673,7 @@ { "cell_type": "code", "execution_count": null, - "id": "e1ce5ca9", + "id": "889877a1", "metadata": {}, "outputs": [], "source": [ @@ -684,7 +684,7 @@ }, { "cell_type": "markdown", - "id": "f544b8c8", + "id": "d6cafc94", "metadata": {}, "source": [ "### Cloud Optimized GeoTIFF (COG)\n", @@ -703,7 +703,7 @@ { "cell_type": "code", "execution_count": null, - "id": "d1d1c629", + "id": "172fb58a", "metadata": {}, "outputs": [], "source": [ @@ -721,7 +721,7 @@ }, { "cell_type": "markdown", - "id": "06be2283", + "id": "a5bd7283", "metadata": {}, "source": [ "### STAC Layer\n", @@ -732,7 +732,7 @@ { "cell_type": "code", "execution_count": null, - "id": "d5fbe001", + "id": "a1af8b9d", "metadata": {}, "outputs": [], "source": [ @@ -745,7 +745,7 @@ }, { "cell_type": "markdown", - "id": "d016bfa6", + "id": "88e92f34", "metadata": {}, "source": [ "### Adding HTML\n", @@ -756,7 +756,7 @@ { "cell_type": "code", "execution_count": null, - "id": "2b45b8bc", + "id": "6fe03469", "metadata": {}, "outputs": [], "source": [ @@ -783,7 +783,7 @@ }, { "cell_type": "markdown", - "id": "6f7010e4", + "id": "78575920", "metadata": {}, "source": [ "## Adding Components to the Map\n", @@ -799,7 +799,7 @@ { "cell_type": "code", "execution_count": null, - "id": "b437777b", + "id": "01f31ccb", "metadata": {}, "outputs": [], "source": [ @@ -809,7 +809,7 @@ { "cell_type": "code", "execution_count": null, - "id": "0a9d1d62", + "id": "5f5860e6", "metadata": {}, "outputs": [], "source": [ @@ -832,7 +832,7 @@ }, { "cell_type": "markdown", - "id": "35d677bc", + "id": "a1a60d7c", "metadata": {}, "source": [ "Make the color bar background transparent to blend seamlessly with the map." @@ -841,7 +841,7 @@ { "cell_type": "code", "execution_count": null, - "id": "a8257314", + "id": "5795f792", "metadata": {}, "outputs": [], "source": [ @@ -867,7 +867,7 @@ }, { "cell_type": "markdown", - "id": "c0c9821f", + "id": "f9ae60e0", "metadata": {}, "source": [ "Make the color bar vertical for a different layout." @@ -876,7 +876,7 @@ { "cell_type": "code", "execution_count": null, - "id": "07af7fa9", + "id": "86a62e7e", "metadata": {}, "outputs": [], "source": [ @@ -904,7 +904,7 @@ }, { "cell_type": "markdown", - "id": "c6e4ca7d", + "id": "899d44a9", "metadata": {}, "source": [ "### Adding Legend\n", @@ -918,7 +918,7 @@ { "cell_type": "code", "execution_count": null, - "id": "c9f65a8e", + "id": "d8d22335", "metadata": {}, "outputs": [], "source": [ @@ -939,7 +939,7 @@ { "cell_type": "code", "execution_count": null, - "id": "667dd2dc", + "id": "6dc72c19", "metadata": {}, "outputs": [], "source": [ @@ -954,7 +954,7 @@ { "cell_type": "code", "execution_count": null, - "id": "c1987438", + "id": "6bfb5040", "metadata": {}, "outputs": [], "source": [ @@ -997,7 +997,7 @@ }, { "cell_type": "markdown", - "id": "6f1e1fc8", + "id": "3c3bd22e", "metadata": {}, "source": [ "### Adding Video\n", @@ -1008,7 +1008,7 @@ { "cell_type": "code", "execution_count": null, - "id": "46ed6adb", + "id": "2303e7f4", "metadata": {}, "outputs": [], "source": [ @@ -1031,7 +1031,7 @@ { "cell_type": "code", "execution_count": null, - "id": "e53089dc", + "id": "89b40e52", "metadata": {}, "outputs": [], "source": [ @@ -1053,7 +1053,7 @@ }, { "cell_type": "markdown", - "id": "91ef8883", + "id": "c6187353", "metadata": {}, "source": [ "## PMTiles\n", @@ -1071,7 +1071,7 @@ }, { "cell_type": "markdown", - "id": "f3709a88", + "id": "f642476e", "metadata": {}, "source": [ "### Building Footprint Data\n", @@ -1082,7 +1082,7 @@ { "cell_type": "code", "execution_count": null, - "id": "34778d70", + "id": "52553f29", "metadata": {}, "outputs": [], "source": [ @@ -1095,7 +1095,7 @@ { "cell_type": "code", "execution_count": null, - "id": "a4f203de", + "id": "7b0b3019", "metadata": {}, "outputs": [], "source": [ @@ -1136,7 +1136,7 @@ }, { "cell_type": "markdown", - "id": "bc9f2b70", + "id": "6ce3db51", "metadata": {}, "source": [ "### Fields of The World\n", @@ -1147,7 +1147,7 @@ { "cell_type": "code", "execution_count": null, - "id": "c09642b8", + "id": "27fe6fd2", "metadata": {}, "outputs": [], "source": [ @@ -1160,7 +1160,7 @@ { "cell_type": "code", "execution_count": null, - "id": "a1080be9", + "id": "e7e3ee3b", "metadata": {}, "outputs": [], "source": [ @@ -1219,7 +1219,7 @@ { "cell_type": "code", "execution_count": null, - "id": "558cf2ce", + "id": "66558979", "metadata": {}, "outputs": [], "source": [ @@ -1253,7 +1253,7 @@ }, { "cell_type": "markdown", - "id": "a86b32df", + "id": "2d20f49d", "metadata": {}, "source": [ "### 3D PMTiles\n", @@ -1264,7 +1264,7 @@ { "cell_type": "code", "execution_count": null, - "id": "778ff6c1", + "id": "e2472d0c", "metadata": {}, "outputs": [], "source": [ @@ -1277,7 +1277,7 @@ { "cell_type": "code", "execution_count": null, - "id": "ef645705", + "id": "e22ef570", "metadata": {}, "outputs": [], "source": [ @@ -1332,7 +1332,7 @@ }, { "cell_type": "markdown", - "id": "29b1a04b", + "id": "35b983d7", "metadata": {}, "source": [ "## H3 Hexagonal Grid\n", @@ -1343,7 +1343,7 @@ { "cell_type": "code", "execution_count": null, - "id": "f68d582e", + "id": "58848fc4", "metadata": {}, "outputs": [], "source": [ @@ -1355,7 +1355,7 @@ { "cell_type": "code", "execution_count": null, - "id": "0bc298f6", + "id": "04f541af", "metadata": {}, "outputs": [], "source": [ @@ -1376,7 +1376,7 @@ { "cell_type": "code", "execution_count": null, - "id": "bdb91626", + "id": "561b81ab", "metadata": {}, "outputs": [], "source": [ @@ -1398,7 +1398,7 @@ }, { "cell_type": "markdown", - "id": "d6def81e", + "id": "6e073dba", "metadata": {}, "source": [ "## Summary and Best Practices\n", @@ -1464,6 +1464,7 @@ "orcid": "0000-0001-5437-4073" } ], + "execute": false, "jupytext": { "default_lexer": "ipython3" } From 61804f88375a5999996f8b2b13b8fded1a183e7e Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Sun, 16 Nov 2025 14:02:40 -0500 Subject: [PATCH 6/7] Update file extension --- 02-schedule.md | 41 +++++++++++++++++++---------------------- myst.yml | 3 +-- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/02-schedule.md b/02-schedule.md index 94a3662..96ba2e1 100644 --- a/02-schedule.md +++ b/02-schedule.md @@ -1,25 +1,23 @@ # ⌚ Schedule -:::{attention} 🏗️ Under development +:::{attention} 🏗️ Under development :icon: false These materials are under development and subject to change all the way until AGU! ::: - ## 🌅 Morning (8:30 - 12:00) -| Time | Duration | Topic | Presenter(s) | -|-----------|-------------|-------------------------------------------------------------------------------------|-------------------| -| 8:30 AM | 10 minutes | [Meet the instructors](00-instructors.md) | All | -| 8:40 AM | 45 minutes | [](modules/01-working-in-jupyterhub/index.md) | Tasha | -| 9:25 AM | 10 minutes | **Break** | | -| 9:35 AM | 60 minutes | [](modules/02-interactive-viz/index.md) | Qiusheng | -| 10:35 AM | 10 minutes | **Break** | | -| 10:45 AM | 60 minutes | [](modules/03-integrating-ai/index.md) | Qiusheng | -| 11:45 AM | 15 minutes | **Q&A** | All | - +| Time | Duration | Topic | Presenter(s) | +| -------- | ---------- | --------------------------------------------- | ------------ | +| 8:30 AM | 10 minutes | [Meet the instructors](00-instructors.md) | All | +| 8:40 AM | 45 minutes | [](modules/01-working-in-jupyterhub/index.md) | Tasha | +| 9:25 AM | 10 minutes | **Break** | | +| 9:35 AM | 60 minutes | [](modules/02-interactive-viz/index.ipynb) | Qiusheng | +| 10:35 AM | 10 minutes | **Break** | | +| 10:45 AM | 60 minutes | [](modules/03-integrating-ai/index.md) | Qiusheng | +| 11:45 AM | 15 minutes | **Q&A** | All | ## 🍽️ Lunch! (12:00 - 1:30) @@ -29,15 +27,14 @@ AGU does not provide lunch. We may do a group order for food delivery. ::: - ## 🌇 Afternoon (1:30 - 5:00) -| Time | Duration | Topic | Presenter(s) | -|-----------|-------------|-----------------------------------------------------------------------------------|-------------------| -| 1:30 PM | 60 minutes | [](modules/04-data-in-the-cloud/index.md) | Max | -| 2:30 PM | 10 minutes | **Break** | | -| 2:40 PM | 60 minutes | [](modules/05-sharing-and-publishing/index.md) | Fernando & Matt | -| 3:40 PM | 10 minutes | **Break** | | -| 3:50 PM | 55 minutes | [](modules/06-geojupyter/index.md) | Fernando & Matt | -| 4:45 PM | 15 minutes | **Closing Q&A** | All | -| 5:00 PM | | **End of day** | | +| Time | Duration | Topic | Presenter(s) | +| ------- | ---------- | ---------------------------------------------- | --------------- | +| 1:30 PM | 60 minutes | [](modules/04-data-in-the-cloud/index.md) | Max | +| 2:30 PM | 10 minutes | **Break** | | +| 2:40 PM | 60 minutes | [](modules/05-sharing-and-publishing/index.md) | Fernando & Matt | +| 3:40 PM | 10 minutes | **Break** | | +| 3:50 PM | 55 minutes | [](modules/06-geojupyter/index.md) | Fernando & Matt | +| 4:45 PM | 15 minutes | **Closing Q&A** | All | +| 5:00 PM | | **End of day** | | diff --git a/myst.yml b/myst.yml index 56eb43a..4edebca 100644 --- a/myst.yml +++ b/myst.yml @@ -16,7 +16,7 @@ project: - pattern: "0*.md" - title: "Modules" children: - - pattern: "modules/0*/index.md" + - pattern: "modules/0*/index.*" - title: "Reference" children: - pattern: "reference/*.md" @@ -24,7 +24,6 @@ project: children: - pattern: "for-instructors/*.md" - site: template: "book-theme" actions: From d2beb22cadfa3d283c86dbcbe318d67b01170958 Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Wed, 19 Nov 2025 00:12:57 -0500 Subject: [PATCH 7/7] Add mapclassify and update colab link --- modules/02-interactive-viz/index.ipynb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/02-interactive-viz/index.ipynb b/modules/02-interactive-viz/index.ipynb index 6051e24..1529a6c 100644 --- a/modules/02-interactive-viz/index.ipynb +++ b/modules/02-interactive-viz/index.ipynb @@ -7,7 +7,7 @@ "source": [ "# 👁️ 2 - Interactive visualization of raster and vector data\n", "\n", - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/opengeos/leafmap/blob/master/docs/workshops/AGU_2025.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/geojupyter/workshop-open-source-geospatial/blob/main/modules/02-interactive-viz/index.ipynb)\n", "\n", "## Introduction\n", "\n", @@ -45,7 +45,7 @@ "metadata": {}, "outputs": [], "source": [ - "# %pip install -U leafmap" + "# %pip install -U leafmap mapclassify" ] }, { @@ -1467,6 +1467,9 @@ "execute": false, "jupytext": { "default_lexer": "ipython3" + }, + "language_info": { + "name": "python" } }, "nbformat": 4,