Python 地理信息处理:geopy、shapely、folium 地图可视化实战
Introduction
地理信息处理是很多应用场景的基础。本文讲解用 geopy 做地址编码和距离计算、用 shapely 做几何运算(点、线、面)、用 folium 绑 Leaflet 做交互地图可视化。
---
geopy:地址与地理编码
pip install geopy
from geopy.geocoders import Nominatim
from geopy.distance import geodesic
from geopy.exc import GeocoderTimedOut
import time
geocoder = Nominatim(user_agent='my_app')
# 地址 → 坐标
location = geocoder.geocode('杭州市西湖区')
print(f"地址: {location.address}")
print(f"坐标: ({location.latitude}, {location.longitude})")
# 坐标 → 地址(反向编码)
location = geocoder.reverse('30.2741, 120.0001')
print(location.address)
# 计算两点距离(公里)
hangzhou = (30.2741, 120.0001)
shanghai = (31.2304, 121.4737)
dist = geodesic(hangzhou, shanghai).kilometers
print(f"杭州到上海: {dist:.1f} 公里")
# 批量编码(注意频率限制)
def batch_geocode(addresses):
results = []
for addr in addresses:
try:
loc = geocoder.geocode(addr)
results.append((addr, loc.latitude, loc.longitude) if loc else (addr, None, None))
time.sleep(1) # Nominatim 要求每秒最多一次
except GeocoderTimedOut:
results.append((addr, None, None))
return results
shapely:几何运算
pip install shapely
from shapely.geometry import Point, LineString, Polygon, MultiPolygon
from shapely.ops import unary_union
import matplotlib.pyplot as plt
# 点
p1 = Point(120.0, 30.0)
p2 = Point(121.0, 31.0)
print(f"两点距离: {p1.distance(p2):.3f} 度")
# 线
road = LineString([(120.0, 30.0), (120.5, 30.5), (121.0, 30.0)])
print(f"线长度: {road.length:.3f}")
print(f"线的中心: {road.centroid}")
# 面(矩形)
park = Polygon([(120.0, 30.0), (120.1, 30.0), (120.1, 30.1), (120.0, 30.1)])
print(f"公园面积: {park.area:.6f} 平方度")
# 判断包含/相交
point = Point(120.05, 30.05)
print(f"点在公园内: {park.contains(point)}") # True
print(f"点在公园边界上: {park.touches(point)}") # False
# 缓冲区分析(半径1度圆)
circle = point.buffer(1)
print(f"圆形面积: {circle.area:.3f}")
# 多边形合并(做辐射区域合并)
polygons = [Point(120, 30).buffer(0.5), Point(121, 31).buffer(0.5)]
merged = unary_union(polygons)
print(f"合并后类型: {merged.geom_type}")
folium:交互地图
pip install folium
import folium
# 创建地图(以杭州为中心)
m = folium.Map(location=[30.27, 120.15], zoom_start=12, tiles='OpenStreetMap')
# 添加标记
folium.Marker(
location=[30.2741, 120.0001],
popup='杭州西湖',
tooltip='点击查看详情',
icon=folium.Icon(color='green', icon='star')
).add_to(m)
# 添加弹出信息
folium.Marker(
location=[30.28, 120.12],
popup=folium.Popup('<b>灵隐寺</b><br>千年古刹', max_width=200),
).add_to(m)
# 添加圆(辐射范围)
folium.Circle(
location=[30.27, 120.15],
radius=2000, # 米
color='red', fill=True, fill_color='red', fill_opacity=0.2
).add_to(m)
# 添加热力图层
import random
heat_data = [[30.27 + random.gauss(0, 0.05), 120.15 + random.gauss(0, 0.05)] for _ in range(100)]
folium.plugins.HeatMap(heat_data).add_to(m)
# 地理编码弹窗
from folium.plugins import MousePosition
m.add_child(MousePosition())
m.save('hangzhou_map.html')
print('地图已生成: hangzhou_map.html')
完整实战:门店分布分析
import folium, pandas as pd
from folium.plugins import MarkerCluster
# 模拟门店数据
stores = pd.DataFrame({
'name': ['武林店', '西湖店', '滨江店', '城西店', '下沙店'],
'lat': [30.27, 30.25, 30.21, 30.28, 30.30],
'lon': [120.15, 120.14, 120.21, 120.10, 120.35],
'sales': [150, 220, 180, 95, 60]
})
m = folium.Map(location=[30.26, 120.18], zoom_start=11)
cluster = MarkerCluster(name='门店标注').add_to(m)
for _, row in stores.iterrows():
folium.Marker(
location=[row['lat'], row['lon']],
popup=f"<b>{row['name']}</b><br>月销: {row['sales']}万",
tooltip=row['name'],
icon=folium.Icon(
color='green' if row['sales'] > 150 else 'orange' if row['sales'] > 100 else 'red'
)
).add_to(cluster)
folium.LayerControl().add_to(m)
m.save('stores_map.html')
常见问题
Q1: folium 地图不显示中文标注?设置 tiles='OpenStreetMap' 时中文可能显示方块,用国内地图服务(如高德)需要申请 API Key。
Q2: shapely 的距离单位是什么?默认是度(经纬度),不是米。1度 ≈ 111公里。要精确米,用 pyproj 投影到平面坐标系。
Q3: 地理编码请求频率限制?Nominatim 要求每秒不超过 1 次,Keyed 功能免费但需申请。生产环境用 Google Maps API 或高德/百度地图 API(需要注册)。
延伸阅读
- GeoPandas(pandas 的地理扩展)
- 高德/百度地图 API
- GIS 数据格式(GeoJSON/Shapefile)
---
作者:小马 | 绍大技术网 shaoda.net