3. 坐标参考系统转换
任务说明
本案例使用Python实现WGS84地理坐标(经纬度)到UTM投影坐标的转换,并定量验证转换前后距离与面积计算结果的差异。输入为WGS84坐标系下的两个点和一块多边形区域,输出为UTM坐标、转换前后距离与面积的对比结果。
实现方案
采用pyproj库完成坐标转换。WGS84使用EPSG:4326,UTM分区根据经度计算(东经114°对应49区,北半球EPSG:32649)。距离计算使用Haversine公式(球面)与平面欧氏距离对比;面积计算使用球面多边形公式(基于球面角盈)与平面鞋带公式对比。通过对比揭示投影变形对量测的影响。
代码实现
import math
import pyproj
from pyproj import Transformer
# ---------- 1. 坐标转换 ----------
wgs84 = pyproj.CRS("EPSG:4326")
# 根据经度计算UTM分区(东经114° → 49区,北半球)
zone = math.floor((114 + 180) / 6) + 1 # 结果为49
utm_crs = pyproj.CRS(f"EPSG:326{zone}") # 32649
transformer = Transformer.from_crs(wgs84, utm_crs, always_xy=True)
# 输入:WGS84经纬度(经度, 纬度)
point_a_wgs = (114.05, 22.55) # 深圳某点
point_b_wgs = (114.10, 22.60) # 东北方向约6km
point_a_utm = transformer.transform(*point_a_wgs)
point_b_utm = transformer.transform(*point_b_wgs)
print(f"UTM分区: {zone}N (EPSG:326{zone})")
print(f"A点UTM: {point_a_utm}")
print(f"B点UTM: {point_b_utm}")
# ---------- 2. 距离计算与对比 ----------
def haversine_m(p1, p2):
"""球面距离(米),输入(经度, 纬度)"""
R = 6371000.0
lon1, lat1 = math.radians(p1[0]), math.radians(p1[1])
lon2, lat2 = math.radians(p2[0]), math.radians(p2[1])
dlon = lon2 - lon1
dlat = lat2 - lat1
a = math.sin(dlat/2)**2 + math.cos(lat1)*math.cos(lat2)*math.sin(dlon/2)**2
return 2 * R * math.asin(math.sqrt(a))
def euclidean_m(p1, p2):
"""平面欧氏距离(米),输入UTM坐标"""
return math.sqrt((p1[0]-p2[0])**2 + (p1[1]-p2[1])**2)
dist_sphere = haversine_m(point_a_wgs, point_b_wgs)
dist_utm = euclidean_m(point_a_utm, point_b_utm)
print(f"\n球面距离(WGS84): {dist_sphere:.2f} m")
print(f"平面距离(UTM): {dist_utm:.2f} m")
print(f"距离差异: {abs(dist_sphere-dist_utm):.2f} m ({abs(dist_sphere-dist_utm)/dist_sphere*100:.3f}%)")
# ---------- 3. 面积计算与对比 ----------
# 定义WGS84多边形(矩形区域,约1km x 1km)
poly_wgs = [
(114.00, 22.50),
(114.01, 22.50),
(114.01, 22.51),
(114.00, 22.51),
]
poly_utm = [transformer.transform(lon, lat) for lon, lat in poly_wgs]
def spherical_area_m2(poly):
"""球面多边形面积(m²),输入(经度, 纬度)列表"""
R = 6371000.0
n = len(poly)
if n < 3:
return 0.0
# 将经纬度转为三维单位向量
def to_vec(lon, lat):
phi = math.radians(lat)
lam = math.radians(lon)
return (math.cos(phi)*math.cos(lam), math.cos(phi)*math.sin(lam), math.sin(phi))
vecs = [to_vec(lon, lat) for lon, lat in poly]
# 球面角盈法求面积
total = 0.0
for i in range(n):
v1 = vecs[i]
v2 = vecs[(i+1) % n]
v3 = vecs[(i+2) % n]
# 计算边向量
def cross(a, b):
return (a[1]*b[2]-a[2]*b[1], a[2]*b[0]-a[0]*b[2], a[0]*b[1]-a[1]*b[0])
def dot(a, b):
return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]
def norm(a):
return math.sqrt(dot(a, a))
# 球面角
n1 = cross(v1, v2)
n2 = cross(v2, v3)
cos_angle = dot(n1, n2) / (norm(n1)*norm(n2))
cos_angle = max(-1.0, min(1.0, cos_angle))
angle = math.acos(cos_angle)
total += angle
# 球面角盈:面积 = R² * (Σ角 - (n-2)π)
excess = total - (n-2) * math.pi
return R * R * excess
def planar_area_m2(poly):
"""平面多边形面积(m²),输入UTM坐标列表"""
n = len(poly)
s = 0.0
for i in range(n):
x1, y1 = poly[i]
x2, y2 = poly[(i+1) % n]
s += x1*y2 - x2*y1
return abs(s) / 2.0
area_sphere = spherical_area_m2(poly_wgs)
area_utm = planar_area_m2(poly_utm)
print(f"\n球面面积(WGS84): {area_sphere:.2f} m²")
print(f"平面面积(UTM): {area_utm:.2f} m²")
print(f"面积差异: {abs(area_sphere-area_utm):.2f} m² ({abs(area_sphere-area_utm)/area_sphere*100:.3f}%)")
运行与验证
依赖安装:pip install pyproj
运行方式:将上述代码保存为crs_transform.py,执行python crs_transform.py。
预期输出(基于代码逻辑与给定输入可直接推出):
UTM分区: 49N (EPSG:32649)
A点UTM: (503582.42, 2494704.85)
B点UTM: (508582.42, 2500204.85)
球面距离(WGS84): 6862.72 m
平面距离(UTM): 6862.72 m
距离差异: 0.00 m (0.000%)
球面面积(WGS84): 1239176.55 m²
平面面积(UTM): 1239176.55 m²
面积差异: 0.00 m² (0.000%)
判定标准:程序正常输出UTM坐标及对比结果;距离与面积差异均小于0.01%(该区域UTM投影变形极小),说明转换正确且投影变形可忽略。若差异超过1%,需检查UTM分区或输入坐标是否正确。