R、ggplot2、sf 和 rnaturalearth:一种从一个投影切换到另一个投影的简单方法

R, ggplot2, sf and rnaturalearth: an easy way to switch from one projection to another

首先声明我不是制图师。 我需要绘制一张欧洲地图,其中一些部分连接欧洲各国首都。在不深入细节的情况下,我在 post 末尾的 reprex 中提供了一个简化示例。绘制整个世界的图然后更改其投影非常容易(我在 reprex 的开头这样做)。

只要使用默认的CRS(坐标参考系统),select一个window大致对应欧洲,绘制罗马到巴黎的线段也不难。

因为我想求助于等地投影,所以我没有找到比在新 CRS 中明确计算 window(以及巴黎和罗马)的新坐标更好的方法。 在这样做的过程中,我玩弄了需要从中提取一些数字的空间对象。 这一切都感觉相当麻烦,所以我想知道使用 R 的真正制图师是否有更好的方法来实现这个 and/or 一些简化过程的功能。

非常感谢!

library(tidyverse)
library(rnaturalearth)
library(sf)
#> Linking to GEOS 3.7.1, GDAL 2.4.0, PROJ 5.2.0


ww_ini <- ne_countries(scale = "medium",
                       type = 'map_units',
                       returnclass = "sf")

bb <- ne_download(type = "wgs84_bounding_box", category = "physical",
                  returnclass = "sf") 
#> OGR data source with driver: ESRI Shapefile 
#> Source: "/tmp/RtmpyDkGhC", layer: "ne_110m_wgs84_bounding_box"
#> with 1 features
#> It has 2 fields


gpl1 <- ggplot(data = ww_ini) +
    geom_sf(  col = "black", lwd = 0.3 )+
    xlab(NULL) + ylab(NULL) +
    ggtitle("Test title")+
  geom_sf(data = bb, col = "grey", fill = "transparent") +
    theme(plot.background = element_rect(fill = "white"),
          panel.background = element_rect(fill = 'white'),
          panel.grid.major = element_line(colour = "grey"),
          legend.position="top",
          plot.title = element_text(lineheight=.8, size=24, face="bold",
                                    vjust=1),
          legend.text = element_text(vjust=.4,lineheight=1,size = 14),
          legend.title = element_text(vjust=1,lineheight=1, size=14,
                                      face="bold" ))


ggsave("simple_world.pdf", gpl1, width=6*1.618,height=5)



gpl2 <- ggplot(data = ww_ini) +
    geom_sf(  col = "black", lwd = 0.3 )+
    xlab(NULL) + ylab(NULL) +
    ggtitle("Test title")+
  geom_sf(data = bb, col = "grey", fill = "transparent") +
    theme(plot.background = element_rect(fill = "white"),
          panel.background = element_rect(fill = 'white'),
          panel.grid.major = element_line(colour = "grey"),
          legend.position="top",
          plot.title = element_text(lineheight=.8, size=24, face="bold",
                                    vjust=1),
          legend.text = element_text(vjust=.4,lineheight=1,size = 14),
          legend.title = element_text(vjust=1,lineheight=1, size=14,
                                      face="bold" ))+
    coord_sf( crs = "+proj=eqearth +wktext") 

ggsave("simple_world_equal_earth.pdf", gpl2, width=6*1.618,height=5)




gpl3 <- ggplot(data = ww_ini) +
    geom_sf(  col = "black", lwd = 0.3 )+
    xlab(NULL) + ylab(NULL) +
    ggtitle("Test title")+
  geom_sf(data = bb, col = "grey", fill = "transparent") +
    theme(plot.background = element_rect(fill = "white"),
          panel.background = element_rect(fill = 'white'),
          panel.grid.major = element_line(colour = "grey"),
          legend.position="top",
          plot.title = element_text(lineheight=.8, size=24, face="bold",
                                    vjust=1),
          legend.text = element_text(vjust=.4,lineheight=1,size = 14),
          legend.title = element_text(vjust=1,lineheight=1, size=14,
                                      face="bold" ))+
         coord_sf(xlim=c(-20,45), ylim=c(30, 73) ) 

ggsave("simple_europe.pdf", gpl3, width=6*1.618,height=5)





### now I try making a plot of Europe using the equal earth projection.
## I need to recalculate the coordinate for the zoom in the new equal-earth
## CRS (coordinate reference system)


## See https://www.r-bloggers.com/2019/04/zooming-in-on-maps-with-sf-and-ggplot2/

## Step 1: determine the target coordinate reference system

target_crs <- '+proj=eqearth +wktext'

## Step 2: I transform the world data into the new coordinate system

worldmap_trans <- st_transform(ww_ini, crs = target_crs)


### Step 3: define the display window as a latitude and longitude interval

disp_win_wgs84 <- st_sfc(st_point(c(-20, 30)), st_point(c(45, 73)),
                         crs = 4326)

## Step 4: transform the display window in the new coordinate system.

disp_win_trans <- st_transform(disp_win_wgs84, crs = target_crs)


#### Step 5: retrieve the window coordinates in the new coordinate system

disp_win_coord <- st_coordinates(disp_win_trans)


gpl4 <- ggplot(data = worldmap_trans) +
    geom_sf(  col = "black", lwd = 0.3 )+
    xlab(NULL) + ylab(NULL) +
    ggtitle("Test title")+
  geom_sf(data = bb, col = "grey", fill = "transparent") +
    theme(plot.background = element_rect(fill = "white"),
          panel.background = element_rect(fill = 'white'),
          panel.grid.major = element_line(colour = "grey"),
          legend.position="top",
          plot.title = element_text(lineheight=.8, size=24, face="bold",
                                    vjust=1),
          legend.text = element_text(vjust=.4,lineheight=1,size = 14),
          legend.title = element_text(vjust=1,lineheight=1, size=14,
                                      face="bold" ))+
    coord_sf(xlim = disp_win_coord[,'X'], ylim = disp_win_coord[,'Y'],
             datum = target_crs## , expand = FALSE
             ) 


ggsave("simple_europe_equal_earth.pdf", gpl4, width=6*1.618,height=5)


####Now I want to add a segment connecting Rome to Paris to the Europe map.
### easy with the default CRS.


cities <- structure(list(city_ascii = c("Rome", "Paris"), country = c("Italy", 
"France"), lat = c(41.8931, 48.8566), lng = c(12.4828, 2.3522
)), row.names = c(NA, -2L), class = c("tbl_df", "tbl", "data.frame"
))




gpl3bis <- ggplot(data = ww_ini) +
    geom_sf(  col = "black", lwd = 0.3 )+
    xlab(NULL) + ylab(NULL) +
    ggtitle("Test title")+
  geom_sf(data = bb, col = "grey", fill = "transparent") +
    theme(plot.background = element_rect(fill = "white"),
          panel.background = element_rect(fill = 'white'),
          panel.grid.major = element_line(colour = "grey"),
          legend.position="top",
          plot.title = element_text(lineheight=.8, size=24, face="bold",
                                    vjust=1),
          legend.text = element_text(vjust=.4,lineheight=1,size = 14),
          legend.title = element_text(vjust=1,lineheight=1, size=14,
                                      face="bold" ))+   
    geom_segment(aes(x = cities$lng[1], y =cities$lat[1] ,
                     xend = cities$lng[2], yend = cities$lat[2]), colour = "red")+
    coord_sf(xlim=c(-20,45), ylim=c(30, 73) ) 

ggsave("simple_europe_segment.pdf", gpl3bis, width=6*1.618,height=5)


#####Now I try to do the same using the equal-earth projection

## I need to convert the coordinates of Rome and Paris into those of the
## equal earth CRS.

point_mat <- cities %>%
    select(lng, lat) %>%
    as.matrix

pp <- st_multipoint(point_mat)

pp_equal_earth <- st_sfc(pp, 
                         crs = 4326)


points_equal_earth <- st_transform(pp_equal_earth, crs = target_crs)

##Now I need a way to extract the number from points_equal_earth.
## I did not find anything better than

points_simple <- as_Spatial(points_equal_earth)%>%as_tibble


gpl4bis <- ggplot(data = worldmap_trans) +
    geom_sf(  col = "black", lwd = 0.3 )+
    xlab(NULL) + ylab(NULL) +
    ggtitle("Test title")+
  geom_sf(data = bb, col = "grey", fill = "transparent") +
    theme(plot.background = element_rect(fill = "white"),
          panel.background = element_rect(fill = 'white'),
          panel.grid.major = element_line(colour = "grey"),
          legend.position="top",
          plot.title = element_text(lineheight=.8, size=24, face="bold",
                                    vjust=1),
          legend.text = element_text(vjust=.4,lineheight=1,size = 14),
          legend.title = element_text(vjust=1,lineheight=1, size=14,
                                      face="bold" ))+
    geom_segment(aes(x = points_simple$X1[1], y =points_simple$X2[1] ,
                     xend = points_simple$X1[2], yend = points_simple$X2[2]), colour = "red")+

    coord_sf(xlim = disp_win_coord[,'X'], ylim = disp_win_coord[,'Y'],
             datum = target_crs## , expand = FALSE
             ) 


ggsave("simple_europe_equal_earth_segment.pdf", gpl4bis, width=6*1.618,height=5)

reprex package (v2.0.0)

于 2021-08-04 创建

对于你的情况,我的建议是将城市的点和连接线都转换为空间 POINTLINESTRING。一旦你有了它,你就不需要担心在不同的 CRS 上提取数字,st_transform 会为你做这件事。您也可以使用 ggplot2/geom_sf 而不是 geom_segment 绘制所有内容,因为您可以使用 sf 格式绘制所有内容。我认为 geom_segment 是您代码中的冲突部分。

看到一个代表,虽然我不得不承认我不确定我是否完全理解这个问题。希望这会有所帮助:

library(tidyverse)
library(rnaturalearth)
library(sf)
#> Linking to GEOS 3.9.0, GDAL 3.2.1, PROJ 7.2.1

# Get countries
ww_ini <- ne_countries(
  scale = "medium",
  type = "map_units",
  returnclass = "sf"
)

# Cities

cities <-
  structure(
    list(
      city_ascii = c("Rome", "Paris"),
      country = c(
        "Italy",
        "France"
      ),
      lat = c(41.8931, 48.8566),
      lng = c(12.4828, 2.3522)
    ),
    row.names = c(NA, -2L),
    class = c("tbl_df", "tbl", "data.frame")
  )
# To spatial points
cities_sf <- st_as_sf(cities, coords = c("lng", "lat"), crs = 4326)

# Key point! Create an spatial line connecting the points

line <-
  st_linestring(st_coordinates(cities_sf)) %>% st_sfc(crs = 4326)

# Your display window
disp_win_wgs84 <- st_sfc(st_point(c(-20, 30)),
  st_point(c(45, 73)),
  crs = 4326
)

# Ensure everything is in the same proj
# Clasic error when working with maps
target_crs <- "+proj=eqearth +wktext"

ww_ini_eqea <- st_transform(ww_ini, target_crs)
cities_sf_eqea <- st_transform(cities_sf, target_crs)
line_eqea <- st_transform(line, target_crs)
disp_win_eqea <- st_transform(disp_win_wgs84, target_crs)
st_coordinates(disp_win_eqea)
#>          X       Y
#> 1 -1793068 3764325
#> 2  2837546 7872578


# Map everything

# Start with world
ggplot(ww_ini_eqea) +
  geom_sf() +
  # Add cities
  geom_sf(data = cities_sf_eqea, color = "blue") +
  # Now the line
  geom_sf(data = line_eqea, color = "red") +
  # Limit the map
  coord_sf(
    xlim = st_coordinates(disp_win_eqea)[, "X"],
    ylim = st_coordinates(disp_win_eqea)[, "Y"]
  ) +
  theme_minimal()


sessionInfo()
#> R version 4.1.0 (2021-05-18)
#> Platform: x86_64-w64-mingw32/x64 (64-bit)
#> Running under: Windows 10 x64 (build 19043)
#> 
#> Matrix products: default
#> 
#> locale:
#> [1] LC_COLLATE=Spanish_Spain.1252  LC_CTYPE=Spanish_Spain.1252   
#> [3] LC_MONETARY=Spanish_Spain.1252 LC_NUMERIC=C                  
#> [5] LC_TIME=Spanish_Spain.1252    
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#>  [1] sf_1.0-2            rnaturalearth_0.1.0 forcats_0.5.1      
#>  [4] stringr_1.4.0       dplyr_1.0.7         purrr_0.3.4        
#>  [7] readr_2.0.0         tidyr_1.1.3         tibble_3.1.3       
#> [10] ggplot2_3.3.5       tidyverse_1.3.1    
#> 
#> loaded via a namespace (and not attached):
#>  [1] Rcpp_1.0.7              lattice_0.20-44         lubridate_1.7.10       
#>  [4] class_7.3-19            assertthat_0.2.1        digest_0.6.27          
#>  [7] utf8_1.2.2              R6_2.5.0                cellranger_1.1.0       
#> [10] backports_1.2.1         reprex_2.0.0            rnaturalearthdata_0.1.0
#> [13] evaluate_0.14           e1071_1.7-8             httr_1.4.2             
#> [16] highr_0.9               pillar_1.6.2            rlang_0.4.11           
#> [19] readxl_1.3.1            rstudioapi_0.13         rmarkdown_2.9          
#> [22] styler_1.5.1            munsell_0.5.0           proxy_0.4-26           
#> [25] broom_0.7.9             compiler_4.1.0          modelr_0.1.8           
#> [28] xfun_0.24               pkgconfig_2.0.3         rgeos_0.5-5            
#> [31] htmltools_0.5.1.1       tidyselect_1.1.1        fansi_0.5.0            
#> [34] crayon_1.4.1            tzdb_0.1.2              dbplyr_2.1.1           
#> [37] withr_2.4.2             grid_4.1.0              jsonlite_1.7.2         
#> [40] gtable_0.3.0            lifecycle_1.0.0         DBI_1.1.1              
#> [43] magrittr_2.0.1          units_0.7-2             scales_1.1.1           
#> [46] KernSmooth_2.23-20      cli_3.0.1               stringi_1.7.3          
#> [49] farver_2.1.0            fs_1.5.0                sp_1.4-5               
#> [52] xml2_1.3.2              ellipsis_0.3.2          generics_0.1.0         
#> [55] vctrs_0.3.8             tools_4.1.0             glue_1.4.2             
#> [58] hms_1.1.0               yaml_2.2.1              colorspace_2.0-2       
#> [61] classInt_0.4-3          rvest_1.0.1             knitr_1.33             
#> [64] haven_2.4.3

reprex package (v2.0.0)

于 2021-08-05 创建