第6章:坐标参考系统(CRS)
坐标参考系统(Coordinate Reference System,CRS)是 GIS 数据处理中最基础也是最关键的概念之一。它定义了空间数据中坐标值与地球上实际位置之间的映射关系。在 GeoPandas 中,正确理解和使用 CRS 是进行空间分析的前提。本章将深入讲解 CRS 的原理、GeoPandas 中的 CRS 操作,以及中国常用坐标系的应用。
6.1 什么是坐标参考系统
6.1.1 基本概念
坐标参考系统是一种数学模型,用于将地球表面的三维位置映射到二维平面(或保持三维)的坐标值上。每个 CRS 包含以下核心要素:
| 要素 | 说明 |
|---|---|
| 基准面(Datum) | 定义椭球体的形状和位置,如 WGS84、CGCS2000 |
| 椭球体(Ellipsoid) | 近似地球形状的数学模型,包含长半轴和扁率 |
| 投影方式(Projection) | 将三维曲面映射到二维平面的数学方法 |
| 坐标轴定义 | 坐标轴的方向、单位和顺序 |
6.1.2 地理坐标系(Geographic CRS)
地理坐标系使用经度(Longitude)和纬度(Latitude)来描述地球表面上的位置,单位通常为度(degree)。
特点:
- 坐标以角度表示,范围为经度 -180° 至 180°,纬度 -90° 至 90°
- 不涉及投影变换,直接基于椭球体表面
- 不能直接用于距离、面积等度量计算(1度在不同纬度对应的实际距离不同)
- 常见代表:EPSG:4326(WGS84)、EPSG:4490(CGCS2000)
import geopandas as gpd
from shapely.geometry import Point
# 创建一个地理坐标系的 GeoDataFrame
gdf = gpd.GeoDataFrame(
{"name": ["北京", "上海"]},
geometry=[Point(116.4, 39.9), Point(121.5, 31.2)],
crs="EPSG:4326" # WGS84 地理坐标系
)
print(gdf.crs)
# EPSG:4326
print(gdf.crs.is_geographic)
# True
6.1.3 投影坐标系(Projected CRS)
投影坐标系将地球表面通过某种数学变换(投影)映射到平面上,坐标单位通常为米(meter)。
特点:
- 坐标以线性单位(通常为米)表示
- 经过投影变换,存在一定程度的变形
- 可以直接进行距离、面积等度量计算
- 常见代表:EPSG:3857(Web Mercator)、UTM 各分区
# 将数据转换到投影坐标系
gdf_projected = gdf.to_crs("EPSG:3857")
print(gdf_projected.crs.is_projected)
# True
print(gdf_projected.crs.axis_info)
# 显示坐标轴信息,包括单位为 metre
6.1.4 两者的关键区别
| 特性 | 地理坐标系 | 投影坐标系 |
|---|---|---|
| 坐标单位 | 度(degree) | 米(metre)或其他线性单位 |
| 坐标范围 | 经度 ±180°,纬度 ±90° | 取决于投影方式 |
| 是否涉及投影 | 否 | 是 |
| 能否直接度量 | 不能(度 ≠ 固定距离) | 能(单位一致) |
| 变形 | 无投影变形 | 存在变形(面积/角度/距离) |
| 适用场景 | 数据存储与交换 | 空间分析与制图 |
重要提示: 在进行面积计算、距离度量、缓冲区分析等操作之前,必须先将数据转换到合适的投影坐标系,否则计算结果将没有实际意义。
6.2 CRS 在 GeoPandas 中的表示
6.2.1 pyproj.CRS 对象
GeoPandas 使用 pyproj 库的 CRS 类来表示坐标参考系统。.crs 属性返回的是一个 pyproj.CRS 对象,而不是简单的字符串。
import geopandas as gpd
from pyproj import CRS
# 读取数据
gdf = gpd.read_file("example.shp")
# 查看 CRS 类型
print(type(gdf.crs))
# <class 'pyproj.crs.crs.CRS'>
# CRS 对象包含丰富的属性
crs = gdf.crs
print(crs.name) # 坐标系名称
print(crs.datum) # 基准面
print(crs.ellipsoid) # 椭球体
print(crs.is_geographic) # 是否为地理坐标系
print(crs.is_projected) # 是否为投影坐标系
print(crs.axis_info) # 坐标轴信息
6.2.2 pyproj.CRS 的丰富属性
pyproj.CRS 对象提供了大量有用的属性和方法:
from pyproj import CRS
crs = CRS.from_epsg(4326)
# 基本信息
print(f"名称: {crs.name}")
# 名称: WGS 84
print(f"类型: {'地理坐标系' if crs.is_geographic else '投影坐标系'}")
# 类型: 地理坐标系
# 椭球体参数
ellipsoid = crs.ellipsoid
print(f"椭球体: {ellipsoid.name}")
print(f"长半轴: {ellipsoid.semi_major_metre} 米")
print(f"扁率倒数: {ellipsoid.inverse_flattening}")
# 基准面
datum = crs.datum
print(f"基准面: {datum.name}")
# EPSG 代码
print(f"EPSG 代码: {crs.to_epsg()}")
# 坐标轴信息
for axis in crs.axis_info:
print(f"轴: {axis.name}, 方向: {axis.direction}, 单位: {axis.unit_name}")
输出示例:
名称: WGS 84
类型: 地理坐标系
椭球体: WGS 84
长半轴: 6378137.0 米
扁率倒数: 298.257223563
基准面: World Geodetic System 1984
EPSG 代码: 4326
轴: Geodetic latitude, 方向: north, 单位: degree
轴: Geodetic longitude, 方向: east, 单位: degree
6.2.3 CRS 的不可变性
pyproj.CRS 对象是不可变的(immutable),一旦创建就不能修改。如果需要不同的 CRS,必须创建新的对象:
from pyproj import CRS
crs1 = CRS.from_epsg(4326)
crs2 = CRS.from_epsg(3857)
# crs1 和 crs2 是不同的对象,互不影响
print(crs1 == crs2) # False
6.3 查看与设置 CRS
6.3.1 查看 CRS — .crs 属性
每个 GeoDataFrame 和 GeoSeries 都有一个 .crs 属性:
import geopandas as gpd
# 从文件读取时,CRS 通常自动识别
gdf = gpd.read_file("china_provinces.shp")
print(gdf.crs)
# EPSG:4326
# 如果数据没有 CRS 信息
print(gdf.crs is None)
# 可能为 True,表示 CRS 未定义
6.3.2 设置 CRS — set_crs() 方法
set_crs() 方法用于声明数据的坐标参考系统。它不会改变坐标值,只是告诉 GeoPandas 这些坐标值应该如何理解。
from shapely.geometry import Point
# 创建没有 CRS 的 GeoDataFrame
gdf = gpd.GeoDataFrame(
{"city": ["北京"]},
geometry=[Point(116.4, 39.9)]
)
print(gdf.crs)
# None
# 声明坐标系为 WGS84
gdf = gdf.set_crs("EPSG:4326")
print(gdf.crs)
# EPSG:4326
# 注意:坐标值没有变化!
print(gdf.geometry.iloc[0])
# POINT (116.4 39.9)
6.3.3 set_crs() 的重要参数
# 基本用法:声明 CRS
gdf = gdf.set_crs("EPSG:4326")
# 如果已有 CRS,默认会报错
# gdf = gdf.set_crs("EPSG:3857") # 会抛出 ValueError
# 使用 allow_override=True 强制覆盖已有 CRS
gdf = gdf.set_crs("EPSG:3857", allow_override=True)
# 原地修改(修改原对象,不创建新对象)
gdf.set_crs("EPSG:4326", inplace=True, allow_override=True)
参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
crs |
str/int/CRS | 要设置的坐标参考系统 |
epsg |
int | EPSG 代码(与 crs 参数二选一) |
allow_override |
bool | 是否允许覆盖已有 CRS,默认 False |
inplace |
bool | 是否原地修改,默认 False |
6.3.4 set_crs() 与 to_crs() 的核心区别
这是一个初学者极易混淆的重点:
| 方法 | 功能 | 坐标值是否改变 |
|---|---|---|
set_crs() |
声明/定义 CRS(”这些坐标是什么意思”) | 不改变 |
to_crs() |
转换/重投影(”把坐标转换到另一个系统”) | 改变 |
# 错误示范:用 set_crs() 企图进行坐标转换
# 这只会修改 CRS 标签,但坐标值不变,导致数据位置完全错误!
gdf_wrong = gdf.set_crs("EPSG:3857", allow_override=True)
# 坐标仍然是 (116.4, 39.9),但 GeoPandas 会认为这是 Web Mercator 的坐标
# 正确做法:用 to_crs() 进行坐标转换
gdf_correct = gdf.to_crs("EPSG:3857")
# 坐标被正确转换为 Web Mercator 坐标
6.4 坐标转换 to_crs()
6.4.1 基本用法
to_crs() 方法执行实际的坐标重投影,将几何对象的坐标值从当前 CRS 转换到目标 CRS。
import geopandas as gpd
from shapely.geometry import Point
# 创建 WGS84 坐标数据
cities = gpd.GeoDataFrame(
{
"city": ["北京", "上海", "广州", "成都"],
"population": [2171, 2487, 1868, 2094]
},
geometry=[
Point(116.407, 39.904),
Point(121.474, 31.230),
Point(113.264, 23.129),
Point(104.066, 30.573)
],
crs="EPSG:4326"
)
print("原始坐标(WGS84):")
print(cities.geometry.values)
# 转换到 Web Mercator
cities_mercator = cities.to_crs("EPSG:3857")
print("\n转换后坐标(Web Mercator):")
print(cities_mercator.geometry.values)
输出示例:
原始坐标(WGS84):
[<POINT (116.407 39.904)> <POINT (121.474 31.23)>
<POINT (113.264 23.129)> <POINT (104.066 30.573)>]
转换后坐标(Web Mercator):
[<POINT (12958175.1 4852834.1)> <POINT (13522015.4 3663573.5)>
<POINT (12608062.5 2632018.2)> <POINT (11584945.3 3574480.8)>]
6.4.2 常见的转换场景
WGS84 → Web Mercator(用于 Web 地图展示)
# 适用于 Google Maps、OpenStreetMap 等 Web 地图
gdf_web = gdf.to_crs(epsg=3857)
WGS84 → UTM(用于精确测量)
# 北京位于 UTM 第 50 带北半球
gdf_utm = gdf.to_crs(epsg=32650)
CGCS2000 → WGS84
# CGCS2000 与 WGS84 非常接近,但不完全相同
gdf_wgs84 = gdf_cgcs2000.to_crs(epsg=4326)
使用 Proj 字符串进行自定义投影
# 使用自定义的兰伯特等角圆锥投影(适合中国区域)
proj_string = "+proj=lcc +lat_1=25 +lat_2=47 +lat_0=36 +lon_0=105 +datum=WGS84 +units=m"
gdf_lcc = gdf.to_crs(proj_string)
6.4.3 to_crs() 参数详解
# 使用 EPSG 代码
gdf.to_crs(epsg=4326)
# 使用 CRS 字符串
gdf.to_crs("EPSG:4326")
# 使用 pyproj.CRS 对象
from pyproj import CRS
target_crs = CRS.from_epsg(4326)
gdf.to_crs(target_crs)
# 使用 WKT 字符串
gdf.to_crs(wkt_string)
# 使用 Proj 字符串
gdf.to_crs("+proj=utm +zone=50 +datum=WGS84 +units=m")
6.4.4 转换的注意事项
# 1. 必须先有 CRS 才能转换
gdf_no_crs = gpd.GeoDataFrame(geometry=[Point(0, 0)])
# gdf_no_crs.to_crs("EPSG:3857") # 报错!因为不知道原始 CRS
# 正确做法:先声明再转换
gdf_no_crs = gdf_no_crs.set_crs("EPSG:4326")
gdf_converted = gdf_no_crs.to_crs("EPSG:3857")
# 2. 转换可能引入微小的数值误差
# 这是正常现象,因为涉及复杂的数学变换
# 3. 某些投影会导致特定区域的较大变形
# 例如 Web Mercator 在极地区域变形严重
6.5 常用坐标系统
6.5.1 EPSG:4326 — WGS84
| 属性 | 值 |
|---|---|
| 全称 | World Geodetic System 1984 |
| 类型 | 地理坐标系 |
| 单位 | 度(degree) |
| 适用范围 | 全球 |
| 常见用途 | GPS 定位、数据存储与交换 |
from pyproj import CRS
crs_4326 = CRS.from_epsg(4326)
print(crs_4326.name)
# WGS 84
print(crs_4326.ellipsoid.name)
# WGS 84
print(crs_4326.datum.name)
# World Geodetic System 1984
WGS84 是目前国际上使用最广泛的坐标系统,GPS 卫星导航系统使用的就是 WGS84。它也是 GeoJSON 规范指定的默认坐标系。
6.5.2 EPSG:3857 — Web Mercator
| 属性 | 值 |
|---|---|
| 全称 | WGS 84 / Pseudo-Mercator |
| 类型 | 投影坐标系 |
| 投影方式 | 伪墨卡托(Pseudo-Mercator) |
| 单位 | 米(metre) |
| 适用范围 | 全球(纬度约 ±85°) |
| 常见用途 | Web 地图(Google Maps、百度地图等) |
crs_3857 = CRS.from_epsg(3857)
print(crs_3857.name)
# WGS 84 / Pseudo-Mercator
# Web Mercator 的特点:
# - 保角性好(形状正确)
# - 面积变形严重(尤其在高纬度地区)
# - 不适合用于面积计算
注意: Web Mercator 适合用于显示,但不适合用于面积计算或距离度量,因为它的面积变形非常严重。例如,在该投影下格陵兰岛看起来和非洲差不多大,但实际上非洲面积是格陵兰岛的 14 倍。
6.5.3 EPSG:4490 — CGCS2000
| 属性 | 值 |
|---|---|
| 全称 | China Geodetic Coordinate System 2000 |
| 类型 | 地理坐标系 |
| 单位 | 度(degree) |
| 适用范围 | 中国 |
| 常见用途 | 中国官方测绘、天地图 |
crs_4490 = CRS.from_epsg(4490)
print(crs_4490.name)
# China Geodetic Coordinate System 2000
print(crs_4490.datum.name)
# China 2000
# CGCS2000 与 WGS84 的椭球体参数非常接近
# WGS84 长半轴: 6378137.0 m, 扁率倒数: 298.257223563
# CGCS2000 长半轴: 6378137.0 m, 扁率倒数: 298.257222101
# 差异在毫米级别
6.5.4 UTM 分区
UTM(Universal Transverse Mercator,通用横轴墨卡托投影)将地球按经度分为 60 个带(每带 6°),是精确测量和分析的首选投影。
中国区域常用 UTM 分带:
| UTM 分区 | EPSG 代码 | 经度范围 | 覆盖的主要城市 |
|---|---|---|---|
| 43N | 32643 | 72°E - 78°E | 喀什 |
| 44N | 32644 | 78°E - 84°E | 和田 |
| 45N | 32645 | 84°E - 90°E | 乌鲁木齐 |
| 46N | 32646 | 90°E - 96°E | 西宁 |
| 47N | 32647 | 96°E - 102°E | 昆明、成都 |
| 48N | 32648 | 102°E - 108°E | 西安、重庆 |
| 49N | 32649 | 108°E - 114°E | 武汉、长沙 |
| 50N | 32650 | 114°E - 120°E | 北京、南京 |
| 51N | 32651 | 120°E - 126°E | 上海、杭州 |
| 52N | 32652 | 126°E - 132°E | 哈尔滨 |
# 根据城市位置选择对应的 UTM 分区
# 北京(经度 116.4°)位于第 50 带
gdf_beijing_utm = gdf_beijing.to_crs(epsg=32650)
# 上海(经度 121.5°)位于第 51 带
gdf_shanghai_utm = gdf_shanghai.to_crs(epsg=32651)
6.6 自动估算 UTM
6.6.1 estimate_utm_crs() 方法
GeoPandas 提供了 estimate_utm_crs() 方法,可以根据数据的空间范围自动估算最合适的 UTM 分区:
import geopandas as gpd
from shapely.geometry import Point
# 创建北京区域的数据
gdf = gpd.GeoDataFrame(
{"name": ["天安门", "故宫", "颐和园"]},
geometry=[
Point(116.391, 39.907),
Point(116.397, 39.917),
Point(116.275, 39.999)
],
crs="EPSG:4326"
)
# 自动估算最合适的 UTM CRS
utm_crs = gdf.estimate_utm_crs()
print(utm_crs)
# EPSG:32650(UTM zone 50N)
# 直接用估算的 CRS 进行转换
gdf_utm = gdf.to_crs(utm_crs)
print(gdf_utm.crs)
# EPSG:32650
6.6.2 estimate_utm_crs() 的参数
# 基本用法
utm_crs = gdf.estimate_utm_crs()
# 指定基准面(默认为 WGS 84)
utm_crs_nad83 = gdf.estimate_utm_crs(datum_name="NAD83")
# 该方法通过计算数据的中心经度来确定 UTM 分区
# 公式:zone = floor((中心经度 + 180) / 6) + 1
6.6.3 使用场景
# 场景1:计算面积前自动选择投影
def calculate_area_in_sqm(gdf):
"""计算面积(平方米),自动选择合适的 UTM 投影"""
if gdf.crs.is_geographic:
utm_crs = gdf.estimate_utm_crs()
gdf_projected = gdf.to_crs(utm_crs)
else:
gdf_projected = gdf
return gdf_projected.area
# 场景2:为不同地区的数据分别估算最佳投影
regions = {
"华北": gdf_north,
"华东": gdf_east,
"西南": gdf_southwest
}
for name, region_gdf in regions.items():
utm = region_gdf.estimate_utm_crs()
print(f"{name}区域最佳 UTM: {utm}")
6.7 CRS 格式支持
GeoPandas(通过 pyproj)支持多种 CRS 表示格式,可以灵活地指定坐标系。
6.7.1 EPSG 代码
最常用、最简洁的方式:
# 字符串形式
gdf.set_crs("EPSG:4326")
gdf.to_crs("EPSG:4326")
# 整数形式(通过 epsg 参数)
gdf.set_crs(epsg=4326)
gdf.to_crs(epsg=4326)
# 通过 pyproj.CRS
from pyproj import CRS
crs = CRS.from_epsg(4326)
gdf.set_crs(crs)
6.7.2 WKT 格式(Well-Known Text)
WKT 是 OGC 标准的 CRS 描述格式,信息最为完整:
from pyproj import CRS
crs = CRS.from_epsg(4326)
wkt = crs.to_wkt()
print(wkt)
输出示例(WKT2:2019 格式):
GEOGCRS["WGS 84",
ENSEMBLE["World Geodetic System 1984 ensemble",
MEMBER["World Geodetic System 1984 (Transit)"],
MEMBER["World Geodetic System 1984 (G730)"],
...
ELLIPSOID["WGS 84",6378137,298.257223563,
LENGTHUNIT["metre",1]]],
PRIMEM["Greenwich",0,
ANGLEUNIT["degree",0.0174532925199433]],
CS[ellipsoidal,2],
AXIS["geodetic latitude (Lat)",north,
ORDER[1]],
AXIS["geodetic longitude (Lon)",east,
ORDER[2]],
ANGLEUNIT["degree",0.0174532925199433],
ID["EPSG",4326]]
# 使用 WKT 字符串创建 CRS
crs_from_wkt = CRS.from_wkt(wkt)
# 也可以获取旧版 WKT1 格式
wkt1 = crs.to_wkt(version="WKT1_GDAL")
6.7.3 Proj 字符串
Proj 字符串是 PROJ 库传统使用的参数化格式:
from pyproj import CRS
# 从 Proj 字符串创建 CRS
crs = CRS.from_proj4("+proj=longlat +datum=WGS84 +no_defs")
# 获取 Proj 字符串
proj_str = crs.to_proj4()
print(proj_str)
# +proj=longlat +datum=WGS84 +no_defs +type=crs
# 自定义投影
custom_proj = CRS.from_proj4(
"+proj=tmerc +lat_0=0 +lon_0=117 +k=1 +x_0=500000 +y_0=0 "
"+ellps=GRS80 +units=m +no_defs"
)
注意: Proj 字符串存在信息丢失的问题,不推荐用于精确的 CRS 定义。建议优先使用 EPSG 代码或 WKT 格式。
6.7.4 Authority 字符串
Authority 字符串是一种通用的 CRS 标识方式:
from pyproj import CRS
# EPSG authority
crs = CRS.from_authority("EPSG", 4326)
# OGC authority
crs_ogc = CRS.from_authority("OGC", "CRS84")
# 获取 authority 信息
auth = crs.to_authority()
print(auth)
# ('EPSG', '4326')
6.7.5 CRS84 与 EPSG:4326 的区别
from pyproj import CRS
# EPSG:4326 的轴序是 纬度(Lat), 经度(Lon)
crs_4326 = CRS.from_epsg(4326)
for axis in crs_4326.axis_info:
print(f"{axis.name}: {axis.direction}")
# Geodetic latitude: north
# Geodetic longitude: east
# OGC:CRS84 的轴序是 经度(Lon), 纬度(Lat)
crs84 = CRS.from_authority("OGC", "CRS84")
for axis in crs84.axis_info:
print(f"{axis.name}: {axis.direction}")
# Geodetic longitude: east
# Geodetic latitude: north
# 在实际应用中,GeoPandas 始终使用 (x, y) 即 (经度, 纬度) 的顺序
# 所以在 GeoPandas 中两者的行为基本一致
6.8 CRS 比较与验证
6.8.1 CRS 比较
from pyproj import CRS
crs1 = CRS.from_epsg(4326)
crs2 = CRS.from_proj4("+proj=longlat +datum=WGS84 +no_defs")
crs3 = CRS.from_epsg(3857)
# 直接比较
print(crs1 == crs2) # True(虽然创建方式不同,但本质相同)
print(crs1 == crs3) # False
# 精确比较与宽松比较
print(crs1.equals(crs2)) # True
6.8.2 空间操作中的 CRS 自动检查
GeoPandas 在执行空间操作时会自动检查 CRS 的一致性:
import geopandas as gpd
from shapely.geometry import Point, Polygon
# 创建两个不同 CRS 的 GeoDataFrame
gdf1 = gpd.GeoDataFrame(
geometry=[Point(116.4, 39.9)],
crs="EPSG:4326"
)
gdf2 = gpd.GeoDataFrame(
geometry=[Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])],
crs="EPSG:3857"
)
# 尝试执行空间连接 → 会抛出警告或错误
# gpd.sjoin(gdf1, gdf2)
# UserWarning: CRS mismatch between the CRS of left geometries
# and the CRS of right geometries.
# 正确做法:统一 CRS 后再操作
gdf2_4326 = gdf2.to_crs("EPSG:4326")
result = gpd.sjoin(gdf1, gdf2_4326)
6.8.3 CRS 缺失的情况
# 当 CRS 为 None 时,某些操作会发出警告
gdf_no_crs = gpd.GeoDataFrame(geometry=[Point(0, 0)])
print(gdf_no_crs.crs)
# None
# 与有 CRS 的数据进行操作时会警告
# 建议始终为数据设置正确的 CRS
6.8.4 CRS 验证最佳实践
def validate_crs(gdf, expected_epsg=None):
"""验证 GeoDataFrame 的 CRS"""
if gdf.crs is None:
print("⚠️ 警告:CRS 未定义")
return False
print(f"✓ CRS: {gdf.crs.name}")
print(f" EPSG: {gdf.crs.to_epsg()}")
print(f" 类型: {'地理坐标系' if gdf.crs.is_geographic else '投影坐标系'}")
if expected_epsg and gdf.crs.to_epsg() != expected_epsg:
print(f"⚠️ 期望 EPSG:{expected_epsg},实际为 EPSG:{gdf.crs.to_epsg()}")
return False
return True
# 使用示例
validate_crs(gdf, expected_epsg=4326)
6.9 中国常用坐标系
6.9.1 CGCS2000(中国大地坐标系 2000)
CGCS2000 是中国现行的国家大地坐标系统,自 2008 年 7 月 1 日起正式启用。
| 属性 | 值 |
|---|---|
| EPSG 代码 | 4490(地理坐标系) |
| 椭球体 | CGCS2000 |
| 长半轴 | 6,378,137.0 m |
| 扁率倒数 | 298.257222101 |
| 适用范围 | 中国及周边地区 |
from pyproj import CRS
# CGCS2000 地理坐标系
cgcs2000 = CRS.from_epsg(4490)
print(cgcs2000.name)
# China Geodetic Coordinate System 2000
# CGCS2000 投影坐标系(高斯-克吕格 3° 带)
# 以中央经线 117° 为例
cgcs2000_proj = CRS.from_epsg(4548) # CGCS2000 / 3-degree Gauss-Kruger zone 39
print(cgcs2000_proj.name)
CGCS2000 投影坐标系的 EPSG 代码表(部分):
| 投影类型 | 中央经线 | EPSG 代码 | 覆盖范围 |
|---|---|---|---|
| 3° 带第 25 带 | 75°E | 4513 | 新疆西部 |
| 3° 带第 35 带 | 105°E | 4543 | 四川、云南 |
| 3° 带第 37 带 | 111°E | 4547 | 湖南、湖北 |
| 3° 带第 39 带 | 117°E | 4549 | 山东、江苏 |
| 6° 带第 13 带 | 75°E | 4491 | 新疆西部 |
| 6° 带第 19 带 | 111°E | 4497 | 湖南、湖北 |
| 6° 带第 20 带 | 117°E | 4498 | 山东、江苏 |
6.9.2 北京 54 坐标系
北京 54 坐标系是中国过去长期使用的坐标系,基于前苏联 1942 年普尔科沃坐标系。
| 属性 | 值 |
|---|---|
| 椭球体 | Krassowsky 1940 |
| 长半轴 | 6,378,245.0 m |
| 扁率倒数 | 298.3 |
| 状态 | 已停用,被 CGCS2000 替代 |
# 北京 54 地理坐标系
# 注意:pyproj 中的 EPSG 代码可能不完整
# 可能需要使用 WKT 或 Proj 字符串定义
# 使用 Proj 字符串
bj54_proj = "+proj=longlat +ellps=krass +no_defs"
注意: 北京 54 坐标系已于 2008 年停用,新数据不应使用该坐标系。如果需要处理历史数据,可能需要使用七参数转换模型。
6.9.3 西安 80 坐标系
西安 80 坐标系是 1980 年建立的中国大地坐标系,使用 IAG 1975 椭球体。
| 属性 | 值 |
|---|---|
| EPSG 代码 | 4610 |
| 椭球体 | IAG 1975 |
| 长半轴 | 6,378,140.0 m |
| 扁率倒数 | 298.257 |
| 状态 | 已停用,被 CGCS2000 替代 |
from pyproj import CRS
# 西安 80 地理坐标系
xian80 = CRS.from_epsg(4610)
print(xian80.name)
# Xian 1980
6.9.4 高斯-克吕格投影
高斯-克吕格投影(Gauss-Kruger projection)是中国测绘中广泛使用的投影方式,类似于 UTM 投影但略有不同。
高斯-克吕格与 UTM 的对比:
| 特性 | 高斯-克吕格 | UTM |
|---|---|---|
| 分带方式 | 6° 或 3° 带 | 6° 带 |
| 中央经线比例因子 | 1.0 | 0.9996 |
| 东偏移 | 500000 m(加带号前缀) | 500000 m |
| 中国使用 | 广泛 | 国际数据交换 |
from pyproj import CRS
# CGCS2000 高斯-克吕格 6° 带第 20 带(中央经线 117°)
gk_6degree = CRS.from_epsg(4498)
# CGCS2000 高斯-克吕格 3° 带第 39 带(中央经线 117°)
gk_3degree = CRS.from_epsg(4549)
# 如果 EPSG 代码不够用,可以自定义
custom_gk = CRS.from_proj4(
"+proj=tmerc +lat_0=0 +lon_0=117 +k=1.0 "
"+x_0=39500000 +y_0=0 +ellps=GRS80 +units=m +no_defs"
)
6.9.5 中国坐标系之间的转换
import geopandas as gpd
from shapely.geometry import Point
# 创建 CGCS2000 数据
gdf_cgcs = gpd.GeoDataFrame(
{"name": ["测试点"]},
geometry=[Point(116.4, 39.9)],
crs="EPSG:4490"
)
# CGCS2000 → WGS84
# 由于两个椭球体参数非常接近,差异在毫米级别
gdf_wgs84 = gdf_cgcs.to_crs("EPSG:4326")
# CGCS2000 地理坐标 → CGCS2000 投影坐标
gdf_proj = gdf_cgcs.to_crs("EPSG:4549") # 3° 带 39 带
# 西安 80 → CGCS2000
# 需要注意:这种转换可能需要地方性的转换参数
gdf_xian80 = gpd.GeoDataFrame(
geometry=[Point(116.4, 39.9)],
crs="EPSG:4610"
)
gdf_to_cgcs = gdf_xian80.to_crs("EPSG:4490")
重要提示: 北京 54、西安 80 与 CGCS2000/WGS84 之间的精确转换通常需要地方性的七参数(或三参数)转换模型。这些参数在不同地区不同,通常由当地测绘部门提供。通用的转换可能存在数米甚至更大的误差。
6.10 常见问题与最佳实践
6.10.1 问题:面积计算结果不正确
import geopandas as gpd
# ❌ 错误:直接在地理坐标系下计算面积
gdf = gpd.read_file("polygons.shp") # CRS: EPSG:4326
area_wrong = gdf.area # 结果单位是"平方度",没有实际意义!
# ✅ 正确:先转换到投影坐标系再计算
utm_crs = gdf.estimate_utm_crs()
gdf_utm = gdf.to_crs(utm_crs)
area_correct = gdf_utm.area # 结果单位是平方米
print(f"面积: {area_correct.iloc[0]:.2f} 平方米")
6.10.2 问题:两个数据集叠加不对齐
# 常见原因:两个数据集的 CRS 不一致
gdf1 = gpd.read_file("data1.shp") # EPSG:4326
gdf2 = gpd.read_file("data2.shp") # EPSG:4490
# 检查 CRS
print(f"数据集1: {gdf1.crs}")
print(f"数据集2: {gdf2.crs}")
# 统一 CRS
gdf2 = gdf2.to_crs(gdf1.crs)
6.10.3 问题:坐标值看起来不对
# 可能原因1:使用了错误的 CRS 标签
# 例如数据实际是 CGCS2000,但标记为 WGS84
# 检查方法:查看坐标值是否在合理范围内
# 可能原因2:投影坐标被误标为地理坐标
# 例如坐标值为 (39500116, 4430000),这明显是投影坐标而非经纬度
# 可能原因3:经纬度顺序颠倒
# 标准经纬度:经度 73°-135°,纬度 18°-54°(中国范围)
6.10.4 最佳实践
- 始终检查 CRS
# 读取数据后第一步就检查 CRS
gdf = gpd.read_file("data.shp")
print(f"CRS: {gdf.crs}")
print(f"EPSG: {gdf.crs.to_epsg() if gdf.crs else 'None'}")
- 数据存储使用地理坐标系,分析使用投影坐标系
# 存储和交换数据时使用 WGS84 或 CGCS2000
gdf.to_crs("EPSG:4326").to_file("output.geojson", driver="GeoJSON")
# 进行空间分析时转换到投影坐标系
gdf_analysis = gdf.to_crs(gdf.estimate_utm_crs())
- 多数据集操作前统一 CRS
# 定义统一的 CRS
TARGET_CRS = "EPSG:4326"
# 所有数据集统一转换
datasets = [gdf1, gdf2, gdf3]
datasets_unified = [ds.to_crs(TARGET_CRS) for ds in datasets]
- 使用 EPSG 代码而非 Proj 字符串
# ✅ 推荐
gdf.to_crs("EPSG:4326")
# ⚠️ 不推荐(可能丢失信息)
gdf.to_crs("+proj=longlat +datum=WGS84 +no_defs")
- 了解你的投影的适用范围
from pyproj import CRS
crs = CRS.from_epsg(32650)
area_of_use = crs.area_of_use
print(f"适用范围: {area_of_use.name}")
print(f"经度: {area_of_use.west}° - {area_of_use.east}°")
print(f"纬度: {area_of_use.south}° - {area_of_use.north}°")
6.11 本章小结
本章详细介绍了坐标参考系统(CRS)在 GeoPandas 中的应用,涵盖了以下核心内容:
| 主题 | 关键要点 |
|---|---|
| CRS 概念 | 地理坐标系使用经纬度(度),投影坐标系使用平面坐标(米) |
| CRS 表示 | GeoPandas 使用 pyproj.CRS 对象,支持 EPSG、WKT、Proj 等格式 |
| set_crs() | 声明/定义 CRS,不改变坐标值 |
| to_crs() | 执行坐标重投影,改变坐标值 |
| 常用坐标系 | EPSG:4326(WGS84)、EPSG:3857(Web Mercator)、EPSG:4490(CGCS2000) |
| UTM 估算 | estimate_utm_crs() 自动选择最合适的 UTM 分区 |
| CRS 验证 | GeoPandas 自动检查 CRS 一致性,不匹配时发出警告 |
| 中国坐标系 | CGCS2000 是现行标准,北京 54 和西安 80 已停用 |
| 最佳实践 | 存储用地理坐标系,分析用投影坐标系;始终检查并统一 CRS |
核心要诀:
- 度量分析前,务必转换到投影坐标系
set_crs()是”贴标签”,to_crs()是”做转换”- 中国区域优先考虑 CGCS2000 系列坐标系
- 多数据集操作前,必须统一 CRS
下一章我们将学习 GeoPandas 的数据读写功能,了解如何读取和输出各种矢量文件格式。