如何处理地图投影转换

最近学习地理信息可视化总是遇到投影的麻烦,包括前段时间输出两篇关于simple features的分享中,其中没有特别处理投影的问题,老司机一看就能看出其中存在的投影问题。

空间数据可视化笔记——simple features空间对象基础

空间数据可视化与simple future模型应用

于是花时间详细研究了下关于投影究竟是怎么一回事,没想到还挺复杂,这里输出一篇阶段性学习心得。

之前在学习ggplot2中的geom_polygon()图层制作地图图形时,从来没有苦恼过投影的问题,因为coord_map()中直接给出投影转换的参数,如果要制作基于国家的地图,直接赋值为polyconic既可得到常见的多圆锥投影视角图形,如果想要做平面视角的世界地图,直接使用默认的coord_map()内默认参数即可(默认的投影参数是mercator【墨卡托投影】),如果想要获取三维椭球体投影的世界地图,直接赋值为ortho即可。

但是使用geom_polygon()制作地图成本非常高,因为geom_polygon不直接支持GIS的数据模型(如sp、sf等)。需要花大把的时间导入这些数据模型,并从模型中抽取出geom_polygon所支持的点、线、多边形数据,才能按照ggplot2所规范的可视化语法进行制图。

R语言中支持GIS数据模型的包一共有两个:sp包和sf包,在旧版的ggplot2中,geom_polygon高度依赖从sp导入的数据对象(虽然也可以从sf中获取)。但是这种情况马上会随着sf包的逐步完善以及ggplot2和sf包的进一步融合而大有改观。

最新版的ggplot2(github上面的开发版)已经内置了geom_sf()图层。它的最大优势是我们直接导入的数据模型不需要做清洗转换了(因为geom_sf函数可以自动识别),不需要声明经纬度和group了,仅需指定我们想要自定义美学映射即可,其他的都交给geom_sf处理吧。

geom_sf理论上完全可以替代geom_polygon,而且性能更好,速度更快。但是有一点需要注意,使用sf模型需要我们熟悉一点关于投影相关的知识,需要能够自由灵活的转换各种投影,否则你很难做出来完美的图。

之前的文章中没有特别探究投影问题,当时做的案例图是这个样子的,很明显与常见的纸质中国地图有很大差别。

之前使用simple模型练习的图表

常见的多圆锥视角中国地图投影

投影问题涉及到两个关键环节:地理坐标和投影坐标的转换。

因为地图是一个不规则的椭球体,所以地理坐标系会应为观察地球的视角不同的多种多样,首先一个规范的地理坐标系是定义在一个特征椭球模型上的经纬度点,不同视角的椭球模型构成不同的地理坐标系,即在不同的视角地理坐标系下,地球上同一个地点的经纬度可能不一样。

一个地理坐标系想要展现在平面坐标系上,需要通过特征投影算法进行投影变化,地理坐标系通过投影算法变换后即构成投影坐标系。

如果你拿到的地图素材本身结构很完整,那么投影问题本身问题不大,万一原始素材中缺少投影信息(如shp文件中缺少.prj文件),要么需要构建一个投影文件,要么需要手动为其制定一个合适的投影坐标系。

library("rpostgis")
library("RPostgreSQL")
library("sf")
library("ggplot2")
library("magrittr")
library("rgdal")
library("dplyr")

conn <- dbConnect(PostgreSQL(),dbname='mytest',host='localhost',port='5432',user='postgres',password='708965')  #建立链接
postgresqlpqExec(conn, "SET client_encoding = 'gbk'")                                                           #设定编码(多字节字符串)
my_spdf <- pgGetGeom(conn, name=c("public","bou2_4p"), geom = "geom") %>% st_as_sf()  #读入方法1
st_crs(my_spdf)
Coordinate Reference System: NA
#使用st_crs函数来查看导入的sf对象是否含有投影信息。

my_spdf <- st_set_crs(my_spdf,4326)
my_spdf <- st_transform(my_spdf, "+init=epsg:4508")
ggplot() + geom_sf(data =my_spdf)

由于投影后的投影坐标系已经被投影算法转换,所以在使用geom_text等图层函数时,务必要使用与几何对象投影一致的经纬度点,这里使用sf中的点中心计算函数最为快捷。

为每个省份添加数据标签的方法是使用sf提供的st_centroid函数,它可以根据每一个feature求出地理中心点。

new_data <- my_spdf %>%
  group_by(name) %>% 
  summarize() %>% 
  st_centroid() %>% 
  bind_cols(as_data_frame(st_coordinates(.)))
ggplot() + 
   geom_sf(data =my_spdf,fill = 'grey95') +
   coord_sf(ndiscr = 0) + #其中ndiscr = 0用于控制不显示子午线。
   geom_text(data = new_data,aes(x=X,y=Y,label = name)) +
   theme_void()

这便是sf包中核心的投影转换过程。投影函数涉及三个:

st_crs()
st_set_crs()
st_transform()

st_crs()用于显示数据模型内包含的投影信息(没有则显示NA)。 st_crs() <- 则用于给没有默认投影的数据模型添加投影参数,其更加pipline的写法可写为model <- st_set_crs(model,string)。 st_transform()函数专门用户坐标参考系统的转换。

sf包中的投影参数一共有两种写法,一种是使用其EPSG代码(或称之为WKID或者SRID)。 另一种写法是写成proj4string的字符串格式:

典型的格式如下,在st_crs()或者st_transform()函数内部赋值任意一种格式即可,效果等同。

Coordinate Reference System:
  EPSG: 4508 
  proj4string: "+proj=tmerc +lat_0=0 +lon_0=111 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs"

美国地图大致需要这么几步该完成投影工作:

Americ_map <- st_read("D:/R/rstudy/Country/USA_map/states.shp") 
Americ_map <- st_set_crs(Americ_map,4236) 
Americ_map <- sf::st_transform(Americ_map,"+proj=laea +y_0=0 +lon_0=-120 +lat_0=35 +ellps=WGS84 +no_defs")
ggplot() + geom_sf(data = Americ_map)

世界地图如果需要得到椭球三维投影,需要惊醒如下几步:

World_region <- st_read("D:/R/rstudy/wold_map/World_region.shp") 
world2 <- sf::st_transform(World_region,"+proj=laea +y_0=0 +lon_0=120 +lat_0=30 +ellps=WGS84 +no_defs")
ggplot() + geom_sf(data = world2)

在使用sf模型时,导入素材通常要检查模型是否包含默认投影,如果有则可以直接进行转换,没有则最好先转化为WGS84(4236),然后再往其他投影坐标系进行转换。

在Python中投影的问题同样需要手动处理:

from shapely.geometry import Point,LineString
import geopandas as gpd
from matplotlib.colors import LinearSegmentedColormap
from matplotlib import pyplot as plt
import pandas as pd
import numpy  as np
import string

China_map = gpd.GeoDataFrame.from_file("D:/R/mapdata/State/china.geojson", encoding = 'gb18030')

China_map.crs
{'init': 'epsg:4326'}

当前的坐标参考系统是 krassovsky投影 - 克拉索夫斯基椭球体 是北京54坐标系所采用的地理坐标系。

China_map = China_map.to_crs(from_epsg(4508))

转换为国家2000坐标系,EPSG:4508 CGCS2000 / Gauss-Kruger CM 111E

China_map.crs
{'init': 'epsg:4508', 'no_defs': True}

经过以上过程之后,我们获取了CGCS2000中国地图投影效果,这也是大多数纸质出版物地图的国家标准。

mydata = pd.DataFrame({
  "id":range(1,35),  "name":China_map["name"],
  "scale":np.random.randint(100,200,34),
  "Scope":list(string.ascii_uppercase[:5])*6 + list(string.ascii_uppercase[:4])
},columns = ["id","name","scale","Scope"])

final_namdata = China_map.set_index('id').join(mydata.loc[:,["id","scale","Scope"]].set_index('id'))
final_namdata.plot(column='scale',cmap=plt.get_cmap("BrBG"),figsize=(20,10), legend = True);
plt.gca().xaxis.set_major_locator(plt.NullLocator())
plt.gca().yaxis.set_major_locator(plt.NullLocator())
plt.legend(labels = ['a', 'b'], loc = 'best')

投影转换本来技术上不复杂(因为有现成的轮子可用),但是还是需要平时积累一些常用的投影类型及其对应的EPSG代码,这样用的时候才能得心应手,否则看着简单,可能一开始就错了。

关于地理坐标系(GCS)投影坐标系(PCS)和坐标参考系统(CRS)以及EPSG、WKID、SRID这些概念简称,都应该有一个大概了解(知道是指代的什么),否则即便在stockoverflow找到帖子也是一脸懵逼。

这些概念在百度、google、CSDN上面可以很容易找到专业解答。

https://developers.arcgis.com/javascript/3/jshelp/gcs.htm

https://developers.arcgis.com/javascript/3/jshelp/pcs.htm

https://www.cnblogs.com/liweis/p/5951032.html

原文发布于微信公众号 - 数据小魔方(datamofang)

原文发表时间:2018-07-29

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏木子昭的博客

白话版 动态规划法

关于动态规划法的解释, 大多都是基于背包问题的, 但背包问题背负了很多算法的解释工作,经常让初学者混淆,刚刚刷leetcode的时候,发现了一个很不错的关于动...

384110
来自专栏机器学习算法工程师

LightGBM大战XGBoost,谁将夺得桂冠?

如果你是一个机器学习社区的活跃成员,你一定知道 **提升机器**(Boosting Machine)以及它们的能力。提升机器从AdaBoost发展到目前最流行的...

37330
来自专栏鸿的学习笔记

写给开发者的机器学习指南(七)

Classifying email as spam or ham (NaiveBayes)

13110
来自专栏机器之心

仅需15分钟,使用OpenCV+Keras轻松破解验证码

45090
来自专栏杨熹的专栏

使聊天机器人具有个性

本文结构: 模型效果 模型的三个模块 模块细节 ---- 今天的论文是 《Assigning Personality/Identity to a Chattin...

35880
来自专栏机器学习算法工程师

LightGBM大战XGBoost,谁将夺得桂冠?

如果你是一个机器学习社区的活跃成员,你一定知道 **提升机器**(Boosting Machine)以及它们的能力。提升机器从AdaBoost发展到目前最流行的...

19530
来自专栏游戏开发那些事

【小白学游戏常用算法】二、A*启发式搜索算法

  在上一篇博客中,我们一起学习了随机迷宫算法,在本篇博客中,我们将一起了解一下寻路算法中常用的A*算法。

18820
来自专栏Coding迪斯尼

神经网络实战:快速构建一个基于神经网络的手写数字识别系统

13520
来自专栏数值分析与有限元编程

Newton–Raphson法解串联弹簧问题

如图所示的串联弹簧,F=100,弹簧刚度为k1 = 50 + 500u ,k2 = 100+ 200u ,u是弹簧伸长量,则平衡方程为 ? ? k1,k2带入得...

29260
来自专栏机器之心

教程 | 如何使用DeepFake实现视频换脸

3.1K30

扫码关注云+社区

领取腾讯云代金券