Folium Python Mapping Tutorial
Folium is a powerful Python library that makes it easy to visualize geospatial data on interactive maps. Built on top of Leaflet.js, Folium allows you to create beautiful, interactive maps with just a few lines of Python code.
Installation
First, install Folium using pip:
pip install folium
For additional functionality with data manipulation:
pip install folium pandas geopandas
Basic Map Creation
Let’s start with creating a simple map:
import folium
# Create a base map centered on a specific location
# Default tile layer is OpenStreetMap
m = folium.Map(
location=[45.5236, -122.6750], # Portland, Oregon coordinates
zoom_start=13,
tiles='OpenStreetMap'
)
# Display the map
m
Different Tile Layers
Folium supports various tile layers:
# Satellite imagery
m_satellite = folium.Map(
location=[45.5236, -122.6750],
zoom_start=13,
tiles='Esri.WorldImagery'
)
# Terrain view
m_terrain = folium.Map(
location=[45.5236, -122.6750],
zoom_start=13,
tiles='Stamen Terrain'
)
# Dark theme
m_dark = folium.Map(
location=[45.5236, -122.6750],
zoom_start=13,
tiles='CartoDB dark_matter'
)
Adding Markers
Simple Markers
import folium
# Create a map
m = folium.Map(location=[45.5236, -122.6750], zoom_start=13)
# Add a simple marker
folium.Marker(
location=[45.5236, -122.6750],
popup='Portland, Oregon'
).add_to(m)
# Add multiple markers
locations = [
[45.5236, -122.6750, 'Portland'],
[45.5152, -122.6784, 'Downtown Portland'],
[45.5424, -122.6544, 'Rose Garden']
]
for lat, lon, name in locations:
folium.Marker(
location=[lat, lon],
popup=name
).add_to(m)
m
Circle Markers
# Create a map
m = folium.Map(location=[45.5236, -122.6750], zoom_start=13)
# Add circle markers
folium.CircleMarker(
location=[45.5236, -122.6750],
radius=20,
popup='Portland',
color='red',
fillColor='red',
fillOpacity=0.7
).add_to(m)
m
Marker Customization
Custom Icons
import folium
m = folium.Map(location=[45.5236, -122.6750], zoom_start=13)
# Custom icon marker
folium.Marker(
location=[45.5236, -122.6750],
popup='Custom Icon',
icon=folium.Icon(color='green', icon='info-sign')
).add_to(m)
# Font Awesome icons
folium.Marker(
location=[45.5152, -122.6784],
popup='Coffee Shop',
icon=folium.Icon(color='brown', prefix='fa', icon='coffee')
).add_to(m)
# Custom color and icon
folium.Marker(
location=[45.5424, -122.6544],
popup='Hospital',
icon=folium.Icon(color='red', icon='plus-sign')
).add_to(m)
m
Marker Clusters
For handling many markers efficiently:
from folium.plugins import MarkerCluster
# Create a map
m = folium.Map(location=[45.5236, -122.6750], zoom_start=11)
# Create a marker cluster
marker_cluster = MarkerCluster().add_to(m)
# Sample data with many points
import random
for i in range(100):
lat = 45.5236 + random.uniform(-0.1, 0.1)
lon = -122.6750 + random.uniform(-0.1, 0.1)
folium.Marker(
location=[lat, lon],
popup=f'Marker {i}'
).add_to(marker_cluster)
m
Adding Popups and Tooltips
Rich HTML Popups
import folium
m = folium.Map(location=[45.5236, -122.6750], zoom_start=13)
# HTML popup
html_popup = """
<div style="width: 200px;">
<h4>Portland, Oregon</h4>
<p>Known for:</p>
<ul>
<li>Food trucks</li>
<li>Coffee culture</li>
<li>Breweries</li>
</ul>
<img src="https://via.placeholder.com/150x100" width="150">
</div>
"""
popup = folium.Popup(html_popup, max_width=300)
folium.Marker(
location=[45.5236, -122.6750],
popup=popup
).add_to(m)
m
Tooltips
# Tooltip that appears on hover
folium.Marker(
location=[45.5152, -122.6784],
popup='Click for popup',
tooltip='Hover tooltip text'
).add_to(m)
# HTML tooltip
html_tooltip = "<b>Rich Tooltip</b><br>This is a <i>formatted</i> tooltip"
folium.Marker(
location=[45.5424, -122.6544],
tooltip=html_tooltip
).add_to(m)
Working with GeoJSON Data
Adding GeoJSON Layers
import folium
import json
# Sample GeoJSON data
geojson_data = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"name": "Portland Area",
"population": 650000
},
"geometry": {
"type": "Polygon",
"coordinates": [[
[-122.7, 45.4],
[-122.6, 45.4],
[-122.6, 45.6],
[-122.7, 45.6],
[-122.7, 45.4]
]]
}
}
]
}
m = folium.Map(location=[45.5236, -122.6750], zoom_start=11)
# Add GeoJSON layer
folium.GeoJson(
geojson_data,
style_function=lambda feature: {
'fillColor': 'lightblue',
'color': 'black',
'weight': 2,
'fillOpacity': 0.7,
},
popup=folium.GeoJsonPopup(fields=['name', 'population'])
).add_to(m)
m
Styling GeoJSON Features
# Dynamic styling based on properties
def style_function(feature):
return {
'fillColor': 'red' if feature['properties']['population'] > 500000 else 'blue',
'color': 'black',
'weight': 2,
'fillOpacity': 0.7,
}
folium.GeoJson(
geojson_data,
style_function=style_function
).add_to(m)
Choropleth Maps
Choropleth maps are great for visualizing statistical data across geographic regions:
import folium
import pandas as pd
# Sample data
data = {
'state': ['Oregon', 'Washington', 'California'],
'value': [100, 150, 300]
}
df = pd.DataFrame(data)
# Create choropleth map
m = folium.Map(location=[45.5236, -122.6750], zoom_start=6)
# Note: You would need actual GeoJSON data for US states
# This is a simplified example
folium.Choropleth(
geo_data='path/to/us-states.json', # Path to your GeoJSON file
name='choropleth',
data=df,
columns=['state', 'value'],
key_on='feature.properties.NAME',
fill_color='YlOrRd',
fill_opacity=0.7,
line_opacity=0.2,
legend_name='Values'
).add_to(m)
folium.LayerControl().add_to(m)
Heat Maps
Heat maps are excellent for showing density or intensity of data points:
from folium.plugins import HeatMap
import random
# Generate sample data
heat_data = []
for i in range(1000):
lat = 45.5236 + random.gauss(0, 0.02)
lon = -122.6750 + random.gauss(0, 0.02)
intensity = random.random()
heat_data.append([lat, lon, intensity])
# Create heat map
m = folium.Map(location=[45.5236, -122.6750], zoom_start=12)
HeatMap(heat_data).add_to(m)
m
Time-based Heat Maps
from folium.plugins import HeatMapWithTime
import datetime
# Generate time-series heat map data
heat_data_time = []
for day in range(7): # 7 days of data
day_data = []
for i in range(100):
lat = 45.5236 + random.gauss(0, 0.02)
lon = -122.6750 + random.gauss(0, 0.02)
day_data.append([lat, lon])
heat_data_time.append(day_data)
# Create time-based heat map
m = folium.Map(location=[45.5236, -122.6750], zoom_start=12)
HeatMapWithTime(
heat_data_time,
index=['Day 1', 'Day 2', 'Day 3', 'Day 4', 'Day 5', 'Day 6', 'Day 7'],
auto_play=True,
max_opacity=0.8
).add_to(m)
m
Adding Layers
Layer Control
import folium
m = folium.Map(location=[45.5236, -122.6750], zoom_start=13)
# Base layers
folium.raster_layers.TileLayer(
tiles='OpenStreetMap',
name='OpenStreetMap'
).add_to(m)
folium.raster_layers.TileLayer(
tiles='Stamen Terrain',
name='Terrain'
).add_to(m)
# Feature groups (overlay layers)
markers_group = folium.FeatureGroup(name='Markers')
circles_group = folium.FeatureGroup(name='Circles')
# Add markers to groups
folium.Marker([45.5236, -122.6750], popup='Marker 1').add_to(markers_group)
folium.CircleMarker([45.5152, -122.6784], radius=10, popup='Circle 1').add_to(circles_group)
# Add groups to map
markers_group.add_to(m)
circles_group.add_to(m)
# Add layer control
folium.LayerControl().add_to(m)
m
Advanced Features
Drawing Tools
from folium.plugins import Draw
m = folium.Map(location=[45.5236, -122.6750], zoom_start=13)
# Add drawing tools
draw = Draw(
export=True,
filename='data.geojson',
position='topleft',
draw_options={
'polyline': True,
'polygon': True,
'circle': True,
'rectangle': True,
'marker': True,
'circlemarker': True
}
)
draw.add_to(m)
m
Measuring Tool
from folium.plugins import MeasureControl
m = folium.Map(location=[45.5236, -122.6750], zoom_start=13)
# Add measuring tool
measure_control = MeasureControl(
position='topright',
primary_length_unit='meters',
secondary_length_unit='miles',
primary_area_unit='sqmeters',
secondary_area_unit='acres'
)
measure_control.add_to(m)
m
Working with Real Data
Using Pandas with Geographic Data
import pandas as pd
import folium
# Sample data
data = {
'City': ['Portland', 'Seattle', 'San Francisco'],
'Latitude': [45.5236, 47.6062, 37.7749],
'Longitude': [-122.6750, -122.3321, -122.4194],
'Population': [650000, 750000, 875000]
}
df = pd.DataFrame(data)
# Create map
m = folium.Map(location=[45.5236, -122.6750], zoom_start=6)
# Add markers from DataFrame
for idx, row in df.iterrows():
folium.CircleMarker(
location=[row['Latitude'], row['Longitude']],
radius=row['Population'] / 50000, # Scale radius by population
popup=f"{row['City']}<br>Population: {row['Population']:,}",
color='blue',
fillColor='lightblue',
fillOpacity=0.7
).add_to(m)
m
Saving and Displaying Maps
Saving Maps
# Save as HTML file
m.save('my_map.html')
# Save with specific options
m.save('my_map.html', close_file=False)
# Get HTML representation
html_string = m._repr_html_()
Displaying in Jupyter Notebooks
# In Jupyter, simply call the map object
m
# Or explicitly display
from IPython.display import display
display(m)
Converting to PNG/PDF
# Using selenium (requires additional setup)
from selenium import webdriver
# Save map as HTML first
m.save('temp_map.html')
# Use selenium to capture screenshot
options = webdriver.ChromeOptions()
options.add_argument('--headless')
driver = webdriver.Chrome(options=options)
driver.get('file:///path/to/temp_map.html')
driver.save_screenshot('map.png')
driver.quit()
Best Practices and Tips
Performance Optimization
- Use MarkerCluster for many markers: When displaying hundreds or thousands of markers, use MarkerCluster to improve performance.
- Limit data size: For large datasets, consider data aggregation or sampling.
- Choose appropriate zoom levels: Set reasonable default zoom levels for your data extent.
Design Considerations
- Color schemes: Use colorbrewer schemes for choropleth maps.
- Popup content: Keep popups informative but not overwhelming.
- Layer organization: Use meaningful names for layers and feature groups.
Common Issues and Solutions
- Coordinate order: Folium uses [latitude, longitude] order, which is opposite to many GIS systems.
- Large file sizes: Maps with lots of data can create large HTML files. Consider data optimization.
- Mobile responsiveness: Test your maps on different screen sizes.
Example: Complete Project
Here’s a complete example combining multiple features:
import folium
import pandas as pd
from folium.plugins import MarkerCluster, HeatMap
import random
# Create sample data
cities_data = {
'city': ['Portland', 'Eugene', 'Salem', 'Bend', 'Corvallis'],
'lat': [45.5152, 44.0521, 44.9429, 44.0582, 44.5646],
'lon': [-122.6784, -123.0868, -123.0351, -121.3153, -123.2620],
'population': [650000, 170000, 175000, 95000, 58000]
}
df = pd.DataFrame(cities_data)
# Create base map
m = folium.Map(
location=[44.5, -122.5],
zoom_start=7,
tiles='OpenStreetMap'
)
# Add marker cluster
marker_cluster = MarkerCluster(name='Cities').add_to(m)
# Add city markers
for idx, row in df.iterrows():
folium.Marker(
location=[row['lat'], row['lon']],
popup=f"<b>{row['city']}</b><br>Population: {row['population']:,}",
icon=folium.Icon(color='blue', icon='info-sign')
).add_to(marker_cluster)
# Add heat map layer
heat_data = [[row['lat'], row['lon'], row['population']/10000]
for idx, row in df.iterrows()]
hm = folium.FeatureGroup(name='Population Heat Map')
HeatMap(heat_data).add_to(hm)
hm.add_to(m)
# Add layer control
folium.LayerControl().add_to(m)
# Save the map
m.save('oregon_cities_map.html')
# Display
m
This tutorial covers the essential features of Folium for creating interactive maps in Python. Experiment with different combinations of markers, layers, and styling options to create maps that effectively communicate your geospatial data story.