In this Folium tutorial, we build a complete set of interactive maps that run in Colab or any local Python setup. We explore multiple basemap styles, design rich markers with HTML popups, and visualize spatial density using heatmaps. We also create region-level choropleth maps from GeoJSON, scale to thousands of points using marker clustering, and animate time-based movement with a timestamped layer. Finally, we combine real-world USGS earthquake data with layered magnitude buckets, density heatmaps, legends, and fullscreen controls to produce a practical, dashboard-like global monitor.
from folium import plugins
from folium.plugins import HeatMap, MarkerCluster, TimestampedGeoJson, MiniMap, Draw, Fullscreen
import pandas as pd
import numpy as np
import json
import requests
from datetime import datetime, timedelta
import branca.colormap as cm
print(f”Folium version: {folium.__version__}”)
print(“All imports successful!\n”)
We import all required libraries, such as Folium, Pandas, NumPy, Requests, and Folium plugins to prepare our geospatial environment. We initialize the mapping workflow by confirming the Folium version and ensuring that all dependencies load successfully. This setup establishes the technical foundation for building interactive maps, processing data, and integrating external geospatial sources.
“””Create a map with multiple tile layers”””
m = folium.Map(
location=[40.7128, -74.0060],
zoom_start=12,
tiles=”OpenStreetMap”
)
folium.TileLayer(‘cartodbpositron’, name=”CartoDB Positron”).add_to(m)
folium.TileLayer(‘cartodbdark_matter’, name=”CartoDB Dark Matter”).add_to(m)
folium.TileLayer(
tiles=”https://tiles.stadiamaps.com/tiles/stamen_terrain/{z}/{x}/{y}.png”,
attr=”Map tiles by Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under ODbL”,
name=”Terrain”
).add_to(m)
folium.TileLayer(
tiles=”https://tiles.stadiamaps.com/tiles/stamen_toner/{z}/{x}/{y}.png”,
attr=”Map tiles by Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under ODbL”,
name=”Toner”
).add_to(m)
folium.TileLayer(
tiles=”https://tiles.stadiamaps.com/tiles/stamen_watercolor/{z}/{x}/{y}.jpg”,
attr=”Map tiles by Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under ODbL”,
name=”Watercolor”
).add_to(m)
folium.LayerControl().add_to(m)
return m
We create a multi-layer base map and configure multiple tile providers to enable different visual styles. We add terrain, dark mode, toner, and watercolor layers so we can switch perspectives based on the analytical requirements. By including a layer control panel, we can dynamically toggle map styles and explore spatial data more effectively.
“””Create map with custom markers and HTML popups”””
landmarks = [
{‘name’: ‘Statue of Liberty’, ‘lat’: 40.6892, ‘lon’: -74.0445, ‘type’: ‘monument’, ‘visitors’: 4500000},
{‘name’: ‘Empire State Building’, ‘lat’: 40.7484, ‘lon’: -73.9857, ‘type’: ‘building’, ‘visitors’: 4000000},
{‘name’: ‘Central Park’, ‘lat’: 40.7829, ‘lon’: -73.9654, ‘type’: ‘park’, ‘visitors’: 42000000},
{‘name’: ‘Brooklyn Bridge’, ‘lat’: 40.7061, ‘lon’: -73.9969, ‘type’: ‘bridge’, ‘visitors’: 4000000},
{‘name’: ‘Times Square’, ‘lat’: 40.7580, ‘lon’: -73.9855, ‘type’: ‘plaza’, ‘visitors’: 50000000}
]
m = folium.Map(location=[40.7128, -74.0060], zoom_start=12)
icon_colors = {
‘monument’: ‘red’,
‘building’: ‘blue’,
‘park’: ‘green’,
‘bridge’: ‘orange’,
‘plaza’: ‘purple’
}
icon_symbols = {
‘monument’: ‘star’,
‘building’: ‘home’,
‘park’: ‘tree’,
‘bridge’: ‘road’,
‘plaza’: ‘shopping-cart’
}
for landmark in landmarks:
html = f”””
{landmark[‘name’]}
Type: {landmark[‘type’].title()}
Annual Visitors: {landmark[‘visitors’]:,}
“””
iframe = folium.IFrame(html, width=220, height=250)
popup = folium.Popup(iframe, max_width=220)
folium.Marker(
location=[landmark[‘lat’], landmark[‘lon’]],
popup=popup,
tooltip=landmark[‘name’],
icon=folium.Icon(
color=icon_colors[landmark[‘type’]],
icon=icon_symbols[landmark[‘type’]],
prefix=’fa’
)
).add_to(m)
folium.CircleMarker(
location=[40.7128, -74.0060],
radius=20,
popup=’NYC Center’,
color=”#3186cc”,
fill=True,
fillColor=”#3186cc”,
fillOpacity=0.2
).add_to(m)
return m
We build a map with advanced markers and rich HTML popups to represent real-world landmarks. We customize marker icons, colors, and symbols by location type to enhance visual clarity and semantic meaning. By embedding structured HTML content inside popups, we present detailed contextual information directly within the interactive map.
“””Create a heatmap showing data density”””
np.random.seed(42)
n_incidents = 1000
crime_data = []
hotspots = [
[40.7580, -73.9855],
[40.7484, -73.9857],
[40.7128, -74.0060],
]
for _ in range(n_incidents):
hotspot = hotspots[np.random.choice(len(hotspots))]
lat = hotspot[0] + np.random.normal(0, 0.02)
lon = hotspot[1] + np.random.normal(0, 0.02)
intensity = np.random.uniform(0.3, 1.0)
crime_data.append([lat, lon, intensity])
m = folium.Map(location=[40.7128, -74.0060], zoom_start=12)
HeatMap(
crime_data,
min_opacity=0.2,
max_zoom=18,
max_val=1.0,
radius=15,
blur=25,
gradient={
0.0: ‘blue’,
0.3: ‘lime’,
0.5: ‘yellow’,
0.7: ‘orange’,
1.0: ‘red’
}
).add_to(m)
title_html=””‘
NYC Crime Density Heatmap
Simulated incident data
”’
m.get_root().html.add_child(folium.Element(title_html))
return m
We generate synthetic spatial data and use a heatmap to visualize density patterns across geographic locations. We simulate clustered coordinates and apply gradient-based intensity visualization to reveal spatial concentration trends. By overlaying this density layer on the map, we gain insight into how events distribute across regions.
“””Create a choropleth map showing data across regions”””
us_states_url=”https://raw.githubusercontent.com/python-visualization/folium/master/examples/data/us-states.json”
try:
us_states = requests.get(us_states_url).json()
except:
print(“Warning: Could not fetch GeoJSON data. Using offline sample.”)
return None
state_data = {
‘Alabama’: 5.1, ‘Alaska’: 6.3, ‘Arizona’: 4.7, ‘Arkansas’: 3.8, ‘California’: 5.3,
‘Colorado’: 3.9, ‘Connecticut’: 4.3, ‘Delaware’: 4.1, ‘Florida’: 3.6, ‘Georgia’: 4.0,
‘Hawaii’: 2.8, ‘Idaho’: 2.9, ‘Illinois’: 5.0, ‘Indiana’: 3.5, ‘Iowa’: 3.1,
‘Kansas’: 3.3, ‘Kentucky’: 4.3, ‘Louisiana’: 4.6, ‘Maine’: 3.2, ‘Maryland’: 4.0,
‘Massachusetts’: 3.6, ‘Michigan’: 4.3, ‘Minnesota’: 3.2, ‘Mississippi’: 5.2,
‘Missouri’: 3.7, ‘Montana’: 3.5, ‘Nebraska’: 2.9, ‘Nevada’: 4.8, ‘New Hampshire’: 2.7,
‘New Jersey’: 4.2, ‘New Mexico’: 5.0, ‘New York’: 4.5, ‘North Carolina’: 4.0,
‘North Dakota’: 2.6, ‘Ohio’: 4.2, ‘Oklahoma’: 3.4, ‘Oregon’: 4.2, ‘Pennsylvania’: 4.4,
‘Rhode Island’: 4.0, ‘South Carolina’: 3.5, ‘South Dakota’: 2.9, ‘Tennessee’: 3.6,
‘Texas’: 4.0, ‘Utah’: 2.8, ‘Vermont’: 2.8, ‘Virginia’: 3.3, ‘Washington’: 4.6,
‘West Virginia’: 5.1, ‘Wisconsin’: 3.4, ‘Wyoming’: 3.6
}
df = pd.DataFrame(list(state_data.items()), columns=[‘State’, ‘Unemployment’])
m = folium.Map(location=[37.8, -96], zoom_start=4)
folium.Choropleth(
geo_data=us_states,
name=”choropleth”,
data=df,
columns=[‘State’, ‘Unemployment’],
key_on=’feature.properties.name’,
fill_color=”YlOrRd”,
fill_opacity=0.7,
line_opacity=0.5,
legend_name=”Unemployment Rate (%)”
).add_to(m)
style_function = lambda x: {‘fillColor’: ‘#ffffff’,
‘color’:’#000000′,
‘fillOpacity’: 0.1,
‘weight’: 0.1}
highlight_function = lambda x: {‘fillColor’: ‘#000000’,
‘color’:’#000000′,
‘fillOpacity’: 0.50,
‘weight’: 0.1}
NIL = folium.features.GeoJson(
us_states,
style_function=style_function,
control=False,
highlight_function=highlight_function,
tooltip=folium.features.GeoJsonTooltip(
fields=[‘name’],
aliases=[‘State:’],
style=(“background-color: white; color: #333333; font-family: arial; font-size: 12px; padding: 10px;”)
)
)
m.add_child(NIL)
m.keep_in_front(NIL)
folium.LayerControl().add_to(m)
return m
We create a choropleth map by combining GeoJSON boundary data with structured numerical attributes. We map unemployment rates to geographic regions and use color gradients to visually represent statistical differences. By enabling hover interactions and tooltips, we can explore region-specific data directly within the map interface.
“””Create a map with marker clustering for large datasets”””
np.random.seed(123)
n_locations = 5000
lats = np.random.uniform(25, 49, n_locations)
lons = np.random.uniform(-125, -65, n_locations)
values = np.random.randint(1, 100, n_locations)
df_markers = pd.DataFrame({
‘lat’: lats,
‘lon’: lons,
‘value’: values
})
m = folium.Map(location=[37.8, -96], zoom_start=4)
marker_cluster = MarkerCluster(
name=”Location Cluster”,
overlay=True,
control=True
).add_to(m)
for idx, row in df_markers.iterrows():
if row[‘value’]
Marker Clustering Demo
5000 markers – zoom to see individual points
”’
m.get_root().html.add_child(folium.Element(title_html))
return m
def create_time_series_map():
“””Create an animated map showing data changes over time”””
start_date = datetime(2024, 8, 1)
features = []
path = [
[25.0, -70.0], [26.5, -72.0], [28.0, -74.5], [29.5, -76.5],
[31.0, -78.0], [32.5, -79.5], [34.0, -80.5], [35.5, -81.0]
]
for i, (lat, lon) in enumerate(path):
timestamp = start_date + timedelta(hours=i*6)
feature = {
‘type’: ‘Feature’,
‘geometry’: {
‘type’: ‘Point’,
‘coordinates’: [lon, lat]
},
‘properties’: {
‘time’: timestamp.isoformat(),
‘popup’: f’Hurricane PositionTime: {timestamp.strftime(“%Y-%m-%d %H:%M”)}Category: {min(5, i//2 + 1)}’,
‘icon’: ‘circle’,
‘iconstyle’: {
‘fillColor’: [‘yellow’, ‘orange’, ‘red’, ‘darkred’, ‘purple’][min(4, i//2)],
‘fillOpacity’: 0.8,
‘stroke’: ‘true’,
‘radius’: 8 + i * 2
}
}
}
features.append(feature)
m = folium.Map(
location=[30.0, -75.0],
zoom_start=5,
tiles=”CartoDB Positron”
)
TimestampedGeoJson(
{‘type’: ‘FeatureCollection’, ‘features’: features},
period=’PT6H’,
add_last_point=True,
auto_play=True,
loop=True,
max_speed=2,
loop_button=True,
date_options=”YYYY-MM-DD HH:mm”,
time_slider_drag_update=True
).add_to(m)
title_html=””‘
Hurricane Path Animation
Simulated hurricane trackingUse controls below to play/pause
”’
m.get_root().html.add_child(folium.Element(title_html))
return m
def create_interactive_plugins_map():
“””Create a map with multiple interactive plugins”””
m = folium.Map(
location=[40.7128, -74.0060],
zoom_start=12,
tiles=”OpenStreetMap”
)
minimap = MiniMap(toggle_display=True)
m.add_child(minimap)
draw = Draw(
export=True,
filename=”drawn_shapes.geojson”,
position=’topleft’,
draw_options={
‘polyline’: True,
‘polygon’: True,
‘circle’: True,
‘rectangle’: True,
‘marker’: True,
‘circlemarker’: True
},
edit_options={‘edit’: True}
)
m.add_child(draw)
Fullscreen(
position=’topright’,
title=”Expand map”,
title_cancel=”Exit fullscreen”,
force_separate_button=True
).add_to(m)
plugins.MeasureControl(
position=’bottomleft’,
primary_length_unit=”kilometers”,
secondary_length_unit=”miles”,
primary_area_unit=”sqkilometers”,
secondary_area_unit=”acres”
).add_to(m)
plugins.MousePosition(
position=’bottomright’,
separator=” | “,
empty_string=’NaN’,
lng_first=True,
num_digits=20,
prefix=’Coordinates:’,
).add_to(m)
plugins.LocateControl(
auto_start=False,
position=’topleft’
).add_to(m)
folium.Marker(
[40.7128, -74.0060],
popup=’NYCTry the drawing tools!’,
icon=folium.Icon(color=”red”, icon=’info-sign’)
).add_to(m)
return m
def create_earthquake_map():
“””Create comprehensive earthquake visualization using real USGS data”””
url=”https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_month.geojson”
try:
response = requests.get(url)
earthquake_data = response.json()
print(f”Successfully loaded {len(earthquake_data[‘features’])} earthquakes”)
except Exception as e:
print(f”Error fetching data: {e}”)
earthquake_data = {
‘features’: [
{
‘properties’: {‘mag’: 5.2, ‘place’: ‘Sample Location 1’, ‘time’: 1640000000000},
‘geometry’: {‘coordinates’: [-122.0, 37.0, 10]}
},
{
‘properties’: {‘mag’: 6.1, ‘place’: ‘Sample Location 2’, ‘time’: 1640100000000},
‘geometry’: {‘coordinates’: [140.0, 35.0, 20]}
}
]
}
earthquakes = []
for feature in earthquake_data[‘features’]:
props = feature[‘properties’]
coords = feature[‘geometry’][‘coordinates’]
earthquakes.append({
‘lat’: coords[1],
‘lon’: coords[0],
‘depth’: coords[2],
‘magnitude’: props.get(‘mag’, 0),
‘place’: props.get(‘place’, ‘Unknown’),
‘time’: datetime.fromtimestamp(props.get(‘time’, 0) / 1000)
})
df_eq = pd.DataFrame(earthquakes)
print(f”\nEarthquake Statistics:”)
print(f”Total earthquakes: {len(df_eq)}”)
print(f”Magnitude range: {df_eq[‘magnitude’].min():.1f} – {df_eq[‘magnitude’].max():.1f}”)
print(f”Depth range: {df_eq[‘depth’].min():.1f} – {df_eq[‘depth’].max():.1f} km”)
m = folium.Map(
location=[20, 0],
zoom_start=2,
tiles=”CartoDB dark_matter”
)
minor = folium.FeatureGroup(name=”Minor (
Magnitude {mag:.1f}
Location: {eq[‘place’]}
Depth: {eq[‘depth’]:.1f} km
Time: {eq[‘time’].strftime(‘%Y-%m-%d %H:%M:%S’)}
Coordinates: {eq[‘lat’]:.4f}, {eq[‘lon’]:.4f}
“””
folium.CircleMarker(
location=[eq[‘lat’], eq[‘lon’]],
radius=radius,
popup=folium.Popup(popup_html, max_width=270),
tooltip=f”M{mag:.1f} – {eq[‘place’]}”,
color=color,
fill=True,
fillColor=color,
fillOpacity=0.7,
weight=2
).add_to(group)
minor.add_to(m)
moderate.add_to(m)
strong.add_to(m)
major.add_to(m)
heat_data = [[row[‘lat’], row[‘lon’], row[‘magnitude’]] for idx, row in df_eq.iterrows()]
heatmap = folium.FeatureGroup(name=”Density Heatmap”, show=False)
HeatMap(
heat_data,
min_opacity=0.3,
radius=15,
blur=20,
gradient={0.4: ‘blue’, 0.6: ‘cyan’, 0.7: ‘lime’, 0.8: ‘yellow’, 1: ‘red’}
).add_to(heatmap)
heatmap.add_to(m)
folium.LayerControl(position=’topright’, collapsed=False).add_to(m)
legend_html=””‘
Earthquake Magnitude
● Minor (
● Moderate (4.0-5.0)
● Strong (5.0-6.0)
● Major (≥ 6.0)
Data: USGS (Past 30 days)
”’
m.get_root().html.add_child(folium.Element(legend_html))
title_html=””‘
🌍 Global Earthquake Monitor
Real-time earthquake data (M ≥ 2.5)Click markers for details | Toggle layers to explore
”’
m.get_root().html.add_child(folium.Element(title_html))
Fullscreen(position=’topright’).add_to(m)
return m
if __name__ == “__main__”:
print(“=” * 80)
print(“ADVANCED FOLIUM TUTORIAL – ALL EXAMPLES”)
print(“=” * 80)
print(“\nGenerating all maps…\n”)
maps = {
‘multi_tile_map’: create_multi_tile_map(),
‘advanced_markers_map’: create_advanced_markers_map(),
‘heatmap’: create_heatmap(),
‘choropleth_map’: create_choropleth_map(),
‘marker_cluster_map’: create_marker_cluster_map(),
‘time_series_map’: create_time_series_map(),
‘interactive_plugins_map’: create_interactive_plugins_map(),
‘earthquake_map’: create_earthquake_map()
}
print(“\n” + “=” * 80)
print(“SAVING MAPS TO HTML FILES”)
print(“=” * 80)
for name, map_obj in maps.items():
if map_obj is not None:
filename = f”{name}.html”
map_obj.save(filename)
print(f”✓ Saved: {filename}”)
else:
print(f”✗ Skipped: {name} (map generation failed)”)
print(“\n” + “=” * 80)
print(“ALL MAPS GENERATED SUCCESSFULLY!”)
print(“=” * 80)
print(“\nYou can now:”)
print(“1. Open any HTML file in your browser to view the interactive map”)
print(“2. Access the map objects in code using the ‘maps’ dictionary”)
print(“3. Display maps in Jupyter/Colab by returning the map object”)
print(“\nExample: To display the earthquake map in a notebook, just run:”)
print(” maps[‘earthquake_map’]”)
print(“\n” + “=” * 80)


