"Have You Never Been Mellow" 是已故英国女爵 Olivia Newton-John 的一首歌曲,收录于她的同名专辑《Have You Never Been Mellow》中。这首歌曲于 1975 年 1 月 21 日发行。而这首歌是德国制作人 Keanu Silva 在 2020 年于 Hexagon 厂牌发布的 Future House 重制作品。
前言
昨天有同学来问问题,问题很有趣,也就正如标题所说的,如何将 WGS84 坐标系矢量图转换为国测局 02 坐标系矢量图。Jim 虽然之前没做过,但是和那个同学讨论了一番,半个小时就给搞定了 hhh。本文就来讲讲这个问题的解决思路。
解决逻辑
我们知道在 shapefile 矢量图中,子图形都是以 WKT(Well-Known Text)格式存储的,其可以表示如下几种几何对象:
几何对象 | WKT 例子 |
---|---|
点 | POINT(0 0) |
线 | LINESTRING(0 0,1 1,2 2) |
多边形 | POLYGON((0 0,0 1,1 1,1 0,0 0)) |
其中,WKT 文本中每一个数对都是一个点,每个点都是一个二维坐标,坐标的顺序是 x,y。
为了简化问题,我们假设我们的矢量图中只有一个多边形,那么我们的问题就变成了如何将 WGS84 坐标系的多边形顶点坐标转换为国测局 02 坐标系的多边形顶点坐标。所以问题就变成了,把一个 WGS84 坐标系下的多边形拆成多个顶点,将他们的坐标进行转换,再把转换后的顶点连起来。这就是我们解决这个问题的思路。
第一步:获取多边形顶点坐标
我们知道,一个多边形的顶点坐标是以 WKT 格式存储的,所以我们只需要读取 WKT 文本,然后解析出多边形的顶点坐标即可。这里我们使用 Python 的 Geopandas 库来读取 shapefile 文件,使用 Shapely 库来解析 WKT 文本。
我们这里以一个中国分省地图举例子,名字就叫 china_province.shp
:
from shapely.geometry import Polygon
import geopandas as gpd
import numpy as np
gdf = gpd.read_file('china_province.shp')
# 以第 0 行为例,第 0 行是黑龙江省
lngs, lats = gdf.iloc[0, :].geometry.exterior.coords.xy
coordinates = np.vstack([lngs, lats]).T
这样,我们就可以得到一个多边形的顶点坐标了,我们来看看输出:
array([[121.48844147, 53.33264923],
[121.49954224, 53.33600616],
[121.51840973, 53.33919144],
...,
[121.50035095, 53.31388855],
[121.49738312, 53.32104492],
[121.48844147, 53.33264923]])
可以发现,每一行都是 [longitude, latitude]
的数对,并且最后一行和第一行是重复的,这是因为多边形的最后一个顶点和第一个顶点是重合的。
第二步:坐标转换
为了方便,这里我们直接使用懒人高德地图 API 进行坐标转换,这里我们使用 requests 库来进行网络请求:
import requests
import os
def translate_cord(lng: float, lon: float) -> (float, float):
# GPS坐标转换为高德坐标
url = 'https://restapi.amap.com/v3/assistant/coordinate/convert'
params = {
'key': os.getenv('AMAP_KEY'), # 这里需要你自己申请一个高德地图的 API key,这里写到了环境变量里
'locations': f'{lng},{lon}',
'coordsys': 'gps'
}
res = requests.get(url, params=params)
lng, lon = res.json()['locations'].split(',')
return lng, lon
这样,我们就可以将 WGS84 坐标系下的经纬度坐标转换为国测局 02 坐标系下的经纬度坐标了。另外,高德地图的用量限制是免费版 5000 次/天,如果用量大得考虑送钱了。
第三步:将转换后的坐标连起来
昨天看到一个很有帮助的文章:Create Polygon from Points,看起来是对一个列表或者 numpy.ndarray
套上一个 Polygon()
就可以了,我们也来试试:
from shapely.geometry import Polygon
l_translated = []
for lng, lat in tqdm(coordinates):
lng, lat = translate_cord(lng, lat)
l_translated.append([lng, lat])
translated = np.array(l_translated)
heilongjiang = Polygon(translated)
这样,变量 heilongjiang
就是一个 Polygon
对象了,然后我们把它存储成一个 shapefile 文件:
gdf_hlj = gpd.GeoDataFrame({'geometry': [heilongjiang]})
gdf_hlj.to_file('heilongjiang.shp')
最后:效果
我们来到 QGIS 里面看看效果(投影:EPSG 4500: CGCS2000 / Gauss-Kruger zone 22):
乍一看还差不多,我们拉得近一点:
在五常市这里,我们就能看到明显的偏移了。所以在 1km 级分辨率情况下,我们这个工作就很有必要进行了。
总结
本文的工作看似很简单,但其实是一个开创性的工作(至少 Jim 和同学都没有找到网上现成的基于国测局 02 坐标的中国地图),希望大家能用得上。