Creating 3D maps with MapLibre
The notebook demonstrates how to create 3D maps using the MapLibre Python package. The examples shown in this notebook are based on the MapLibre documentation. Credits to the original authors at eoda GmbH.
Uncomment the following line to install leafmap if needed.
# %pip install "leafmap[maplibre]"
import leafmap.maplibregl as leafmap
Create an interactive map by specifying map center [lon, lat], zoom level, pitch, and bearing.
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
.
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
.
m = leafmap.Map(style="background-lightgray")
m
Alternatively, you can provide a URL to a vector style.
style = "https://demotiles.maplibre.org/style.json"
m = leafmap.Map(style=style)
m
The control to add to the map. Can be one of the following: scale
, fullscreen
, geolocate
, navigation
.
m = leafmap.Map()
m.add_control("geolocate", position="top-left")
m
m = leafmap.Map()
m
m.add_basemap()
m = leafmap.Map()
m.add_basemap("OpenTopoMap")
m
m.add_basemap("Esri.WorldImagery")
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
m = leafmap.Map(center=[-100, 40], zoom=3)
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", opacity=0.8)
m
m = leafmap.Map()
url = (
"https://github.com/opengeos/datasets/releases/download/raster/Libya-2023-07-01.tif"
)
m.add_cog_layer(url, name="COG", attribution="Maxar", fit_bounds=True)
m
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)
m
url = "https://github.com/opengeos/datasets/releases/download/raster/srtm90.tif"
filepath = "srtm90.tif"
leafmap.download_file(url, filepath)
m = leafmap.Map()
m.add_raster(filepath, colormap="terrain", name="DEM")
m
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
m.layer_interact()
m = leafmap.Map(style="positron")
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
from maplibre.controls import Marker, MarkerOptions, Popup, PopupOptions
import pandas as pd
m = leafmap.Map(style="positron")
url = "https://github.com/visgl/deck.gl-data/raw/master/examples/line/airports.json"
data = leafmap.pandas_to_geojson(
url, "coordinates", properties=["type", "name", "abbrev"]
)
m.add_geojson(
data,
name="Airports",
layer_type="circle",
paint={
"circle-color": [
"match",
["get", "type"],
"mid",
"darkred",
"major",
"darkgreen",
"darkblue",
],
"circle_radius": 10,
"circle-opacity": 0.3,
},
)
def get_color(airport_type: str) -> str:
color = "darkblue"
if airport_type == "mid":
color = "darkred"
elif airport_type == "major":
color = "darkgreen"
return color
airports_data = pd.read_json(url)
popup_options = PopupOptions(close_button=False)
for _, r in airports_data.iterrows():
m.add_marker(
lng_lat=r["coordinates"],
options=MarkerOptions(color=get_color(r["type"])),
popup=Popup(
text=r["name"],
options=popup_options,
),
)
m
m = leafmap.Map(
center=(-87.61694, 41.86625), zoom=17, pitch=40, bearing=20, style="positron"
)
m.add_basemap("OpenStreetMap.Mapnik")
data = "https://maplibre.org/maplibre-gl-js/docs/assets/indoor-3d-map.geojson"
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
import leafmap.maplibregl as leafmap
from maplibre.basemaps import construct_basemap_style
from maplibre import Layer, LayerType, Map, MapOptions
from maplibre.sources import GeoJSONSource
bg_layer = Layer(
type=LayerType.BACKGROUND,
id="background",
source=None,
paint={"background-color": "darkblue", "background-opacity": 0.8},
)
countries_source = GeoJSONSource(
data="https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_110m_admin_0_countries.geojson"
)
lines_layer = Layer(
type=LayerType.LINE,
source="countries",
paint={"line-color": "white", "line-width": 1.5},
)
polygons_layer = Layer(
type=LayerType.FILL,
source="countries",
paint={"fill-color": "darkred", "fill-opacity": 0.8},
)
custom_basemap = construct_basemap_style(
layers=[bg_layer, polygons_layer, lines_layer],
sources={"countries": countries_source},
)
m = leafmap.Map(style=custom_basemap)
data = "https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson"
m.add_geojson(
data,
layer_type="circle",
name="earthquakes",
paint={"circle-color": "yellow", "circle-radius": 5},
)
m.add_popup("earthquakes", "mag")
m
import pandas as pd
import h3
RESOLUTION = 7
COLORS = (
"lightblue",
"turquoise",
"lightgreen",
"yellow",
"orange",
"darkred",
)
road_safety = pd.read_csv(
"https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/3d-heatmap/heatmap-data.csv"
).dropna()
def create_h3_grid(res=RESOLUTION) -> dict:
road_safety["h3"] = road_safety.apply(
lambda x: h3.geo_to_h3(x["lat"], x["lng"], resolution=res), axis=1
)
df = road_safety.groupby("h3").h3.agg("count").to_frame("count").reset_index()
df["hexagon"] = df.apply(
lambda x: [h3.h3_to_geo_boundary(x["h3"], geo_json=True)], axis=1
)
df["color"] = pd.cut(
df["count"],
bins=len(COLORS),
labels=COLORS,
)
return leafmap.pandas_to_geojson(
df, "hexagon", geometry_type="Polygon", properties=["count", "color"]
)
m = leafmap.Map(
center=(-1.415727, 52.232395),
zoom=7,
pitch=40,
bearing=-27,
)
try:
data = create_h3_grid()
m.add_geojson(
data,
layer_type="fill-extrusion",
paint={
"fill-extrusion-color": ["get", "color"],
"fill-extrusion-opacity": 0.7,
"fill-extrusion-height": ["*", 100, ["get", "count"]],
},
)
except:
pass
m
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
import requests
data = requests.get(
"https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_10m_airports.geojson"
).json()
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