2

I am trying to plot species range areas using convex hulls to then calculate the area and create a figure.

There is a well known issue with the 180 degree international dateline that I have been trying to remedy following many examples on SE, e.g:

How to remedy a path that crosses the international dateline with R

This comes close to what I am aiming for but plots in mapview not ggplot2: How to construct/plot convex hulls of polygons from points by factor using sf?

Here is my attempt:

library(tidyverse)
library(maps)
library(ggmap)
library(sf)
library(sp)
library(rnaturalearth)
library(rnaturalearthdata)
library(ggspatial)
library(mapproj)

Generate species occurrence data, with some points crossing 180 longitude


df <- data.frame(species = rep("sp1",8),
                 longitude = as.double(c(-170.2, -179.5, 55.9, 167.6, 154.3, 101.7, 70.54, -165.94)),
                  latitude = as.double(c(8.25, -24.75, 24.25,19.25, 33.45, -15.5, 5.56, 4.6)))

Pacific centered world map from map_data and plot

world <- map_data("world2") 

map<-ggplot() +
geom_polygon(data = world, aes(x = long, y = lat, group = group),
col = "#78909C", fill = "#78909C", lwd = 0)+
coord_map(orientation = c(90,0, 150), ylim = c(-40, 40), xlim = c(20,210))

my map

Add occurrence points to the map

map +
geom_point(data = df, mapping = aes(x = longitude, y = latitude))

map with points

Construct minimum convex hulls from species occurrence data.

species.sf <- df %>%
  st_as_sf( coords = c( "longitude", "latitude" ))

Create hulls and wrap around dateline

hull<- species.sf %>%
  summarise( geometry = st_combine( geometry ) ) %>%
  st_convex_hull()

hull<-st_wrap_dateline(hull,options = c("WRAPDATELINE=YES", "DATELINEOFFSET=180"),
   quiet = TRUE)

Plot hull - cuts at 180 but clearly not including all occurrence points


map +
geom_point(data = df, mapping = aes(x = longitude, y = latitude))+
geom_sf(data=hull, inherit.aes = TRUE)

incorrect hull

Calculate Area of Hull - must be incorrect based on the hull shape

st_area(hull)

I have also tried applying a pacific centred CRS to the map, points and hulls but suspect that I am applying these either in the wrong order or wrong places? I am very new to using R for spatial analysis so any help hugely apriciated . Thanks.

Ronak Shah
  • 377,200
  • 20
  • 156
  • 213

1 Answers1

2

Please find below a solution to your problem. I used the function st_shift_longitude() from the package sf.

Reprex

  • Your data (no changes)
df <- data.frame(species = rep("sp1",8),
                 longitude = as.double(c(-170.2, -179.5, 55.9, 167.6, 154.3, 101.7, 70.54, -165.94)),
                 latitude = as.double(c(8.25, -24.75, 24.25,19.25, 33.45, -15.5, 5.56, 4.6)))



world <- map_data("world2") 

map<-ggplot() +
  geom_polygon(data = world, aes(x = long, y = lat, group = group),
               col = "#78909C", fill = "#78909C", lwd = 0) + 
  coord_map(orientation = c(90,0, 150), ylim = c(-40, 40), xlim = c(20,210))
map

  • Convert your dataframe "df" into the sf object "species.sf" and shift the longitude with st_shift_longitude()
species.sf <- df %>% 
  st_as_sf(coords = c("longitude", "latitude"), crs = 4326) %>% 
  st_shift_longitude()


map + 
  geom_sf(data = species.sf, inherit.aes = TRUE) + 
  coord_sf(xlim = c(40, 210), ylim = c(-40, 40))

  • Compute the convex hull polygon based on the sf object "species.sf" with group_by(species) (for your general case)
hull <- species.sf %>%
  group_by(species) %>% 
  summarise( geometry = st_combine( geometry ) ) %>%
  st_convex_hull()
  • Convert back the sf object "hull" into the dataframe object "hullDF"
hullDF <- hull %>% 
  st_geometry() %>% 
  st_coordinates() %>% 
  as.data.frame() 
  • Vizualisation of the final result
map + 
  geom_point(data = df, mapping = aes(x = longitude, y = latitude)) +
  geom_polygon(data = hullDF, mapping = aes(x = X, y = Y), fill = "lightgreen", alpha = 0.5)

  • Compute area of the hull polygon (needs the units library to convert the result in squared km)
library(units)

hull_area <- hull %>%  
  st_area() %>% 
  set_units(km^2)

hull_area
#> 74714882 [km^2]

Created on 2021-11-12 by the reprex package (v2.0.1)

lovalery
  • 4,524
  • 3
  • 14
  • 28
  • Hi @Gemma Galbraith. I hope the above answer meets your needs. If so, please consider marking this answer as "accepted" to make it easier for other SO users to find the right answers. If not, please tell me what is wrong. Cheers – lovalery Nov 12 '21 at 16:07
  • Thanks @lovalery, this is exactly the solution I am after. Would st_shift_longitude() also work if I wanted to add other spatial layers to this map? For example the coral reefs vector from the natural earth package? The goal here would be not only to map the coral reef habitat for a figure but also to calculate the area of coral reef within a convex hull. This might be better as a separate question though. Thanks for your help! – Gemma Galbraith Nov 12 '21 at 20:20
  • Hi @Gemma. You are welcome. Glad I could help and thanks for validating the answer. Always hard to make constructive comments without seeing the data but yes, I think you should be able to achieve your goal using the same approach developed here. I wish you the best in your work. Cheers. – lovalery Nov 12 '21 at 20:40
  • Another follow up to this question @lovalery. If I has multiple species in the occurrence data how would I apply the st_shift_longitude to the multiple hulls? I have tried implementing group_by which works for the first step: hulls<- fish %>% group_by(Species) %>% summarise( geometry = st_combine( geometry ) ) %>% st_convex_hull() # However, then an error following the shift st_crs(hulls) <- 4326 hulls<-group_by(Species) %>% st_shift_longitude(st_geometry(hulls)) Error in st_shift_longitude(., st_geometry(hulls)) : unused argument (st_geometry(hulls)) – Gemma Galbraith Nov 13 '21 at 03:52
  • Hi @Gemma. O.K. I'll give you an answer in two steps. 1°) the error message you get is related to a syntax error in your "tidy" code. For this to work, the part `%>% st_shift_longitude(st_geometry(hulls))` should be modified as follows: `%>%st_geometry()%>%st_shift_longitude()` (furthermore, "hull" should not be indicated in the brackets). But, don't make any changes, wait for point 2 ;-) – lovalery Nov 13 '21 at 13:46
  • 2°) Reading the code again this morning, I realized that I made a stupid mistake: it is useless to execute the `st_shift_longitude()` function on convex hull polygons because the points have already been shifted. Once is enough !!!! Therefore the code can be lightened. – lovalery Nov 13 '21 at 13:47
  • Conclusion: I have edited my answer above to (i) remove this unnecessary step, (ii) integrate the `group_by(species)` into the code so that it can be applied to your real data (i.e. your fish data) (iii) provide you with a fully tidyverse version of the code (as I understand that is the syntax you prefer). I hope this will solve your problem efficiently. If it does, feel free to upvote my answer ;-) Cheers. – lovalery Nov 13 '21 at 13:47
  • Last point: in the function `group_by()` I wrote `species` with a lower case "s" because in the `sf` object `species.sf` it is written that way. Reading you, I think that in your real data set (i.e. fish data) it is written with a capital "S". So, if that's the case, don't forget to make this modification so that everything runs smoothly. Cheers. – lovalery Nov 13 '21 at 13:53