在很多实际应用场景中,我们无法依赖网络请求(如高德、百度地图 API),例如:
这时,离线地理编码(Offline Geocoding) 就成为必要选择。本文将介绍如何使用 GeoJSON + Shapely 在 Python 中实现 经纬度 ↔ 地区名称 的双向转换,并支持计算两地距离。
pythonpip install shapely
以下 GeoJSON 文件(可从阿里云 DataV 地理工具下载):
geo_province.json:省级边界geo_city.json:市级边界geo_china.json:区县级边界(全国)pythonimport json
import math
from shapely.geometry import Point, shape
class OfflineGeocoder:
def __init__(self, geojson_paths):
self.features = []
self.code2name = {}
for geojson_path in geojson_paths:
with open(geojson_path, 'r', encoding='utf-8') as f:
geojson_data = json.load(f)
self.features.extend(geojson_data['features'])
for feature in geojson_data['features']:
self.code2name[feature['properties']['adcode']] = feature['properties']['name']
self.name_index = {}
for feat in self.features:
props = feat['properties']
key = f"{props.get('name', '')}"
self.name_index[key] = feat
def position2region(self, lat, lon):
"""经纬度 → 地区"""
point = Point(lon, lat)
features = []
for feat in self.features:
if shape(feat['geometry']).contains(point):
features.append(feat)
ans = ""
for feat in features:
props = feat['properties']
temp = ""
for code in props.get('acroutes', []):
temp += f"{self.code2name.get(code, '')}-"
temp += f"{props.get('name', '')}"
if len(temp) > len(ans):
ans = temp
return ans
def region2position(self, full_name):
"""地区 → 经纬度(质心)"""
feat = self.name_index.get(full_name)
if not feat:
return None
centroid = shape(feat['geometry']).centroid
return {'lat': centroid.y, 'lon': centroid.x}
def distance_of_regions(self, region1, region2):
"""
地区 → 距离(千米),保留 2 位小数
"""
pos1 = self.region2position(region1)
pos2 = self.region2position(region2)
if not pos1 or not pos2:
return None
dis = self.distance_of_position(pos1['lat'], pos1['lon'], pos2['lat'], pos2['lon']) / 1000
return f"{dis:.2f} km"
def distance_of_position(self, lat1, lon1, lat2, lon2):
"""经纬度 → 距离(米)"""
# 将度转换为弧度
lat1_rad = math.radians(lat1)
lon1_rad = math.radians(lon1)
lat2_rad = math.radians(lat2)
lon2_rad = math.radians(lon2)
# 地球半径(米)
R = 6371000
# Haversine公式计算两点间距离
dlat = lat2_rad - lat1_rad
dlon = lon2_rad - lon1_rad
a = math.sin(dlat/2)**2 + math.cos(lat1_rad) * math.cos(lat2_rad) * math.sin(dlon/2)**2
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
distance = R * c
return distance
Geo = OfflineGeocoder(["geo_china.json", "geo_province.json", "geo_city.json"])
测试
pythonfrom geo_helper import Geo
# 位置转地区
print(Geo.position2region(39.9087, 116.4729))
# 地区转位置
print(Geo.region2position("银川市"))
# 地区距离
print(Geo.distance_of_regions("银川市", "北京市"))


本文作者:42tr
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!