Creating Circles with st_buffer at Multiple Geographic Locations

Introduction to Making Circles with st_buffer at Multiple Geographic Locations

In this article, we will explore a problem involving creating circles from a dataframe containing longitudes and latitudes. The radius of every circle needs to be precisely 400 Nautical Miles (NM). We will discuss the challenges associated with using the st_buffer function in sf package, which takes distance arguments in degrees, but also provide a solution using a custom function to find the UTM zone proj4string for each point.

Background on Coordinate Reference Systems

The Earth’s surface is divided into several coordinate reference systems (CRS) to represent its curvature and projection biases. The most commonly used CRS are:

  • WGS84: This is an ellipsoidal model that assumes a spheroid shape of the Earth.
  • UTM (Universal Transverse Mercator): This is a pseudocylindrical projection that is well-suited for mapping at high latitudes.

Solution Overview

To solve this problem, we will follow these steps:

  1. Define a function to find the UTM zone proj4string from latitude and longitude for each point.
  2. Convert each point according to its respective UTM zone using the st_transform function in sf package.
  3. Get a buffer for each point separately using the st_buffer function in sf package.

Step-by-Step Solution

Step 1: Define a Function to Find the UTM Zone Proj4string

We will use the purrr package to create a custom function called utm_prj4. This function takes a dataframe of points and returns a list of dataframes with different CRS, each corresponding to a specific UTM zone.

library(purrr)
utm_prj4 <- function(x) {

  coords <- cbind(x, st_coordinates(x))

  long <- coords$X
  lat <- coords$Y

  zone <- if(lat >= 56 && lat < 64 && long >= 3 && long < 12){x <- 32} else if(
    lat >= 72 && lat < 84 && long >= 0 && long < 9) {x <- 31} else if(
      lat >= 72 && lat < 84 && long >= 9 && long < 21) {x <- 33} else if(
        lat >= 72 && lat < 84 && long >= 21 && long < 33) {x <- 35} else if(
          lat >= 72 && lat < 84 && long >= 33 && long < 42) {x <- 37} else{
            x <- (floor((long + 180)/6) %% 60) + 1
          }
  prj <- purrr::map2_chr(zone, lat, function(y, z){
    if (z >= 0){
      paste0("+proj=utm +zone=", y, " +datum=WGS84 +units=m +no_defs")
    } else{
      paste0("+proj=utm +zone=", y, " +south", " +datum=WGS84 +units=m +no_defs")
    }})
  prj
}

Step 2: Convert Each Point According to Its Respective UTM Zone

We will use the st_transform function in sf package to convert each point according to its respective UTM zone.

# creates a list of dataframes, each with different crs

dfs <- map2(1:4, utm_prj4(df), function(x, y){
  st_transform(df[x,], y)
})

Step 3: Get a Buffer for Each Point Separately

We will use the st_buffer function in sf package to get a buffer for each point separately.

# get buffer for each point separately
dfs <- map(dfs, ~ st_buffer(., rad)) %>% unlist()

Example Use Case

Let’s say we have a dataframe df with 8000 rows representing airports all over the world. We want to create circles from this dataframe with a radius of precisely 400 Nautical Miles (NM).

library(sf)
library(units)

# define nautical miles (as per ICAO notation)
NM <- make_unit("NM")
install_conversion_constant("NM", "km", 1.852)

# sample data:
df <- data.frame(lon = c(45,47,1, -109), lat = c(7, 10, 59, 30))

# create simple features with sf
df <- df %>% st_as_sf(coords = c("lon", "lat"), dim = "XY")

# apply coordinate reference system WGS84:
df <- df %>% st_set_crs(4326)

# define radius of interest which is 400 NM
rad <- set_units(400, NM) %>% set_units(km) %>% set_units(m)

# create circles
dfs <- map2(1:4, utm_prj4(df), function(x, y){
  st_transform(df[x,], y)
})

# get buffer for each point separately
dfs <- map(dfs, ~ st_buffer(., rad)) %>% unlist()

# visualize using mapview:
mapview(dfs)

This solution provides a way to create circles from a dataframe with longitudes and latitudes, taking into consideration the radius of every circle. It uses a custom function utm_prj4 to find the UTM zone proj4string for each point and then converts each point according to its respective UTM zone using st_transform. Finally, it gets a buffer for each point separately using st_buffer.

Conclusion

In this article, we have discussed the challenges associated with using the st_buffer function in sf package, which takes distance arguments in degrees. We have also provided a solution using a custom function to find the UTM zone proj4string for each point and then converting each point according to its respective UTM zone.

We hope that this article has been helpful in providing a way to create circles from a dataframe with longitudes and latitudes, taking into consideration the radius of every circle. If you have any questions or need further clarification on any of the steps, please feel free to ask.


Last modified on 2023-07-13