knitr::opts_chunk$set(echo = TRUE,
dpi = 400,
warning = FALSE,
message = FALSE,
fig.align = 'center',
comment = "#>")
這是《R 語言地理數據科學》系列課程的第一講,通常這種課程是面向 R 語言高級用戶的,但是考慮到大家的 R 語言可能還沒入門,所以我打算儘可能細緻的講解,讓這門課即使是剛入門的 R 用戶也能輕鬆理解,不過儘管如此,我依舊建議你結合著我的另一個課程:《R 數據科學》一起學習。
本系列的課程的參考資料較多,我還會結合大量實際案例,因此此處我暫時不一一列舉了。我會在每次課程的最後添加參考資料的連結。
為什麼要使用 R 語言進行地理分析?實際上我並沒有學習過任何桌面 GIS 軟體,例如 QGIS、ArcMap、GRASS、SAGA等,但是程式語言相對桌面軟體的最大優勢就是可重複性了。
所以我也只能從 R 的角度去看這個問題,R 的易用性、跨平臺性以及活躍的社區都是這個問題的答案。基於我自己的使用經驗,使用 R 進行地理計算可以滿足我們 99% 的工作和學習需要。另外結合 R 語言強大的繪圖性能,地理數據可視化也就不是問題了。
例如下圖展示了夜間燈光數據和中國三個城市的位置:
library(leaflet)
popup = c("北京市", "拉薩市", "香港特別行政區")
leaflet(width = "100%", height = "400px") %>%
addProviderTiles("NASAGIBS.ViirsEarthAtNight2012") %>%
addMarkers(lng = c(116.41228, 91.089778, 114.13432),
lat = c(40.185543, 30.036993, 22.381274),
popup = popup)
可重複性:別人可以使用你的代碼和數據得到相同的結果。
R 語言中有諸多用於地理計算和可視化的包,在這裡我主要使用 sf、raster 和 ggplot2 包,另外我會儘可能介紹更多好用的包。我就不再贅述使用 R 語言進行地理分析的歷史了,反正用 sf 包就對了。在數據量不大的時候這個包的性能表現還不錯。
使用 R 語言進行地理分析能做什麼?這裡展示下我之前的教程中涉及到的一些。
R 中的地理數據準備工作本節課主要使用下面兩個 R 包:
install.packages('sf')
install.packages('raster')
地理矢量數據模型的基礎是位於地理參考系統(crs)中的點。點可以表示為獨立的要素(例如某個公交站臺的位置),也可以組合成複雜的幾何形狀,例如直線和多邊形。
例如 crs = 4326:
library(sf)
library(ggplot2)
library(tidyverse)
worldmp <- read_sf("world.geo.json") %>%
st_transform(4326)
tibble(lon = c(0, 116),
lat = c(0, 40),
name = c("crs:4326 原點", "北京")) %>%
st_as_sf(coords = c("lon", "lat"), crs = 4326) -> da4326
ggplot(da4326) +
geom_sf_label(aes(label = name), family = cnfont,
nudge_x = -12, nudge_y = -12,
color = "red") +
geom_sf(color = "red", size = 3) +
geom_sf(data = worldmp, fill = NA) +
coord_sf(crs = 4326) +
labs(title = "CRS: 4326") +
theme(axis.title.x = element_blank(),
axis.title.y = element_blank())
我覺得費力的去解釋 4326 意義不大,因為它表示 WGS 84 坐標系,顯然又變成另一個費解的詞語,因為後面我都直接說 crs 的數值了。我國官方要求使用的坐標系是 CCGS 2000,對應的 crs 是:4490,這一坐標系的坐標原點和北京的位置:
worldmp <- read_sf("world.geo.json") %>%
st_transform(4490)
tibble(lon = c(0, 116),
lat = c(0, 40),
name = c("crs:4490 原點", "北京")) %>%
st_as_sf(coords = c("lon", "lat"), crs = 4326) %>%
st_transform(4490) -> da4490
da4490
da4326
ggplot(da4490) +
geom_sf_label(aes(label = name), family = cnfont,
nudge_x = -12, nudge_y = -12,
color = "red") +
geom_sf(color = "red", size = 3) +
geom_sf(data = worldmp, fill = NA) +
coord_sf(crs = 4490) +
labs(title = "CRS: 4490") +
theme(axis.title.x = element_blank(),
axis.title.y = element_blank())
是不是發現兩個坐標係為什麼差異,所以通常我們不需要特意將 4326 下的坐標轉到 4490 下。兩個坐標系下的坐標僅有細微的差異。
有很多 crs 可供選擇:
# install.packages('rgdal')
rgdal::make_EPSG() %>%
DT::datatable(width = "100%")
sf 包也提供了類似的函數:
sf_proj_info(type = "proj") %>%
DT::datatable(width = "100%")
我們再以 crs = 54003 為例演示下:
worldmp <- read_sf("world_high_resolution_mill.geo.json")
worldmp
# 它的 crs 是 EPSG:54003
rbind(
tibble(lon = 116, lat = 40, name = "北京") %>%
st_as_sf(coords = c("lon", "lat"), crs = 4326) %>%
st_transform(54003),
tibble(X = 0, Y = 0, name = "crs:54003 原點") %>%
st_as_sf(coords = c("X", "Y"), crs = 54003)
) -> da54003
ggplot(da54003) +
geom_sf_text(aes(label = paste0("\n", name)),
family = cnfont,
color = "red") +
geom_sf(color = "red", size = 3) +
geom_sf(data = worldmp, fill = NA) +
coord_sf(crs = 54003) +
labs(title = "CRS: 54003") +
theme(axis.title.x = element_blank(),
axis.title.y = element_blank())
這裡就不再羅列更多的 crs 了,這不是本節課的重點。
simple featuressf 是 simple features 的意思,是代表各種幾何類型的分層數據模型,sf 包支持所有的其中常用幾何類型:
在使用 sf 包進行地理分析的時候,我們通常會將數據讀取為 sf 對象。sf 對象存儲在數據框中,其中地理數據佔據一個特殊列,通常是 geometry,例如我們剛剛讀取的 worldmp:
worldmp
class(worldmp)
plot(worldmp)
通常我使用 ggplot2 繪圖:
ggplot(worldmp) +
geom_sf(aes(fill = name), color = "black",
size = 0.1) +
scale_fill_viridis_d(option = "D") +
theme_ipsum(base_family = cnfont) +
theme(legend.position = "none") +
labs(title = "世界地圖") +
theme(axis.title.x = element_blank(),
axis.title.y = element_blank())
sf 對象的最大優勢就在於,你可以使用幾乎所有處理數據框的手段處理它。例如:
glimpse(worldmp)
現在越多越多的 R 包增加了對 sf 對象的支持,例如:tmap 和 mapview。而對於那些還不支持 sf 對象的包,也完全不用擔心,sf 類可以很容易轉換成 sp 類進行使用,例如:
library(sp)
world_sp = as(worldmp, Class = "Spatial")
而 sp 對象也可以很容易的被轉換為 sf 對象:
world_sf = st_as_sf(world_sp)
上面的例子中我們也演示了一些,下面我再通過一個例子演示下:
# 生成一列隨機數
worldmp$pop = runif(215, 1, 100)
# 繪圖
ggplot(worldmp) +
geom_sf(aes(fill = name), color = "black",
size = 0.1) +
stat_sf_coordinates(geom = "point", color = "#E31A1C",
aes(size = pop), shape = 1) +
scale_fill_viridis_d(option = "D") +
theme_ipsum(base_family = cnfont) +
theme(legend.position = "none") +
labs(title = "世界地圖") +
theme(axis.title.x = element_blank(),
axis.title.y = element_blank())
geometry 是 sf 的基礎,本講中我們將一一介紹常見的七種類:POINT,LINESTRING,POLYGON,MULTIPOINT,MULTILINESTRING,MULTIPOLYGON 和 GEOMETRYCOLLECTION。
POINT、LINESTRING 和 POLYGON# POINT:點
st_point(c(5, 2))
# LINESTRING:線
st_linestring(matrix(c(1, 5, 4, 4, 4, 1, 2, 2, 3, 2),
ncol = 2, byrow = T))
# POLYGON:多邊形
st_polygon(list(matrix(c(1, 5, 2, 2, 4, 1, 4, 4, 1, 5),
ncol = 2, byrow = T)))
# MULTIPOINT
st_multipoint(matrix(c(1, 5, 4, 4, 4, 1, 2, 2, 3, 2),
ncol = 2, byrow = T))
# MULTILINESTRING
st_multilinestring(list(
matrix(c(1, 5, 4, 4, 4, 1, 2, 2, 3, 2),
ncol = 2, byrow = T),
matrix(c(1, 2, 2, 4),
ncol = 2, byrow = T)
))
# MULTIPOLYGON
st_multipolygon(list(
list(matrix(c(1, 5, 2, 2, 4, 1, 4, 4, 1, 5),
ncol = 2, byrow = T)),
list(matrix(c(0, 2, 1, 2, 1, 3, 0, 3, 0, 2),
ncol = 2, byrow = T))
))
st_geometrycollection(list(
st_point(c(5, 2)),
st_linestring(matrix(c(1, 5, 4, 4, 4, 1, 2, 2, 3, 2),
ncol = 2, byrow = T)),
st_polygon(list(matrix(c(1, 5, 2, 2, 4, 1, 4, 4, 1, 5),
ncol = 2, byrow = T))),
st_multipoint(matrix(c(1, 5, 4, 4, 4, 1, 2, 2, 3, 2),
ncol = 2, byrow = T)),
st_multilinestring(list(
matrix(c(1, 5, 4, 4, 4, 1, 2, 2, 3, 2),
ncol = 2, byrow = T),
matrix(c(1, 2, 2, 4),
ncol = 2, byrow = T)
)),
st_multipolygon(list(
list(matrix(c(1, 5, 2, 2, 4, 1, 4, 4, 1, 5),
ncol = 2, byrow = T)),
list(matrix(c(0, 2, 1, 2, 1, 3, 0, 3, 0, 2),
ncol = 2, byrow = T))
))
)) %>% plot()
point1 = st_point(c(5, 2))
point2 = st_point(c(1, 3))
points_sfc = st_sfc(point1, point2)
points_sfc
# 還可以設定 crs
st_sfc(point1, point2, crs = 4326)
我們可以按照 sfg -> sfc -> sf 的順序構造 sf 對象,這樣的操作在實際中很少使用,僅僅用於演示 sf 對象是如何構建的:
# sfg
bj_point <- st_point(c(116, 40))
# sfc
bj_geom <- st_sfc(bj_point, crs = 4326)
# tibble
bj <- tibble(name = "北京")
bj_sf = st_sf(bj, geometry = bj_geom)
bj_sf
# 繪圖展示
ggplot(worldmp) +
geom_sf(aes(fill = name), color = "black",
size = 0.1) +
geom_sf(data = bj_sf, color = "red") +
scale_fill_viridis_d(option = "D") +
theme_ipsum(base_family = cnfont) +
theme(legend.position = "none") +
labs(title = "世界地圖") +
theme(axis.title.x = element_blank(),
axis.title.y = element_blank())
地理柵格數據通常由定義坐標參考系統,範圍和原點的 header 和代表像素的矩陣組成。我平時遇到 raster 數據都是簡單處理之後轉為 sf 對象再進行處理,例如夜間燈光數據(2013年全球的),下面的示例很費時間,建議在夜間運行:
F182013.v4c_web.avg_vis.tif 數據下載自:https://www.ngdc.noaa.gov/eog/dmsp/downloadV4composites.html ,為了方便大家下載,我提供一個百度網盤連結:
連結:https://pan.baidu.com/s/16RmU9FHtFbO5i4aWb_vujg 密碼:b2cv
library(raster)
raster('F182013.v4c_web.avg_vis.tif') -> light
# 從其中提取中國的
worldmp %>%
dplyr::filter(name == "中國") %>%
st_transform(projection(light)) -> cn
mask(light, cn) -> cnlight
# 將 raster 數據轉為 sf
cnlight %>%
rasterToPoints(spatial = TRUE) %>%
st_as_sf() -> cnlight_sf
# 繪圖展示
ggplot(cnlight_sf) +
geom_sf(aes(color = F182013.v4c_web.avg_vis), size = 0.01) +
geom_sf(data = cn, size = 0.3, color = "white", fill = NA) +
scale_color_gradientn(
colours = c("#1E1E1E", "#1E1E1E", "#fcfff7", "#f1ff8f", "#f1e93f", "#fabd08")) +
theme_modern_rc(base_family = cnfont,
plot_title_family = cnfont,
subtitle_family = cnfont,
caption_family = cnfont) +
theme(legend.position = "none",
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_blank(),
axis.title.x = element_blank(),
axis.title.y = element_blank()) +
labs(title = "中國夜間燈光數據",
subtitle = "RStata Project",
caption = "數據來源:Earth Observation Group | ngdc.noaa.gov\n<https://www.ngdc.noaa.gov/eog/dmsp/downloadV4composites.html>")
地理坐標系使用兩個值(經度和緯度)識別地球表面上的任何位置。投影坐標參考系統則是將地球表面展開得到的笛卡爾坐標系,不同的展開方式就得到了不同投影下的坐標系。投影的缺點就是必然產生失真(面積、距離、形狀和方向)。
R 中描述 crs 的方法主要是使用 epsg 代碼和 proj4string。這兩種方法都有優點和缺點。epsg 代碼通常較短,更容易記住,但是該代碼僅引用一種定義明確的坐標參考系;proj4string 則可以更靈活的定義各種參數(例如投影類型,基準面和橢球體)。
我們可以使用 st_crs 查看某個 sf 對象的 crs:
st_crs(worldmp)
# 查看 proj4string
st_crs(worldmp)$proj4string
# 查看度量單位
st_crs(worldmp)$units
# 查看 EPSG 代碼
st_crs(worldmp)$epsg
如果某個對象缺少 crs 或者設置了錯誤的 crs,你可以使用 st_set_crs 設定
st_set_crs(worldmp, 54003)
projection 函數可以提取 raster 對象的 crs:
projection(light)
#> [1] "+proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0"
我們先了解一下 R 語言裡面的測度:
library(units)
l <- set_units(1:10, m)
l
我們再把米轉換成釐米:
set_units(l, cm)
還可以進行計算:
set_units(l, cm) + l
例如 400 公頃等於 4 平方千米:
a <- set_units(400, ha)
# 等於 4 平方千米
set_units(a, km2)
例如速度的定義和轉換:
vel <- set_units(seq(20, 50, 10), km/h)
set_units(vel, m/s)
sf 對象的一個優勢就是對單位的支持,例如我們計算世界各國的面積:
st_area(worldmp)
我們可以把它合併到 worldmp 數據框裡面:
worldmp$area <- set_units(st_area(worldmp), km2)
worldmp
作業:
如果時間允許的話,可以試著從全球的夜間燈光數據裡提取美國的部分並繪圖展示。參考資料Chapter 2 Geographic data in R | Geocomputation with R. https://geocompr.robinlovelace.net/spatial-class.html