Quickstart#
Elevate your Folium maps with professional-grade interactive plugins. foliplus provides advanced Layer Management, Dynamic H3 Heatmaps, Spatial Measurement tools, and unified search capabilities in a single, easy-to-use toolkit.
What you will learn#
Building a multi-theme base map.
Preparing spatially-valid datasets using GeoPandas.
Integrating the full suite of foliplus plugins.
Exporting interactive maps for production use.
1. Installation#
Install the core package via pip:
$ pip install -U foliplus
For the spatial analysis used in this guide, we recommend:
$ pip install -U my-data-toolkit geopandas
Note: Please restart your notebook kernel after installing new packages.
2. Data Preparation & Base Map#
To fully demonstrate the power of foliplus, we’ll prepare a multi-layered urban dataset:
Administrative Context: Loading district boundaries (Polygons) to provide spatial grounding.
Point Density: Generating a synthetic grid of facility points with numeric
scoreweights.Symbolic Landmarks: Identifying district centers with custom icons to showcase how
LayerControlautomatically identifies geometry types.Commuting Routes: Creating a simulated line network using
to_linefrom my-data-toolkit to demonstrate Line geometry support.Base Themes: Setting up both Light and Dark modes for different visualization needs.
import dtoolkit.geoaccessor # noqa: F401
import folium
import geopandas as gpd
import numpy as np
import pandas as pd
# ---------------------------------------------------------------------------
# 1. Load & Refine Administrative Boundaries
# ---------------------------------------------------------------------------
# Load the Shanghai district boundaries
boundary = gpd.read_file("data/gadm41_CHN_3_Shanghai.geojson")
# Calculate District Centers (Layer 1)
# Re-project to EPSG:3857 for accurate geometry centers, then back to EPSG:4326 for Folium
districts = boundary.copy()
districts["geometry"] = boundary.to_crs(3857).centroid.to_crs(4326)
# Inject simulated demographic data for visualization
np.random.seed(42)
districts["population_density"] = np.random.randint(5_000, 30_000, len(districts))
# ---------------------------------------------------------------------------
# 2. Vectorized Point Sampling (Layer 2)
# ---------------------------------------------------------------------------
bounds = boundary.total_bounds
facilities = (
# Generate a dense candidate pool across the city
pd.DataFrame(
{
"x": np.random.uniform(bounds[0], bounds[2], 5_000),
"y": np.random.uniform(bounds[1], bounds[3], 5_000),
"score": np.random.randint(1, 5_000, 5_000),
}
)
.from_xy("x", "y", crs=4326)
# Perform spatial join to keep only points inside the land area
.sjoin(boundary, how="inner")
.groupby("NAME_3")
.sample(n=500, random_state=42, replace=True)
.reset_index(drop=True)
)
# ---------------------------------------------------------------------------
# 3. Initialize Base Map
# ---------------------------------------------------------------------------
center = districts.geocentroid()
m = folium.Map(location=[center.y, center.x], zoom_start=10, tiles=None)
# Add professional base themes
# Canvas-style base maps from Esri — plain gray canvases with no labels,
# which keeps the data layers (markers, polygons, routes) visually dominant.
# The bare URL is used because `World_Dark_Gray_Base` has no xyzservices entry.
ESRI_ATTR = "Tiles © Esri — Esri, DeLorme, NAVTEQ"
folium.TileLayer(
"https://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}",
name="Light Canvas",
attr=ESRI_ATTR,
max_zoom=16,
).add_to(m)
folium.TileLayer(
"https://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Dark_Gray_Base/MapServer/tile/{z}/{y}/{x}",
name="Dark Canvas",
attr=ESRI_ATTR,
max_zoom=16,
show=False,
).add_to(m)
# OpenStreetMap as the labeled fallback, listed last
folium.TileLayer("OpenStreetMap", name="OpenStreetMap", show=False).add_to(m)
# ---------------------------------------------------------------------------
# 4. Layer Orchestration
# ---------------------------------------------------------------------------
# Step 1: Add District Landmarks (Top markers)
districts.explore(
m=m,
name="District Landmarks",
marker_type="marker",
marker_kwds={"icon": folium.Icon(color="cadetblue", icon="map-pin", prefix="fa")},
tooltip="NAME_3",
popup=["NAME_3", "population_density"],
legend=False,
)
# Step 2: Add Facility Points (Heatmap source)
facilities.explore(
m=m,
name="Facility Points",
column="score",
cmap="plasma",
marker_type="circle_marker",
style_kwds={"radius": 3, "fillOpacity": 0.8, "stroke": False},
legend=True,
show=False,
)
# Step 3: Add Administrative Polygons as the base overlay
boundary.explore(
m=m,
name="Municipal Boundaries",
color="gray",
style_kwds={"fillOpacity": 0.05, "weight": 1.5, "dashArray": "5, 5"},
tooltip="NAME_3",
popup=True,
legend=False,
)
# ---------------------------------------------------------------------------
# Step 5: Add Commuting Routes (Line layer via to_line)
# ---------------------------------------------------------------------------
# Create a simulated route network connecting district centers
# to_line creates LineString geometries from (x1,y1) → (x2,y2) columns
(
pd.DataFrame(
{
"x1": districts.geometry.x[::2].values,
"y1": districts.geometry.y[::2].values,
"x2": districts.geometry.x[1::2].values,
"y2": districts.geometry.y[1::2].values,
"route_name": [f"Route {i}" for i in range(len(districts.geometry.x[::2]))],
}
)
.to_line("x1", "y1", "x2", "y2")
.explore(
m=m,
name="Commuting Routes",
color="#e74c3c",
style_kwds={"weight": 6, "opacity": 0.9, "dashArray": "10 10"},
highlight_kwds={"weight": 8},
tooltip="route_name",
legend=False,
show=False,
)
)
print("🌍 Map is ready")
🌍 Map is ready
3. Enable foliplus Plugins#
Now, we activate the professional toolkit. Observe how the plugins interact with our data:
ExportControl: Enables high-resolution map exports with customizable DPI scaling and size limits. Perfect for presentations or publications.HeatmapControl: Aggregates facility points into H3 hexagons. We’ve configured it to use thesumof thescorefield for a weighted density analysis.LayerControl: Allows real-time reordering of layers and quick adjustments to transparency or base themes.MeasureControl: Measures distances, areas, circles, and geocoded markers, then edit them by dragging nodes.SearchControl: Provides a unified interface for coordinate jumping and keyword search (e.g., search for “Shanghai”).
from foliplus import (
ExportControl,
FullscreenControl,
HeatmapControl,
LayerControl,
LocateControl,
MeasureControl,
ScaleControl,
SearchControl,
)
# Add all plugins with default settings to keep it simple but powerful
SearchControl().add_to(m)
LayerControl().add_to(m)
HeatmapControl(agg="sum").add_to(m)
ScaleControl().add_to(m)
MeasureControl().add_to(m)
ExportControl().add_to(m)
FullscreenControl(hide_self=False, hide_others=False).add_to(m)
LocateControl().add_to(m)
print("📦 foliplus's plugins are active")
📦 foliplus's plugins are active
4. Display Map#
Run the next cell to render the interactive map in Notebook.
If you want to share the result, use the optional HTML export section below.
m
If you want to view the map in a browser or host it as a static file, export it to HTML.
file = "map.html"
m.save(file)
print(f"📃 HTML is saved: {file}")
📃 HTML is saved: map.html
5. Troubleshooting & Tips#
Map Blank or Not Rendering: Ensure the notebook is trusted and that the map display cell (Cell 7) was the last one executed.
Dependency Issues: If
dtoolkitorgeopandasare missing, install them via the suggested command in Section 1 and restart the kernel.Custom Localization: While plugins auto-detect your browser language, you can force a locale globally:
LayerControl(locale="en").
6. Building from Source#
foliplus bundles its controls as compiled JS/CSS in foliplus/dist/, which is not committed to the repo. Always build with make dist — it compiles the bundles and packages them, and it asserts every expected artifact is present before uv build runs.
Do not run a bare python -m build (or uv build alone) in a development checkout. Both succeed silently when foliplus/dist/ is absent, producing a wheel that installs and imports fine but renders maps with no controls at all. If an artifact is genuinely missing, _build_shared_header() raises MissingAssetsError at render time rather than emitting an empty bundle.
$ make env # bootstrap .venv + install deps
$ make dist # build-js + build-python (the only correct release path)
$ pip install dist/*.whl
7. Next Steps#
API Exploration: Refer to the API Reference for a deep dive into available properties.
Real-World Integration: Swap the simulated data for your own local datasets to start generating professional spatial insights immediately.
Happy Mapping! You are now ready to build scalable, interactive maps with foliplus.