tidyverse - Calculate average distance to coastline in R - Stack Overflow

admin2025-04-17  4

I'm using R and the terra package for spatial data operations.

For a number of points on Earth, I want to calculate the average distance to a coastline. So far, I have managed to calculate the minimum distance to a coastline:

# libraries
library(tidyverse)
library(sf)
library(terra)
library(rnaturalearth)


# generate a pixel raster at 200 km resolution
rasterPx_200km     <- terra::rast(resolution = 200000,              # 200km x 200 km resolution
                                  xmin = -17367530, xmax=17432470,  # custom extent (without Antarctica)
                                  ymin=-6045743, ymax=7154257,
                                  crs = "EPSG:6933")                # metric WGS84 


# convert into polygons
rasterPoly_200km      <- terra::as.polygons(rasterPx_200km, crs = "epsg:6933")  # raster to poly
rasterPoly_200km$rid  <- 1:nrow(rasterPoly_200km)                               # add raster id



# get center points
centroidPoints          <- terra::centroids(rasterPoly_200km)   # SpatVector      
centroidPoints_df       <- centroidPoints %>% sf::st_as_sf()    # data frame

# get coastline from Natural Earth
coastline         <- rnaturalearth::ne_coastline(scale = 110)                      
coastline_union   <- sf::st_union(coastline)                        # union into MultiLineString
coastline_proj    <- sf::st_transform(coastline_union, crs = 6933)  # same projection as centroidPoints


# calculate shortest distance from coast
minDistanceCoast    <- st_distance(coastline_proj, # coastline as one multiline element
                                   centroidPoints_df, # points
                                   unit="m", 
                                   pairwise = TRUE)   # vector (columns contain distances)

# data frame with distance to coast
minDistanceCoast_df <- minDistanceCoast  %>%
  t()                                    %>%   # transpose to set up columns vs. rows for data frame
  as.data.frame()                        %>%   # make data frame
  rename("minDistanceCoast" = 1)               # rename first column


units(minDistanceCoast_df$minDistanceCoast) <- "km"  # set to kilometres (automatically makes a conversion)


# full data frame with raster IDs
minDistanceCoast_full <- centroidPoints_df %>%
  cbind(minDistanceCoast_df)

The resulting data frame minDistanceCoast_full contains the id of each point (called rid) and the minimum distance from the coastline (without differentiating for land and water pixels). How can I adapt my code to calculate the average distance for each point in centroidPoints_df to the coast line object coastline_proj? The result should be a data frame containing the columns rid and avgDistanceCoast.

My idea would be to generate rays from the points to a number of different directions and measure the distance whenever they touch a coastline; then averaging the distances. It would be good if the number of rays to calculate the average could be set in a function. I'd like to start with 8 distances in the cardinal and intercardinal directions (N, NE, E, ...).

I'm using R and the terra package for spatial data operations.

For a number of points on Earth, I want to calculate the average distance to a coastline. So far, I have managed to calculate the minimum distance to a coastline:

# libraries
library(tidyverse)
library(sf)
library(terra)
library(rnaturalearth)


# generate a pixel raster at 200 km resolution
rasterPx_200km     <- terra::rast(resolution = 200000,              # 200km x 200 km resolution
                                  xmin = -17367530, xmax=17432470,  # custom extent (without Antarctica)
                                  ymin=-6045743, ymax=7154257,
                                  crs = "EPSG:6933")                # metric WGS84 


# convert into polygons
rasterPoly_200km      <- terra::as.polygons(rasterPx_200km, crs = "epsg:6933")  # raster to poly
rasterPoly_200km$rid  <- 1:nrow(rasterPoly_200km)                               # add raster id



# get center points
centroidPoints          <- terra::centroids(rasterPoly_200km)   # SpatVector      
centroidPoints_df       <- centroidPoints %>% sf::st_as_sf()    # data frame

# get coastline from Natural Earth
coastline         <- rnaturalearth::ne_coastline(scale = 110)                      
coastline_union   <- sf::st_union(coastline)                        # union into MultiLineString
coastline_proj    <- sf::st_transform(coastline_union, crs = 6933)  # same projection as centroidPoints


# calculate shortest distance from coast
minDistanceCoast    <- st_distance(coastline_proj, # coastline as one multiline element
                                   centroidPoints_df, # points
                                   unit="m", 
                                   pairwise = TRUE)   # vector (columns contain distances)

# data frame with distance to coast
minDistanceCoast_df <- minDistanceCoast  %>%
  t()                                    %>%   # transpose to set up columns vs. rows for data frame
  as.data.frame()                        %>%   # make data frame
  rename("minDistanceCoast" = 1)               # rename first column


units(minDistanceCoast_df$minDistanceCoast) <- "km"  # set to kilometres (automatically makes a conversion)


# full data frame with raster IDs
minDistanceCoast_full <- centroidPoints_df %>%
  cbind(minDistanceCoast_df)

The resulting data frame minDistanceCoast_full contains the id of each point (called rid) and the minimum distance from the coastline (without differentiating for land and water pixels). How can I adapt my code to calculate the average distance for each point in centroidPoints_df to the coast line object coastline_proj? The result should be a data frame containing the columns rid and avgDistanceCoast.

My idea would be to generate rays from the points to a number of different directions and measure the distance whenever they touch a coastline; then averaging the distances. It would be good if the number of rays to calculate the average could be set in a function. I'd like to start with 8 distances in the cardinal and intercardinal directions (N, NE, E, ...).

Share asked Jan 31 at 12:42 MoehrengulaschMoehrengulasch 2411 silver badge10 bronze badges 3
  • For some of what you're describing, you should look at geosphere, and look at terra::extractAlong for coastlines after projection but without merging to multilinestring. – Chris Commented Jan 31 at 14:44
  • 1 An average distance relying on rays pointing in each cardinal distance? What if you're in Grosseto, Italy, for example? openstreetmap.org/search?lat=42.780&lon=11.123#map=7/42.771/… That North ray might throw off your average some, depending on what you think the average should be. – John Polo Commented Jan 31 at 15:31
  • @JohnPolo: Good point! I want to use this as an approximation for "connectivity" of a place to the rest of the land mass, so this is actually what I want (it would be more precise to use more directions, of course). – Moehrengulasch Commented Feb 2 at 12:02
Add a comment  | 

1 Answer 1

Reset to default 5

This is a cleaned-up answer that shows how to get the average distance to the coast from a point on land, when traveling along paths of constant bearing.

First a function to get the paths

get_dirs <- function(n) {
    dirs <- vect(cbind(0,0), crs="local") |> 
            buffer(360, quads=round(n/4)) |> crds() |> unique()
    dirs <- cbind(0, dirs[,1], 0, dirs[,2]) |> as.lines()
    values(dirs) <- data.frame(ID=1:nrow(dirs))
    crs(dirs) <- "+proj=longlat"
    dirs
}

And a function to get the average distance

get_dist <- function(land, directions, lonlat) {
    v <- vect(lonlat, crs="lonlat")
    i <- which(relate(v, land, "intersects"))
    if (length(i) == 0) return(NA)
    drs <- shift(directions, lonlat[1], lonlat[2])
    edr <- crop(drs, land[i]) |> disagg()
#   i <- which(relate(edr, buffer(v, 10), "intersects"))
    i <- which(round(distance(edr, v), -1) == 0)
    e <- densify(edr[i], 0.1, flat=TRUE)
    d <- perim(e) / 1000
    mean(d)
}

Example data, here using Africa. Remove the country boundaries, and disaggregate to separate islands/continents

library(terra)
land <- rnaturalearth::ne_countries(scale = 110, ret="sv")
africa <- land[land$continent == "Africa", ] |> aggregate() |> disagg()
# example points
pnts <- cbind(c(-10, 20, 20), c(10, -20, 20))

And a solution using 360 directions

dirs <- get_dirs(360)
sapply(1:nrow(pnts), \(i) get_dist(africa, dirs, pnts[i,,drop=F]))
# [1] 1804.092 2176.476 2766.308

Illustrated for the first point:

v <- vect(pnts[1,,drop=FALSE], crs="lonlat")
drs <- shift(dirs, pnts[1,1], pnts[1,2])
edr <- crop(drs, africa) |> disagg()
i <- which(round(distance(edr, v), -1) == 0)
plot(africa, col="light gray", border=NA, background="azure")
lines(edr, col="blue"); lines(edr[i], col="red")
points(edr[i], cex=.25); points(v)

转载请注明原文地址:http://anycun.com/QandA/1744861566a88658.html