znlgis 博客

GIS开发与技术分享

第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 属性

每个 GeoDataFrameGeoSeries 都有一个 .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 最佳实践

  1. 始终检查 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'}")
  1. 数据存储使用地理坐标系,分析使用投影坐标系
# 存储和交换数据时使用 WGS84 或 CGCS2000
gdf.to_crs("EPSG:4326").to_file("output.geojson", driver="GeoJSON")

# 进行空间分析时转换到投影坐标系
gdf_analysis = gdf.to_crs(gdf.estimate_utm_crs())
  1. 多数据集操作前统一 CRS
# 定义统一的 CRS
TARGET_CRS = "EPSG:4326"

# 所有数据集统一转换
datasets = [gdf1, gdf2, gdf3]
datasets_unified = [ds.to_crs(TARGET_CRS) for ds in datasets]
  1. 使用 EPSG 代码而非 Proj 字符串
# ✅ 推荐
gdf.to_crs("EPSG:4326")

# ⚠️ 不推荐(可能丢失信息)
gdf.to_crs("+proj=longlat +datum=WGS84 +no_defs")
  1. 了解你的投影的适用范围
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 的数据读写功能,了解如何读取和输出各种矢量文件格式。