#| echo: false
import pandas as pd
import matplotlib.pyplot as plt
pd.options.display.max_rows = 6
pd.options.display.max_columns = 6
pd.options.display.max_colwidth = 35
plt.rcParams['figure.figsize'] = (5, 5)
import matplotlib.pyplot as plt
import numpy as np
import shapely.geometry
import geopandas as gpd
import rasterio
import rasterio.warp
from rasterio.plot import show
import pyproj
# 出现错误CRSError: Invalid projection: epsg:4326: 的解决方案
# 因为装了多个版本的pyproj
# 在anaconda的目录下搜一下其他的proj.db文件的路径
# 复制路径在所装的环境文件夹下的Library文件夹下的那个路径,使用pyproj.datadir.set_data_dir()来更新目录
import pyproj
# pyproj.datadir.get_data_dir()
path = 'D:\work\miniconda3\Library\share\proj'
pyproj.datadir.set_data_dir(path)
src_srtm = rasterio.open('data/srtm.tif')
src_nlcd = rasterio.open('data/nlcd.tif')
zion = gpd.read_file('data/zion.gpkg')
world = gpd.read_file('data/world.gpkg')
cycle_hire_osm = gpd.read_file('data/cycle_hire_osm.gpkg')
@sec-coordinate-reference-systems 引入了坐标参考系统(CRS),重点关注两种主要类型:地理(“经度/纬度”,单位为经度和纬度)和投影(通常以米为单位) 基准)坐标系。 本章以这些知识为基础并进行了更深入的探讨。 它演示了如何设置地理数据并将其从一种 CRS 转换为另一种 CRS,此外,还强调了您应该注意的由于忽略 CRS 而可能出现的特定问题,特别是当您的数据使用经度/纬度坐标存储时。
在许多项目中,无需担心不同的 CRS,更不用说在不同的 CRS 之间进行转换。 了解您的数据是否位于投影坐标系或地理坐标系中以及其对几何操作的影响非常重要。 但是,如果您了解数据的 CRS 以及几何操作的后果(在下一节中介绍),CRS 应该在幕后工作:当出现问题时,人们通常会突然需要了解 CRS。 拥有一个包含所有项目数据的明确定义的项目 CRS,再加上了解如何以及为何使用不同的 CRS,可以确保不会出错。 此外,学习坐标系将加深您对地理数据集以及如何有效使用它们的了解。
本章讲述 CRS 的基础知识,演示使用不同 CRS 的后果(包括可能出错的地方),以及如何将数据集从一个坐标系“重新投影”到另一个坐标系。 在下一节中,我们将介绍 Python 中的 CRS,然后介绍 @sec-querying-and-setting-coordinate-systems,它展示了如何获取和设置与空间对象关联的 CRS。 @sec-geometry-operations-on-projected-and-unprojected-data 部分参考创建缓冲区的工作示例演示了了解数据所在 CRS 的重要性。 我们分别在@sec-when-to-reproject 节和@sec-which-crs-to-use 节中解决何时重新投影以及使用哪个CRS 的问题。 我们在 @sec-reprojecting-vector-geometries 和 @sec-reprojecting-raster-geometries 部分介绍了重新投影矢量和栅格对象,并在 @sec-custom-map-projections 中介绍了修改地图投影。
大多数需要 CRS 转换的现代地理工具,包括 Python 包和 QGIS 等桌面 GIS 软件,都与 PROJ 接口,这是一个开源 C++ 库,“从一个坐标参考系统转换坐标” (CRS) 到另一个”。 CRS 可以通过多种方式进行描述,包括以下几种。
每个都指的是同一件事:构成全球定位系统 (GPS) 坐标和许多其他数据集基础的“WGS84”坐标系。 但哪一个是正确的呢?
简而言之,识别 CRS 的第三种方法是正确的:本书中介绍的“geopandas”和“rasterio”包以及许多其他用于处理地理数据的软件项目(包括 [QGIS]可以理解“EPSG:4326”) “EPSG:4326”是面向未来的。 此外,虽然它是机器可读的,但与 proj 字符串表示形式“EPSG:4326”不同,它很短、易于记住并且在线高度“可找到”。 “geopandas”和“rasterio”也可以理解更简洁的标识符“4326”,但我们建议使用更明确的“AUTHORITY:CODE”表示形式,以防止歧义并提供上下文。
更长的答案是,这三个描述都不够充分,需要更多细节来明确 CRS 处理和转换:由于 CRS 的复杂性,不可能在如此短的文本字符串中捕获有关它们的所有相关信息。 为此,开放地理空间联盟(OGC,还开发了 sf 包实现的简单功能规范)开发了一种用于描述 CRS 的开放标准格式,称为 WKT(众所周知的文本)。 对此进行了详细说明,该文档“定义了用于坐标参考的抽象模型的文本字符串实现的结构和内容 ISO 19111:2019 中描述的系统”(开放地理空间联盟 2019…添加引用!)。 WGS84 CRS 的 WKT 表示形式(标识符为“EPSG:4326”)如下:
crs = pyproj.CRS.from_string('EPSG:4326') # or '.from_epsg(4326)'
print(crs.to_wkt(pretty=True))
GEOGCRS["WGS 84",
DATUM["World Geodetic System 1984",
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],
ANGLEUNIT["degree",0.0174532925199433]],
AXIS["geodetic longitude (Lon)",east,
ORDER[2],
ANGLEUNIT["degree",0.0174532925199433]],
USAGE[
SCOPE["unknown"],
AREA["World"],
BBOX[-90,-180,90,180]],
ID["EPSG",4326]]
命令的输出显示了 CRS 标识符(也称为空间参考标识符或 SRID)的工作原理:它只是一个外观 - 上,提供与 CRS 的更完整的 WKT 表示相关联的唯一标识符。 这就提出了一个问题:如果标识符与 CRS 的较长 WKT 表示形式不匹配,会发生什么情况? 在这一点上,开放地理空间联盟(2019…添加引用!)很清楚,详细的 WKT 表示优先于 标识符
如果引用的标识符中给出的任何属性或值与 WKT 描述中明确给出的属性或值相冲突,则以 WKT 值为准。
以 AUTHORITY:CODE 形式引用 CRS 标识符的惯例(也被用其他语言编写的地理软件使用)允许引用广泛的正式定义的坐标系。 26 CRS 标识符中最常用的权威是 EPSG,欧洲石油调查组的缩写,该组织发布了标准化的 CRS 列表(EPSG 由石油和天然气机构接管 国际石油和天然气生产商协会地理信息委员会(……添加引文!),2005 年)。 CRS 标识符中可以使用其他权限。 例如,ESRI:54030 指的是 ESRI 的 Robinson 投影实现,它具有以下 WKT 字符串:
crs = pyproj.CRS.from_string('ESRI:54030')
print(crs.to_wkt(pretty=True))
PROJCRS["World_Robinson",
BASEGEOGCRS["WGS 84",
DATUM["World Geodetic System 1984",
ELLIPSOID["WGS 84",6378137,298.257223563,
LENGTHUNIT["metre",1]]],
PRIMEM["Greenwich",0,
ANGLEUNIT["Degree",0.0174532925199433]]],
CONVERSION["World_Robinson",
METHOD["Robinson"],
PARAMETER["Longitude of natural origin",0,
ANGLEUNIT["Degree",0.0174532925199433],
ID["EPSG",8802]],
PARAMETER["False easting",0,
LENGTHUNIT["metre",1],
ID["EPSG",8806]],
PARAMETER["False northing",0,
LENGTHUNIT["metre",1],
ID["EPSG",8807]]],
CS[Cartesian,2],
AXIS["(E)",east,
ORDER[1],
LENGTHUNIT["metre",1]],
AXIS["(N)",north,
ORDER[2],
LENGTHUNIT["metre",1]],
USAGE[
SCOPE["unknown"],
AREA["World"],
BBOX[-90,-180,90,180]],
ID["ESRI",54030]]
WKT 字符串详尽、详细且精确,允许明确的 CRS 存储和转换。 它们包含任何给定 CRS 的所有相关信息,包括其基准面和椭球体、本初子午线、投影和单位。
最近的 PROJ 版本 (6+) 仍然允许使用 proj-string 来定义坐标操作,但一些 proj-string 键(+nadgrids
、+towgs84
、+k
、+init=epsg:
) 要么不再支持,要么不鼓励。 此外,只能在 proj-string 中直接设置三个数据(即 WGS84、NAD83 和 NAD27)。 有关 CRS 定义和 PROJ 库的演变的详细说明,请参阅 Bivand (2021)、Pebesma 和 Bivand (2022) 的第 2 章以及 Floris Vanderhaeghe 的博客文章(…添加引用!)。 正如 PROJ 文档 中所述,WKT CRS 格式有不同版本,包括 WKT1 和 WKT2 的两个变体,后者 (WKT2 ,2018 规范)对应于 ISO 19111:2019(开放地理空间联盟 2019…添加引用!)。
让我们看看 CRS 如何存储在 Python 空间对象中以及如何查询和设置它们。 首先,我们将了解在矢量地理数据对象中获取和设置 CRS。 考虑名为“world”的“GeoDataFrame”对象,它是从文件“world.gpkg”导入的。 对象“world”代表全世界的国家。 可以使用“.crs”属性检索其 CRS:
world.crs
Name: WGS 84
Axis Info [ellipsoidal]:
- Lat[north]: Geodetic latitude (degree)
- Lon[east]: Geodetic longitude (degree)
Area of Use:
- name: World
- bounds: (-180.0, -90.0, 180.0, 90.0)
Datum: World Geodetic System 1984
- Ellipsoid: WGS 84
- Prime Meridian: Greenwich
输出指定以下信息:
WGS 84
)World
)和边界框((-180.0, -90.0, 180.0, 90.0)
)WGS 84
)WKT 表示形式在将对象保存到文件或执行任何坐标操作时在内部使用,可以使用“.crs.to_wkt()”进行提取,如上所示 (@sec-coordinate-reference-systems)。 在上面,我们可以看到“world”对象具有 WGS84 椭球体,使用格林威治本初子午线以及纬度和经度轴顺序。 我们还有适合使用此 CRS 的区域规范,以及 CRS 标识符:“EPSG:4326”。
CRS 规范对象(例如“world.crs”)具有其他几个有用的属性和方法来检索有关所用 CRS 的附加信息。 例如,尝试运行:
world.crs.is_geographic
用于检查 CRS 是否是地理的world.crs.axis_info[0].unit_name
和 world.crs.axis_info[1].unit_name
用于找出两个轴的 CRS 单位(通常具有相同的单位)world.crs.to_authority()
提取权限(例如,EPSG
)和标识符(例如,4326
)world.crs.to_proj4()
返回 proj 字符串表示形式如果坐标参考系 (CRS) 丢失或设置了错误的 CRS,可以在“GeoSeries”或“GeoDataFrame”上使用“.set_crs”方法来设置它。 可以使用 EPSG 代码作为第一个参数来指定 CRS。 如果对象已经有不同的 CRS 定义,我们还必须指定“allow_override=True”来替换它(否则我们会收到错误)。 例如,这里我们设置了“EPSG:4326”CRS,这没有任何效果,因为“world”已经具有确切的 CRS 定义:
world2 = world.set_crs(4326)
在这里,我们将现有的“EPSG:4326”定义替换为新定义“EPSG:3857”:
world3 = world.set_crs(3857, allow_override=True)
数字被解释为“EPSG”代码。 我们还可以使用字符串,如“EPSG:4326”,这对于使代码更加清晰以及使用其他权限时很有用:
world4 = world.set_crs('ESRI:54009', allow_override=True