栅格数据的裁剪与面积汇总——基于 R 语言的方法

在之前的课程「使用 R 语言处理 Merra2 数据获取各省市区县的比湿、降水量、风速和气压数据」中我们介绍了普通的栅格数据如何分省市区县进行裁剪和汇总。今天我们继续介绍分区域汇总计算栅格数据的面积。

附件中的IGBPLUCC_CN文件夹存放了 2001-2021 年中国500m分辨率IGBP土地覆被数据,该数据是徐老师处理的:中国500m分辨率IGBP土地覆被数据(2001-2021年) 今天的课程中我们将以这个数据为例讲解。

对于面积统计量,省份和城市的可以通过加总区县的得到,所以我们仅仅计算区县的就好了。

加载相应的 R 包:

library(tidyverse)
library(sf)
library(terra)

读取区县的矢量数据:

read_sf("2021行政区划/县.shp") %>%
select(-contains("类型")) -> county
county
#> Simple feature collection with 2877 features and 6 fields
#> Geometry type: MULTIPOLYGON
#> Dimension: XY
#> Bounding box: xmin: 73.50235 ymin: 3.83703 xmax: 135.0957 ymax: 53.56362
#> Geodetic CRS: WGS 84
#> # A tibble: 2,877 × 7
#> 省 省代码 市 市代码 县 县代码 geometry
#> <chr> <dbl> <chr> <dbl> <chr> <dbl> <MULTIPOLYGON [°]>
#> 1 安徽省 340000 安庆市 340800 大观区 340803 (((117.0457 30.53298, 117.0528 30.…
#> 2 安徽省 340000 安庆市 340800 怀宁县 340822 (((116.9897 30.74026, 116.9893 30.…
#> 3 安徽省 340000 安庆市 340800 潜山市 340882 (((116.7036 31.04533, 116.7046 31.…
#> 4 安徽省 340000 安庆市 340800 宿松县 340826 (((116.4555 30.12877, 116.4619 30.…
#> 5 安徽省 340000 安庆市 340800 太湖县 340825 (((115.839 30.75738, 115.8464 30.7…
#> 6 安徽省 340000 安庆市 340800 桐城市 340881 (((117.2153 30.69533, 117.2175 30.…
#> 7 安徽省 340000 安庆市 340800 望江县 340827 (((116.8906 30.35758, 116.8984 30.…
#> 8 安徽省 340000 安庆市 340800 宜秀区 340811 (((117.0716 30.77248, 117.0955 30.…
#> 9 安徽省 340000 安庆市 340800 迎江区 340802 (((117.1982 30.69515, 117.1986 30.…
#> 10 安徽省 340000 安庆市 340800 岳西县 340828 (((116.5255 31.02535, 116.5256 31.…
#> # ℹ 2,867 more rows

索引所有的栅格数据:

fs::dir_ls("IGBPLUCC_CN", regexp = "tif$") -> ls
ls
#> IGBPLUCC_CN/2001_01_01.tif IGBPLUCC_CN/2002_01_01.tif
#> IGBPLUCC_CN/2003_01_01.tif IGBPLUCC_CN/2004_01_01.tif
#> IGBPLUCC_CN/2005_01_01.tif IGBPLUCC_CN/2006_01_01.tif
#> IGBPLUCC_CN/2007_01_01.tif IGBPLUCC_CN/2008_01_01.tif
#> IGBPLUCC_CN/2009_01_01.tif IGBPLUCC_CN/2010_01_01.tif
#> IGBPLUCC_CN/2011_01_01.tif IGBPLUCC_CN/2012_01_01.tif
#> IGBPLUCC_CN/2013_01_01.tif IGBPLUCC_CN/2014_01_01.tif
#> IGBPLUCC_CN/2015_01_01.tif IGBPLUCC_CN/2016_01_01.tif
#> IGBPLUCC_CN/2017_01_01.tif IGBPLUCC_CN/2018_01_01.tif
#> IGBPLUCC_CN/2019_01_01.tif IGBPLUCC_CN/2020_01_01.tif
#> IGBPLUCC_CN/2021_01_01.tif

读取第一个栅格数据:

rast(ls[1]) -> rst
rst
#> class       : SpatRaster
#> dimensions : 10518, 13712, 1 (nrow, ncol, nlyr)
#> resolution : 0.004491576, 0.004491576 (x, y)
#> extent : 73.50016, 135.0887, 6.319648, 53.56205 (xmin, xmax, ymin, ymax)
#> coord. ref. : lon/lat WGS 84 (EPSG:4326)
#> source : 2001_01_01.tif
#> name : LC_Type1
#> min value : 1
#> max value : 17

上面读取的 county 是个 sf 对象,在参与 terra 包的栅格数据的运算时,需要使用 vect() 函数转换成 SpatVector 对象。虽然 vect() 其实也可以直接读取 shp 数据,不过使用 read_sf() 读取后的更方便处理。

vect(county)[1] 就表示第一个区县:

vect(county)[1]
#>  class       : SpatVector
#> geometry : polygons
#> dimensions : 1, 6 (geometries, attributes)
#> extent : 116.745, 117.0528, 30.35715, 30.58828 (xmin, xmax, ymin, ymax)
#> coord. ref. : lon/lat WGS 84 (EPSG:4326)
#> names : 省 省代码 市 市代码 县 县代码
#> type : <chr> <num> <chr> <num> <chr> <num>
#> values : 安徽省 3.4e+05 安庆市 3.408e+05 大观区 3.408e+05

从栅格数据中裁剪出来这个区的部分:

rst %>%
mask(vect(county)[1]) %>%
crop(vect(county)[1]) -> rstn

plot(rstn)

使用 cellSize() 函数可以计算栅格数据的每个斑块的面积:

cellSize(rstn, unit = "km") -> arearst

在 crs = 4326 坐标系下,不同纬度的斑块面积是不同的。

plot(arearst)
plot(rstn, add = T)

这里可以使用 zonal() 函数分区统计不同覆被类型斑块的总面积:

zonal(arearst, rstn, fun = sum, na.rm = T)
#>    LC_Type1       area
#> 1 5 0.4290403
#> 2 8 1.5020209
#> 3 9 89.9452921
#> 4 10 3.2201476
#> 5 11 17.8218621
#> 6 12 91.0787940
#> 7 13 28.9742177
#> 8 14 27.9211682
#> 9 16 1.5040513
#> 10 17 41.2358192

这里得到的结果就是大观区各种土地覆被类型的面积统计结果了。

然后我们就可以循环统计各个区县、各个年份的了。为了更快的计算,我们这里采用多线程,首先根据自己电脑的实际情况创建多线程,并把所需的对象和 R 包分发给各个线程:

library(parallel)
# 查看最大可用线程数量
detectCores()
#> [1] 16
makeCluster(16) -> cl
clusterExport(cl, "county")
clusterExport(cl, "ls")
dir.create("res")
clusterEvalQ(cl, ({
library(tidyverse)
library(sf)
library(terra)
})) -> tempres

我们需要循环各年、各区县,为了充分发挥多线程的功能,下面创建一个交叉数据集循环:

as.character(ls) %>%
crossing(county$县代码) %>%
set_names("rstname", "ctyindex") -> cdf

cdf
#> # A tibble: 60,417 × 2
#> rstname ctyindex
#> <chr> <dbl>
#> 1 IGBPLUCC_CN/2001_01_01.tif 110101
#> 2 IGBPLUCC_CN/2001_01_01.tif 110102
#> 3 IGBPLUCC_CN/2001_01_01.tif 110105
#> 4 IGBPLUCC_CN/2001_01_01.tif 110106
#> 5 IGBPLUCC_CN/2001_01_01.tif 110107
#> 6 IGBPLUCC_CN/2001_01_01.tif 110108
#> 7 IGBPLUCC_CN/2001_01_01.tif 110109
#> 8 IGBPLUCC_CN/2001_01_01.tif 110111
#> 9 IGBPLUCC_CN/2001_01_01.tif 110112
#> 10 IGBPLUCC_CN/2001_01_01.tif 110113
#> # ℹ 60,407 more rows

循环过程中可能会因为各种原因中断,为了重新运行的时候跳过之前运行成功的,可以使用下面的程序:

dir.create("res")

cdf %>%
mutate(value = paste0("res/",
str_extract(rstname, "\\d{4}"),
"_", ctyindex, ".rds")) %>%
anti_join(
fs::dir_ls("res") %>%
as.character() %>%
as_tibble()
) -> rstfl

rstfl
#> # A tibble: 27 × 3
#> rstname ctyindex value
#> <chr> <dbl> <chr>
#> 1 IGBPLUCC_CN/2001_01_01.tif 110101 res/2001_110101.rds
#> 2 IGBPLUCC_CN/2001_01_01.tif 110102 res/2001_110102.rds
#> 3 IGBPLUCC_CN/2001_01_01.tif 110105 res/2001_110105.rds
#> 4 IGBPLUCC_CN/2001_01_01.tif 110106 res/2001_110106.rds
#> 5 IGBPLUCC_CN/2001_01_01.tif 110107 res/2001_110107.rds
#> 6 IGBPLUCC_CN/2001_01_01.tif 110108 res/2001_110108.rds
#> 7 IGBPLUCC_CN/2001_01_01.tif 110109 res/2001_110109.rds
#> 8 IGBPLUCC_CN/2001_01_01.tif 110111 res/2001_110111.rds
#> 9 IGBPLUCC_CN/2001_01_01.tif 110112 res/2001_110112.rds
#> 10 IGBPLUCC_CN/2001_01_01.tif 110113 res/2001_110113.rds
#> # ℹ 17 more rows
clusterExport(cl, "rstfl")

rstfl 中的 value 变量就是每次运行要保存的文件名称。

然后就可以使用 parLapply() 多线程循环了:

parLapply(cl, 1:nrow(rstfl), function(x){
# if (!file.exists(rstfl$value[x])){}
rast(rstfl$rstname[x]) -> rst
county %>%
dplyr::filter(县代码 == rstfl$ctyindex[x]) %>%
vect() -> countytemp
try({
rst %>%
terra::crop(countytemp) %>%
terra::mask(countytemp) -> rsttemp
cellSize(rsttemp, unit = "km") -> newrst
zonal(newrst, rsttemp, fun = sum, na.rm = T) %>%
set_names("class", "area_km2") %>%
mutate(file = rstfl$value[x],
县代码 = countytemp$县代码) %>%
write_rds(rstfl$value[x])
rm(rst)
rm(countytemp)
rm(rsttemp)
rm(newrst)
})
}) -> res

这里我没有使用 if (!file.exists(rstfl$value[x])){} 跳过,是因为这种判断其实也会浪费时间,在要处理的文件特别多的情况下,不如这里用的 anti_join() 方法更高效。

运行完之后就可以合并所有的 rds 文件了:

parLapply(cl, fs::dir_ls("res"), readr::read_rds) %>%
bind_rows() %>%
as_tibble() -> df

df
#> # A tibble: 486,534 × 4
#> class area_km2 file 县代码
#> <dbl> <dbl> <chr> <dbl>
#> 1 13 50.9 res/2001_110101.rds 110101
#> 2 13 58.8 res/2001_110102.rds 110102
#> 3 17 0.191 res/2001_110102.rds 110102
#> 4 12 7.65 res/2001_110105.rds 110105
#> 5 13 496. res/2001_110105.rds 110105
#> 6 8 0.767 res/2001_110106.rds 110106
#> 7 9 1.34 res/2001_110106.rds 110106
#> 8 10 4.41 res/2001_110106.rds 110106
#> 9 11 0.958 res/2001_110106.rds 110106
#> 10 12 1.15 res/2001_110106.rds 110106
#> # ℹ 486,524 more rows

再处理下:

# 简单处理下
df %>%
as_tibble() %>%
tidyr::extract(file, "year", "/(\\d{4})_", convert = T) -> df

df
#> # A tibble: 486,534 × 4
#> class area_km2 year 县代码
#> <dbl> <dbl> <int> <dbl>
#> 1 13 50.9 2001 110101
#> 2 13 58.8 2001 110102
#> 3 17 0.191 2001 110102
#> 4 12 7.65 2001 110105
#> 5 13 496. 2001 110105
#> 6 8 0.767 2001 110106
#> 7 9 1.34 2001 110106
#> 8 10 4.41 2001 110106
#> 9 11 0.958 2001 110106
#> 10 12 1.15 2001 110106
#> # ℹ 486,524 more rows
df %>%
left_join(
county %>%
st_drop_geometry() %>%
select(-contains("类型"))
) %>%
rename(年份 = year, 该覆被类型区域面积_km2 = area_km2,
覆被类型 = class) %>%
select(5:9, 4, 3, everything()) %>%
filter(覆被类型 != 0) -> countydf

countydf
#> # A tibble: 486,534 × 9
#> 省 省代码 市 市代码 县 县代码 年份 覆被类型 该覆被类型区域面积_km2
#> <chr> <dbl> <chr> <dbl> <chr> <dbl> <int> <dbl> <dbl>
#> 1 北京市 110000 北京… 110000 东城… 110101 2001 13 50.9
#> 2 北京市 110000 北京… 110000 西城… 110102 2001 13 58.8
#> 3 北京市 110000 北京… 110000 西城… 110102 2001 17 0.191
#> 4 北京市 110000 北京… 110000 朝阳… 110105 2001 12 7.65
#> 5 北京市 110000 北京… 110000 朝阳… 110105 2001 13 496.
#> 6 北京市 110000 北京… 110000 丰台… 110106 2001 8 0.767
#> 7 北京市 110000 北京… 110000 丰台… 110106 2001 9 1.34
#> 8 北京市 110000 北京… 110000 丰台… 110106 2001 10 4.41
#> 9 北京市 110000 北京… 110000 丰台… 110106 2001 11 0.958
#> 10 北京市 110000 北京… 110000 丰台… 110106 2001 12 1.15
#> # ℹ 486,524 more rows
countydf %>%
haven::write_dta("2001~2021年各区县不同作物覆被类型的土地面积.dta", label = "数据处理:微信公众号 RStata")

城市的通过加总区县即可得到:

countydf %>%
group_by(省, 省代码, 市, 市代码, 年份, 覆被类型) %>%
summarise(该覆被类型区域面积_km2 = sum(该覆被类型区域面积_km2, na.rm = T)) %>%
ungroup() -> citydf

citydf
#> # A tibble: 84,449 × 7
#> 省 省代码 市 市代码 年份 覆被类型 该覆被类型区域面积_km2
#> <chr> <dbl> <chr> <dbl> <int> <dbl> <dbl>
#> 1 上海市 310000 上海市 310000 2001 9 518.
#> 2 上海市 310000 上海市 310000 2001 10 119.
#> 3 上海市 310000 上海市 310000 2001 11 469.
#> 4 上海市 310000 上海市 310000 2001 12 2641.
#> 5 上海市 310000 上海市 310000 2001 13 2727.
#> 6 上海市 310000 上海市 310000 2001 14 575.
#> 7 上海市 310000 上海市 310000 2001 16 40.5
#> 8 上海市 310000 上海市 310000 2001 17 874.
#> 9 上海市 310000 上海市 310000 2002 9 784.
#> 10 上海市 310000 上海市 310000 2002 10 134.
#> # ℹ 84,439 more rows
citydf %>%
haven::write_dta("2001~2021年各城市不同作物覆被类型的土地面积.dta", label = "数据处理:微信公众号 RStata")

省份的通过加总城市的即可得到:

citydf %>%
group_by(省, 省代码, 年份, 覆被类型) %>%
summarise(该覆被类型区域面积_km2 = sum(该覆被类型区域面积_km2, na.rm = T)) %>%
ungroup() -> provdf

provdf %>%
haven::write_dta("2001~2021年各省份不同作物覆被类型的土地面积.dta", label = "数据处理:微信公众号 RStata")

这样就完成了栅格数据的分区域面积汇总~

点击这里跳转到 RStata 短书平台获取附件:栅格数据的裁剪与面积汇总——基于 R 语言的方法

评论