Skip to content

Taiwan 2024

image

3D Mapping with Leafmap and MapLibre

  • Registration: https://shorturl.at/4J0HW
  • Notebook: https://leafmap.org/workshops/Taiwan_2024
  • Leafmap: https://leafmap.org

Introduction

This notebook is designed for workshop presented at the Center for GIS, RCHSS, Academia Sinica, Taiwan, on August 7, 2024. Part 1 of the workshop will introduce the Earth Engine Python API and the geemap Python package. Part 2 will focus on 3D mapping with Leafmap and MapLibre.

Prerequisites

  • A Google account to access Google Colab
  • A MapTiler API key to access MapTiler vector tiles

Agenda

The main topics to be covered in this workshop include:

  • Create interactive 3D maps
  • 3D terrain, 3D buildings, and 3D indoor mapping
  • Visualize local vector and raster datasets
  • Visualize geospatial data in the cloud (COG, STAC, PMTiles)
  • Add custom components to the map
  • Export 3D maps as HTML files for website hosting

Installation

Uncomment the following line to install the required Python packages.

1
# %pip install -U "leafmap[maplibre]" geemap

Import the maplibre mapping backend.

1
import leafmap.maplibregl as leafmap

Set up API Key

To run this notebook, you need to set up a MapTiler API key. You can get a free API key by signing up at https://cloud.maptiler.com/.

1
2
# import os
# os.environ["MAPTILER_KEY"] = "YOUR_API_KEY"

Create interactive maps

Create a simple map

Let's create a simple interactive map using Leafmap.

1
2
m = leafmap.Map()
m

You can customize the map by specifying map center [lon, lat], zoom level, pitch, and bearing.

1
2
m = leafmap.Map(center=[-100, 40], zoom=3, pitch=0, bearing=0)
m

To customize the basemap, you can specify the style parameter. It can be an URL or a string, such as dark-matter, positron, voyager, demotiles.

1
2
m = leafmap.Map(style="positron")
m

To create a map with a background color, use style="background-<COLOR>", such as background-lightgray and background-green.

1
2
m = leafmap.Map(style="background-lightgray")
m

Alternatively, you can provide a URL to a vector style.

1
2
3
style = "https://demotiles.maplibre.org/style.json"
m = leafmap.Map(style=style)
m

Add map controls

The control to add to the map. Can be one of the following: scale, fullscreen, geolocate, navigation.

1
2
3
m = leafmap.Map()
m.add_control("geolocate", position="top-left")
m

Add basemaps

You can add basemaps to the map using the add_basemap method.

1
2
3
m = leafmap.Map()
m.add_basemap("OpenTopoMap")
m
1
m.add_basemap("Esri.WorldImagery")
1
2
m = leafmap.Map()
m

To add basemaps interactively, use the add_basemap method without specifying the basemap parameter.

1
m.add_basemap()

Add XYZ tile layer

You can add XYZ tile layers to the map using the add_tile_layer method.

1
2
3
4
5
6
m = leafmap.Map()
url = "https://tile.openstreetmap.org/{z}/{x}/{y}.png"
m.add_tile_layer(
    url, name="OpenStreetMap", attribution="OpenStreetMap", opacity=1.0, visible=True
)
m

Add WMS layer

You can add WMS layers to the map using the add_wms_layer method.

1
2
3
4
5
m = leafmap.Map(center=[-74.5447, 40.6892], zoom=8, style="streets")
url = "https://img.nj.gov/imagerywms/Natural2015"
layers = "Natural2015"
m.add_wms_layer(url, layers=layers, before_id="aeroway_fill")
m
1
2
3
4
5
6
m = leafmap.Map(center=[-100.307965, 46.98692], zoom=13, pitch=45, style="3d-hybrid")
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_layer_control(bg_layers=True)
m.add_legend(builtin_legend="NWI", title="Wetland Type")
m

MapTiler styles

You can use any named style from MapTiler by setting the style parameter to the name of the style.

1
2
m = leafmap.Map(style="streets")
m
1
2
m = leafmap.Map(style="satellite")
m
1
2
m = leafmap.Map(style="hybrid")
m
1
2
m = leafmap.Map(style="topo")
m

3D mapping

3D terrain

MapTiler provides a variety of basemaps and styles that can be used to create 3D maps. You can use any styles from the MapTiler basemap gallery and prefix the style name with 3d-. For example, 3d-hybrid, 3d-satellite, or 3d-topo. To use the hillshade only, you can use the 3d-hillshade style.

1
2
3
m = leafmap.Map(style="3d-hybrid")
m.add_layer_control(bg_layers=True)
m
1
2
3
m = leafmap.Map(style="3d-satellite")
m.add_layer_control(bg_layers=True)
m
1
2
3
m = leafmap.Map(style="3d-topo", exaggeration=1.5, hillshade=False)
m.add_layer_control(bg_layers=True)
m
1
2
3
m = leafmap.Map(style="3d-ocean", exaggeration=1.5, hillshade=True)
m.add_layer_control(bg_layers=True)
m

3D buildings

You can add 3D buildings to the map using the add_3d_buildings method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
m = leafmap.Map(
    center=[-74.0066, 40.7135], zoom=16, pitch=45, bearing=-17, style="basic-v2"
)
MAPTILER_KEY = leafmap.get_api_key("MAPTILER_KEY")
m.add_basemap("Esri.WorldImagery", visible=False)
source = {
    "url": f"https://api.maptiler.com/tiles/v3/tiles.json?key={MAPTILER_KEY}",
    "type": "vector",
}

layer = {
    "id": "3d-buildings",
    "source": "openmaptiles",
    "source-layer": "building",
    "type": "fill-extrusion",
    "min-zoom": 15,
    "paint": {
        "fill-extrusion-color": [
            "interpolate",
            ["linear"],
            ["get", "render_height"],
            0,
            "lightgray",
            200,
            "royalblue",
            400,
            "lightblue",
        ],
        "fill-extrusion-height": [
            "interpolate",
            ["linear"],
            ["zoom"],
            15,
            0,
            16,
            ["get", "render_height"],
        ],
        "fill-extrusion-base": [
            "case",
            [">=", ["get", "zoom"], 16],
            ["get", "render_min_height"],
            0,
        ],
    },
}
m.add_source("openmaptiles", source)
m.add_layer(layer)
m.add_layer_control()
m
1
2
3
4
5
6
7
m = leafmap.Map(
    center=[-74.0066, 40.7135], zoom=16, pitch=45, bearing=-17, style="basic-v2"
)
m.add_basemap("Esri.WorldImagery", visible=False)
m.add_3d_buildings(min_zoom=15)
m.add_layer_control()
m

3D indoor mapping

Let's visualize indoor mapping data using the add_geojson method.

1
2
3
data = "https://maplibre.org/maplibre-gl-js/docs/assets/indoor-3d-map.geojson"
gdf = leafmap.geojson_to_gdf(data)
gdf.explore()
1
gdf.head()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
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.add_layer_control()
m

Visualize vector data

Leafmap provides a variety of methods to visualize vector data on the map.

Point data

1
import requests
1
2
3
4
url = (
    "https://github.com/opengeos/datasets/releases/download/world/world_cities.geojson"
)
geojson = requests.get(url).json()
1
2
3
4
m = leafmap.Map(style="streets")
m.add_geojson(geojson, name="cities")
m.add_popup("cities")
m
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
m = leafmap.Map(style="streets")
source = {"type": "geojson", "data": geojson}

layer = {
    "id": "cities",
    "type": "symbol",
    "source": "point",
    "layout": {
        "icon-image": "marker_15",
        "icon-size": 1,
    },
}
m.add_source("point", source)
m.add_layer(layer)
m.add_popup("cities")
m

Line data

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
m = leafmap.Map(center=[-122.486052, 37.830348], zoom=15, style="streets")

source = {
    "type": "geojson",
    "data": {
        "type": "Feature",
        "properties": {},
        "geometry": {
            "type": "LineString",
            "coordinates": [
                [-122.48369693756104, 37.83381888486939],
                [-122.48348236083984, 37.83317489144141],
                [-122.48339653015138, 37.83270036637107],
                [-122.48356819152832, 37.832056363179625],
                [-122.48404026031496, 37.83114119107971],
                [-122.48404026031496, 37.83049717427869],
                [-122.48348236083984, 37.829920943955045],
                [-122.48356819152832, 37.82954808664175],
                [-122.48507022857666, 37.82944639795659],
                [-122.48610019683838, 37.82880236636284],
                [-122.48695850372314, 37.82931081282506],
                [-122.48700141906738, 37.83080223556934],
                [-122.48751640319824, 37.83168351665737],
                [-122.48803138732912, 37.832158048267786],
                [-122.48888969421387, 37.83297152392784],
                [-122.48987674713133, 37.83263257682617],
                [-122.49043464660643, 37.832937629287755],
                [-122.49125003814696, 37.832429207817725],
                [-122.49163627624512, 37.832564787218985],
                [-122.49223709106445, 37.83337825839438],
                [-122.49378204345702, 37.83368330777276],
            ],
        },
    },
}

layer = {
    "id": "route",
    "type": "line",
    "source": "route",
    "layout": {"line-join": "round", "line-cap": "round"},
    "paint": {"line-color": "#888", "line-width": 8},
}
m.add_source("route", source)
m.add_layer(layer)
m

Polygon data

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
m = leafmap.Map(center=[-68.137343, 45.137451], zoom=5, style="streets")
geojson = {
    "type": "Feature",
    "geometry": {
        "type": "Polygon",
        "coordinates": [
            [
                [-67.13734351262877, 45.137451890638886],
                [-66.96466, 44.8097],
                [-68.03252, 44.3252],
                [-69.06, 43.98],
                [-70.11617, 43.68405],
                [-70.64573401557249, 43.090083319667144],
                [-70.75102474636725, 43.08003225358635],
                [-70.79761105007827, 43.21973948828747],
                [-70.98176001655037, 43.36789581966826],
                [-70.94416541205806, 43.46633942318431],
                [-71.08482, 45.3052400000002],
                [-70.6600225491012, 45.46022288673396],
                [-70.30495378282376, 45.914794623389355],
                [-70.00014034695016, 46.69317088478567],
                [-69.23708614772835, 47.44777598732787],
                [-68.90478084987546, 47.184794623394396],
                [-68.23430497910454, 47.35462921812177],
                [-67.79035274928509, 47.066248887716995],
                [-67.79141211614706, 45.702585354182816],
                [-67.13734351262877, 45.137451890638886],
            ]
        ],
    },
}
source = {"type": "geojson", "data": geojson}
m.add_source("maine", source)
layer = {
    "id": "maine",
    "type": "fill",
    "source": "maine",
    "layout": {},
    "paint": {"fill-color": "#088", "fill-opacity": 0.8},
}
m.add_layer(layer)
m
1
2
3
4
m = leafmap.Map(center=[-68.137343, 45.137451], zoom=5, style="streets")
paint = {"fill-color": "#088", "fill-opacity": 0.8}
m.add_geojson(geojson, layer_type="fill", paint=paint)
m

Multiple geometries

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
m = leafmap.Map(
    center=[-123.13, 49.254], zoom=11, style="dark-matter", pitch=45, bearing=0
)
url = "https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/geojson/vancouver-blocks.json"
paint_line = {
    "line-color": "white",
    "line-width": 2,
}
paint_fill = {
    "fill-extrusion-color": {
        "property": "valuePerSqm",
        "stops": [
            [0, "grey"],
            [1000, "yellow"],
            [5000, "orange"],
            [10000, "darkred"],
            [50000, "lightblue"],
        ],
    },
    "fill-extrusion-height": ["*", 10, ["sqrt", ["get", "valuePerSqm"]]],
    "fill-extrusion-opacity": 0.9,
}
m.add_geojson(url, layer_type="line", paint=paint_line, name="blocks-line")
m.add_geojson(url, layer_type="fill-extrusion", paint=paint_fill, name="blocks-fill")
m
1
m.layer_interact()

Marker cluster

Create a marker cluster layer.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
m = leafmap.Map(center=[-103.59179, 40.66995], zoom=3, style="streets")
data = "https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson"
source_args = {
    "cluster": True,
    "cluster_radius": 50,
    "cluster_min_points": 2,
    "cluster_max_zoom": 14,
    "cluster_properties": {
        "maxMag": ["max", ["get", "mag"]],
        "minMag": ["min", ["get", "mag"]],
    },
}

m.add_geojson(
    data,
    layer_type="circle",
    name="earthquake-circles",
    filter=["!", ["has", "point_count"]],
    paint={"circle-color": "darkblue"},
    source_args=source_args,
)

m.add_geojson(
    data,
    layer_type="circle",
    name="earthquake-clusters",
    filter=["has", "point_count"],
    paint={
        "circle-color": [
            "step",
            ["get", "point_count"],
            "#51bbd6",
            100,
            "#f1f075",
            750,
            "#f28cb1",
        ],
        "circle-radius": ["step", ["get", "point_count"], 20, 100, 30, 750, 40],
    },
    source_args=source_args,
)

m.add_geojson(
    data,
    layer_type="symbol",
    name="earthquake-labels",
    filter=["has", "point_count"],
    layout={
        "text-field": ["get", "point_count_abbreviated"],
        "text-size": 12,
    },
    source_args=source_args,
)
m

Local vector data

You can load local vector data interactively using the open_geojson method.

1
2
m = leafmap.Map(center=[-100, 40], zoom=3)
m
1
2
3
url = "https://github.com/opengeos/datasets/releases/download/us/us_states.geojson"
filepath = "data/us_states.geojson"
leafmap.download_file(url, filepath, quiet=True)
1
m.open_geojson()

Live feature update

Animate a point along a route

1
import time
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
m = leafmap.Map(center=[-100, 40], zoom=3, style="streets")
url = "https://github.com/opengeos/datasets/releases/download/us/arc_with_bearings.geojson"
geojson = requests.get(url).json()
bearings = geojson["features"][0]["properties"]["bearings"]
coordinates = geojson["features"][0]["geometry"]["coordinates"][:-1]
m.add_geojson(geojson, name="route")

origin = [-122.414, 37.776]
destination = [-77.032, 38.913]

point = {
    "type": "FeatureCollection",
    "features": [
        {
            "type": "Feature",
            "properties": {},
            "geometry": {"type": "Point", "coordinates": origin},
        }
    ],
}
source = {"type": "geojson", "data": point}
m.add_source("point", source)
layer = {
    "id": "point",
    "source": "point",
    "type": "symbol",
    "layout": {
        "icon-image": "airport_15",
        "icon-rotate": ["get", "bearing"],
        "icon-rotation-alignment": "map",
        "icon-overlap": "always",
        "icon-ignore-placement": True,
    },
}
m.add_layer(layer)
m
1
2
3
4
5
for index, coordinate in enumerate(coordinates):
    point["features"][0]["geometry"]["coordinates"] = coordinate
    point["features"][0]["properties"]["bearing"] = bearings[index]
    m.set_data("point", point)
    time.sleep(0.05)

Update a feature in realtime

1
2
m = leafmap.Map(center=[-122.019807, 45.632433], zoom=14, pitch=60, style="3d-terrain")
m
1
import geopandas as gpd
1
2
3
4
url = "https://maplibre.org/maplibre-gl-js/docs/assets/hike.geojson"
gdf = gpd.read_file(url)
coordinates = list(gdf.geometry[0].coords)
print(coordinates[:5])
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
source = {
    "type": "geojson",
    "data": {
        "type": "Feature",
        "geometry": {"type": "LineString", "coordinates": [coordinates[0]]},
    },
}
m.add_source("trace", source)
layer = {
    "id": "trace",
    "type": "line",
    "source": "trace",
    "paint": {"line-color": "yellow", "line-opacity": 0.75, "line-width": 5},
}
m.add_layer(layer)
m.jump_to({"center": coordinates[0], "zoom": 14})
m.set_pitch(30)
1
2
3
4
5
for coord in coordinates:
    time.sleep(0.005)
    source["data"]["geometry"]["coordinates"].append(coord)
    m.set_data("trace", source["data"])
    m.pan_to(coord)

Visualize raster data

Local raster data

You can load local raster data using the add_raster method.

1
2
3
url = "https://github.com/opengeos/datasets/releases/download/raster/landsat.tif"
filepath = "landsat.tif"
leafmap.download_file(url, filepath)
1
2
3
4
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
1
m.layer_interact()
1
2
3
url = "https://github.com/opengeos/datasets/releases/download/raster/srtm90.tif"
filepath = "srtm90.tif"
leafmap.download_file(url, filepath)
1
2
3
m = leafmap.Map(style="satellite")
m.add_raster(filepath, colormap="terrain", name="DEM")
m
1
m.layer_interact()

Cloud Optimized GeoTIFF (COG)

You can load Cloud Optimized GeoTIFF (COG) data using the add_cog_layer method.

1
2
3
4
5
6
7
8
m = leafmap.Map()
m.add_basemap("Esri.WorldImagery")
url = (
    "https://github.com/opengeos/datasets/releases/download/raster/Libya-2023-09-13.tif"
)
m.add_cog_layer(url, name="COG", attribution="Maxar", fit_bounds=True, nodata=0)
m.add_layer_control()
m
1
m.layer_interact()

STAC layer

You can load SpatioTemporal Asset Catalog (STAC) data using the add_stac_layer method.

1
2
3
4
m = leafmap.Map()
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=["B4", "B3", "B2"], name="SPOT", vmin=0, vmax=150, nodata=0)
m

PMTiles

Leafmap supports the PMTiles format for fast and efficient rendering of vector tiles.

Overture data

You can also visualize Overture data. Inspired by overture-maps.

1
2
3
4
url = "https://storage.googleapis.com/ahp-research/overture/pmtiles/overture.pmtiles"
metadata = leafmap.pmtiles_metadata(url)
print(f"layer names: {metadata['layer_names']}")
print(f"bounds: {metadata['bounds']}")
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
m = leafmap.Map(height="800px")
m.add_basemap("Esri.WorldImagery")

style = {
    "version": 8,
    "sources": {
        "example_source": {
            "type": "vector",
            "url": "pmtiles://" + url,
            "attribution": "PMTiles",
        }
    },
    "layers": [
        # {
        #     "id": "admins",
        #     "source": "example_source",
        #     "source-layer": "admins",
        #     "type": "fill",
        #     "paint": {"fill-color": "#BDD3C7", "fill-opacity": 0.1},
        # },
        {
            "id": "buildings",
            "source": "example_source",
            "source-layer": "buildings",
            "type": "fill",
            "paint": {"fill-color": "#FFFFB3", "fill-opacity": 0.5},
        },
        {
            "id": "places",
            "source": "example_source",
            "source-layer": "places",
            "type": "fill",
            "paint": {"fill-color": "#BEBADA", "fill-opacity": 0.5},
        },
        {
            "id": "roads",
            "source": "example_source",
            "source-layer": "roads",
            "type": "line",
            "paint": {"line-color": "#FB8072"},
        },
    ],
}

# style = leafmap.pmtiles_style(url)  # Use default style

m.add_pmtiles(
    url,
    style=style,
    visible=True,
    opacity=1.0,
    tooltip=True,
)
m
1
m.layer_interact()

Source Cooperative

Let's visualize the Google-Microsoft Open Buildings - combined by VIDA.

1
2
3
4
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']}")
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
m = leafmap.Map(center=[0, 20], zoom=2, height="800px")
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
1
m.layer_interact()

3D PMTiles

Visualize the global building data in 3D.

1
2
3
4
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']}")
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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}}<br>Country: {{country_iso}}",
    fit_bounds=False,
)
m.add_layer_control()
m

Add custom components

You can add custom components to the map, including images, videos, text, color bar, and legend.

Add image

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
m = leafmap.Map(center=[0.349419, -1.80921], zoom=3, style="streets")
image = "https://upload.wikimedia.org/wikipedia/commons/7/7c/201408_cat.png"
source = {
    "type": "geojson",
    "data": {
        "type": "FeatureCollection",
        "features": [
            {"type": "Feature", "geometry": {"type": "Point", "coordinates": [0, 0]}}
        ],
    },
}

layer = {
    "id": "points",
    "type": "symbol",
    "source": "point",
    "layout": {
        "icon-image": "cat",
        "icon-size": 0.25,
        "text-field": "I love kitty!",
        "text-font": ["Open Sans Regular"],
        "text-offset": [0, 3],
        "text-anchor": "top",
    },
}
m.add_image("cat", image)
m.add_source("point", source)
m.add_layer(layer)
m

Add text

1
2
3
4
5
6
m = leafmap.Map(center=[-100, 40], zoom=3, style="streets")
text = "Hello World"
m.add_text(text, fontsize=20, position="bottom-right")
text2 = "Awesome Text!"
m.add_text(text2, fontsize=25, bg_color="rgba(255, 255, 255, 0.8)", position="top-left")
m

Add GIF

1
2
3
4
5
6
7
8
m = leafmap.Map(center=[-100, 40], zoom=3, style="positron")
image = "https://i.imgur.com/KeiAsTv.gif"
m.add_image(image=image, width=250, height=250, position="bottom-right")
text = "I love sloth!🦥"
m.add_text(text, fontsize=35, padding="20px")
image2 = "https://i.imgur.com/kZC2tpr.gif"
m.add_image(image=image2, bg_color="transparent", position="bottom-left")
m

Add HTML

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
m = leafmap.Map(center=[-100, 40], zoom=3, style="positron")
html = """
<html>
<style>
body {
  font-size: 20px;
}
</style>
<body>

<span style='font-size:100px;'>&#128640;</span>
<p>I will display &#128641;</p>
<p>I will display &#128642;</p>

</body>
</html>
"""
m.add_html(html, bg_color="transparent")
m

Add colorbar

1
dem = "https://github.com/opengeos/datasets/releases/download/raster/srtm90.tif"
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
m = leafmap.Map(style="streets")
m.add_cog_layer(
    dem,
    name="DEM",
    colormap_name="terrain",
    rescale="0, 4000",
    fit_bounds=True,
    nodata=0,
)
m.add_colorbar(
    cmap="terrain", vmin=0, vmax=4000, label="Elevation (m)", position="bottom-right"
)
m
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
m = leafmap.Map(style="streets")
m.add_cog_layer(
    dem,
    name="DEM",
    colormap_name="terrain",
    rescale="0, 4000",
    nodata=0,
    fit_bounds=True,
)
m.add_colorbar(
    cmap="terrain",
    vmin=0,
    vmax=4000,
    label="Elevation (m)",
    position="bottom-right",
    transparent=True,
)
m
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
m = leafmap.Map(style="streets")
m.add_cog_layer(
    dem,
    name="DEM",
    colormap_name="terrain",
    rescale="0, 4000",
    nodata=0,
    fit_bounds=True,
)
m.add_colorbar(
    cmap="terrain",
    vmin=0,
    vmax=4000,
    label="Elevation (m)",
    position="bottom-right",
    width=0.2,
    height=3,
    orientation="vertical",
)
m

Add legend

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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
1
2
3
4
5
6
7
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_layer_control()
m.add_legend(builtin_legend="NWI", title="Wetland Type")
m
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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

Add video

The urls value is an array. For each URL in the array, a video element source will be created. To support the video across browsers, supply URLs in multiple formats. The coordinates array contains [longitude, latitude] pairs for the video corners listed in clockwise order: top left, top right, bottom right, bottom left.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
m = leafmap.Map(
    center=[-122.514426, 37.562984], zoom=17, bearing=-96, style="satellite"
)
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.add_layer_control()
m
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
m = leafmap.Map(center=[-115, 25], zoom=4, style="satellite")
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.add_layer_control()
m

Deck.GL layers

Deck.GL layers can be added to the map using the add_deck_layer method.

Single Deck.GL layer

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
m = leafmap.Map(
    style="positron",
    center=(-122.4, 37.74),
    zoom=12,
    pitch=40,
)
deck_grid_layer = {
    "@@type": "GridLayer",
    "id": "GridLayer",
    "data": "https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/sf-bike-parking.json",
    "extruded": True,
    "getPosition": "@@=COORDINATES",
    "getColorWeight": "@@=SPACES",
    "getElevationWeight": "@@=SPACES",
    "elevationScale": 4,
    "cellSize": 200,
    "pickable": True,
}

m.add_deck_layers([deck_grid_layer], tooltip="Number of points: {{ count }}")
m

Multiple Deck.GL layers

1
import requests
1
2
3
data = requests.get(
    "https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_10m_airports.geojson"
).json()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
m = leafmap.Map(
    style="positron",
    center=(0.45, 51.47),
    zoom=4,
    pitch=30,
)
deck_geojson_layer = {
    "@@type": "GeoJsonLayer",
    "id": "airports",
    "data": data,
    "filled": True,
    "pointRadiusMinPixels": 2,
    "pointRadiusScale": 2000,
    "getPointRadius": "@@=11 - properties.scalerank",
    "getFillColor": [200, 0, 80, 180],
    "autoHighlight": True,
    "pickable": True,
}

deck_arc_layer = {
    "@@type": "ArcLayer",
    "id": "arcs",
    "data": [
        feature
        for feature in data["features"]
        if feature["properties"]["scalerank"] < 4
    ],
    "getSourcePosition": [-0.4531566, 51.4709959],  # London
    "getTargetPosition": "@@=geometry.coordinates",
    "getSourceColor": [0, 128, 200],
    "getTargetColor": [200, 0, 80],
    "getWidth": 2,
    "pickable": True,
}

m.add_deck_layers(
    [deck_geojson_layer, deck_arc_layer],
    tooltip={
        "airports": "{{ &properties.name }}",
        "arcs": "gps_code: {{ properties.gps_code }}",
    },
)
m

Google Earth Engine

You can use the Earth Engine Python API to load and visualize Earth Engine data.

1
2
3
4
5
6
7
m = leafmap.Map(
    center=[-120.4482, 38.0399], zoom=13, pitch=60, bearing=30, style="3d-terrain"
)
m.add_ee_layer(asset_id="ESA/WorldCover/v200", opacity=0.5)
m.add_legend(builtin_legend="ESA_WorldCover", title="ESA Landcover")
m.add_layer_control()
m
1
m.layer_interact()

We can also overlay other data layers on top of Earth Engine data layers.

1
2
3
4
5
6
7
m = leafmap.Map(
    center=[-74.012998, 40.70414], zoom=15.6, pitch=60, bearing=30, style="3d-terrain"
)
m.add_ee_layer(asset_id="ESA/WorldCover/v200", opacity=0.5)
m.add_3d_buildings()
m.add_legend(builtin_legend="ESA_WorldCover", title="ESA Landcover")
m

If you have an Earth Engine, you can uncomment the first two code blocks to add any Earth Engine datasets.

1
2
# import ee
# ee.Initialize(project="YOUR-PROJECT-ID")
1
2
3
4
5
6
7
# m = leafmap.Map(center=[-120.4482, 38.03994], zoom=13, pitch=60, bearing=30, style="3d-terrain")
# dataset = ee.ImageCollection("ESA/WorldCover/v200").first()
# vis_params = {"bands": ["Map"]}
# m.add_ee_layer(dataset, vis_params, name="ESA Worldcover", opacity=0.5)
# m.add_legend(builtin_legend="ESA_WorldCover", title="ESA Landcover")
# m.add_layer_control()
# m

To HTML

To export the map as an HTML file, use the to_html method. To avoid exposing your private API key, you should create a public API key and restrict it to your website domain.

1
2
3
# import os
# os.environ["MAPTILER_KEY"] = "YOUR_PRIVATE_API_KEY"
# os.environ["MAPTILER_KEY_PUBLIC"] = "YOUR_PUBLIC_API_KEY"
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
m = leafmap.Map(
    center=[-122.19861, 46.21168], zoom=13, pitch=60, bearing=150, style="3d-terrain"
)
m.add_layer_control(bg_layers=True)
m.to_html(
    "terrain.html",
    title="Awesome 3D Map",
    width="100%",
    height="100%",
    replace_key=True,
)
m
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
m = leafmap.Map(
    center=[-74.0066, 40.7135], zoom=16, pitch=45, bearing=-17, style="basic-v2"
)
m.add_basemap("Esri.WorldImagery", visible=False)
m.add_3d_buildings(min_zoom=15)
m.add_layer_control()
m.to_html(
    "buildings.html",
    title="Awesome 3D Map",
    width="100%",
    height="100%",
    replace_key=True,
)
m