R cran sf create a polygon from two sewed shapefiles - r

My final aim is to create a map of the Vermeille Coast in order to calculate the distance between two sampling points with the condition that the path between the two points is not crossing the land.
To do so:
1/ I took two shapefiles (you can download them here: https://www.dropbox.com/sh/hzsdklnmvjg4hsz/AAATHLV0pkJXDvSqyRIBlVl_a?dl=0)
2/ I sew them according to : R cran: sf Sew two MULTILINESTRING/LINESTRING
3/ try to create the associated polygons according to that: Sf package: Close a polygon fom complex shape
The script is the following:
frenchCoast_CoteBanyuls <- st_read("coasts_subnational/coasts_subnational.shp")
spainCoast_CoteBanyuls <- st_read("coasts_subnational SPAIN/coasts_subnational.shp")
combined_coast <- rbind(spainCoast_CoteBanyuls, frenchCoast_CoteBanyuls)
plot(combined_coast$geometry)
I get the following plot:
which is correct so far. I just need now to create the associated polygon:
bbox_combined_coast <- st_bbox(combined_coast) %>%
st_as_sfc()
polygon_combined_coast <- bbox_combined_coast %>%
lwgeom::st_split(combined_coast) %>%
st_collection_extract("POLYGON")
par(mfrow = c(2,4), mar = c(0,0,0,0))
for(i in 1:8){
plot(polygon_combined_coast[i], col = 'steelblue')
}
which gave:
None of these polygons represents the shape of combined maps above.
Nevertheless, when I plot the polygon from every single shapefile:
par(mfrow = c(1,2), mar = c(0,0,0,0))
bbox_frenchCoast_CoteBanyuls <- st_bbox(frenchCoast_CoteBanyuls) %>%
st_as_sfc()
polygon_frenchCoast_CoteBanyuls <- bbox_frenchCoast_CoteBanyuls %>%
lwgeom::st_split(frenchCoast_CoteBanyuls) %>%
st_collection_extract("POLYGON")
plot(polygon_frenchCoast_CoteBanyuls[1], col = 'steelblue')
bbox_spainCoast_CoteBanyuls <- st_bbox(spainCoast_CoteBanyuls) %>%
st_as_sfc()
polygon_spainCoast_CoteBanyuls <- bbox_spainCoast_CoteBanyuls %>%
lwgeom::st_split(spainCoast_CoteBanyuls) %>%
st_collection_extract("POLYGON")
plot(polygon_spainCoast_CoteBanyuls[3], col = 'steelblue')
It gave:
these two polygons are correctly shaping the polygon associated to each shapefile.
It seems that rbinding the two shapefiles makes something not expected.
Do you have an idea of the mistake ?
Thanks in advance,
Charlotte

To combine the two coast lines summarize with no grouping variable
combined_coast <- rbind(spainCoast_CoteBanyuls, frenchCoast_CoteBanyuls) %>% summarise()
Note that it will always be a MULTILINESTRING because there are several small islands off the coast so cannot be a single LINESTRING
can turn that into polygons with
combined_coast_poly<- combined_coast %>% st_cast("POLYGON")

The problem is that both coastlines does not touch, that's why the polygon approach doesn't work.
See here a solution, where I merge the two main coastlines, add the small islands in Spain and try the approach you provided:
# Download files
spainurl <- "https://geo.vliz.be/geoserver/wfs?request=getfeature&service=wfs&version=1.0.0&typename=MarineRegions:coasts_subnational&outputformat=SHAPE-ZIP&filter=%3CPropertyIsEqualTo%3E%3CPropertyName%3Emrgid_1%3C%2FPropertyName%3E%3CLiteral%3E3417%3C%2FLiteral%3E%3C%2FPropertyIsEqualTo%3E"
download.file(spainurl, "spain.zip", mode = "wb")
unzip("spain.zip", exdir = "spain", junkpaths = TRUE)
franceurl <- "https://geo.vliz.be/geoserver/wfs?request=getfeature&service=wfs&version=1.0.0&typename=MarineRegions:coasts_subnational&outputformat=SHAPE-ZIP&filter=%3CPropertyIsEqualTo%3E%3CPropertyName%3Emrgid_1%3C%2FPropertyName%3E%3CLiteral%3E19888%3C%2FLiteral%3E%3C%2FPropertyIsEqualTo%3E"
download.file(franceurl, "france.zip", mode = "wb")
unzip("france.zip", exdir = "france", junkpaths = TRUE)
library(sf)
library(tidyverse)
spainCoast_CoteBanyuls <- list.files("spain",
pattern = "shp$",
full.names = TRUE) %>% st_read()
frenchCoast_CoteBanyuls <- list.files("france",
pattern = "shp$",
full.names = TRUE) %>% st_read()
ggplot(spainCoast_CoteBanyuls) +
geom_sf() +
geom_sf(data = frenchCoast_CoteBanyuls)
Ok, now extract every single LINESTRING of the object. France only has 1.
# A. Decompose in linestrings
lines_spain <- st_geometry(spainCoast_CoteBanyuls) %>% st_cast("LINESTRING")
spainCoast_l <- st_sf(n = as.character(seq_len(length(lines_spain))), lines_spain)
ggplot(spainCoast_l) +
geom_sf(aes(color = n), size = 3)
lines_france <- st_geometry(frenchCoast_CoteBanyuls) %>% st_cast("LINESTRING")
franceCoast_l <- st_sf(n = as.character(seq_len(length(lines_france))), lines_france)
ggplot(franceCoast_l) +
geom_sf(aes(color = n), size = 3)
See if France and Spain touches (spoiler: no)
st_touches(lines_france, lines_spain, sparse = FALSE)
#> [,1] [,2] [,3] [,4] [,5] [,6]
#> [1,] FALSE FALSE FALSE FALSE FALSE FALSE
# Lines doesn't touch. Merge both main coastlines
spainmax <- spainCoast_l[which.max(st_length(spainCoast_l)), ]
spainrest <- spainCoast_l[-which.max(st_length(spainCoast_l)), ]
ggplot(spainmax) +
geom_sf() +
geom_sf(data = franceCoast_l)
ggplot(spainrest) +
geom_sf()
Here I merge both LINESTRINGs with st_union():
# Merge
joined <- c(st_geometry(spainmax), st_geometry(franceCoast_l)) %>%
st_union()
ggplot(joined) +
geom_sf()
And now the task is to reassamble the small parts of Spain and apply the lwgeom::st_split() approach.
# Ok, we are ready
# Get the rest of pieces of Spain
join_end <- st_union(joined, st_geometry(spainrest))
ggplot(join_end) +
geom_sf()
bbox_all <- st_bbox(joined) %>%
st_as_sfc()
polygon_joined <- bbox_all %>%
lwgeom::st_split(join_end) %>%
st_collection_extract("POLYGON")
#Polygons on position 2 and 3 need to be removed (visual inspection)
polygon_end <- polygon_joined[-c(2:3)]
ggplot(polygon_end) +
geom_sf()
Created on 2022-06-15 by the reprex package (v2.0.1)

Related

Plot filled areas for sea/ocean and land mass based on {osmdata} using {ggplot2} [duplicate]

This question already has an answer here:
Sf package: Close a polygon fom complex shape
(1 answer)
Closed 5 months ago.
The reprex below shows how I would like to create a map via {osmdata} and {ggplot2} that has sea/ocean in it. I want to color-fill the land and/or sea area. However, it seems unexpectedly difficult to do so. This blog post even claims that it cannot be done.
This vignette of {osmplotr} seems to have to the solution: "Because OpenStreetMap represents coastline as line objects, all coastline data is contained within the $osm_lines object. The osm_line2poly() function can then convert these lines to polygons which can be used to plot filled areas.". Yet, just as in this similar StackOverflow question, the function throws an error as can be seen at the bottom of the reprex. I also found here that the {tigris} package can provide the necessary polygon data - but only for the US.
So how can I get this to work?
library(osmdata)
library(osmplotr)
library(sf)
library(tidyverse)
# define example bbox
bb <- tribble(
~xy, ~min, ~max,
"x", 12.00, 12.18,
"y", 54.08, 54.20
) %>% column_to_rownames("xy") %>% as.matrix()
# get "water"
water <- opq(bb) %>%
add_osm_feature(key = "natural", value = "water") %>%
osmdata_sf()
# get "coastline"
coast <- opq(bb) %>%
add_osm_feature(key = "natural", value = "coastline") %>%
osmdata_sf()
# ggplot
ggplot() +
geom_sf(
data = water$osm_multipolygons,
fill = "navy",
color = NA
) +
geom_sf(
data = coast$osm_lines,
fill = "navy",
color = "blue"
)
# trying osm_line2poly()
osmplotr::osm_line2poly(coast$osm_lines, bb)
#> Error in FUN(X[[i]], ...): unbenutztes Argument (V = c(3, 1, 6, 7, 2, NA, 5))
Created on 2022-09-23 with reprex v2.0.2
Thanks to #JindraLacko, I was able to make my reprex work. Basically, we create a rectangle/polygon which is the size of our bbox and then split it via the coastline.
library(lwgeom)
library(osmdata)
library(osmplotr)
library(sf)
library(tidyverse)
### define example bbox
lon_min <- 12.00 # xmin
lon_max <- 12.18 # xmax
lat_min <- 54.08 # ymin
lat_max <- 54.20 # ymax
bb <- get_bbox(c(lon_min, lat_min, lon_max, lat_max))
### get "water" that is not sea as polygons
water <- opq(bb) %>%
add_osm_feature(key = "natural", value = "water") %>%
osmdata_sf()
### get sea & land as polygons
# 1. get coastline (as line)
coast <- opq(bb) %>%
add_osm_feature(key = "natural", value = "coastline") %>%
osmdata_sf()
# 2. get overall rectangle for bbox
bb_rect <- data.frame(
lat = c(lat_min, lat_max),
lon = c(lon_min, lon_max)
) %>%
st_as_sf(coords = c("lon", "lat"), crs = 4326) %>%
st_bbox() %>%
st_as_sfc()
# 3. split overall rectangle for bbox via coastline
bb_rect_split <- bb_rect %>%
st_split(coast$osm_lines) %>%
st_collection_extract("POLYGON")
# 4. extract splitted rectangle parts
land <- bb_rect_split[1]
sea <- bb_rect_split[2]
### ggplot
ggplot() +
geom_sf(
data = land,
fill = "bisque",
color = NA
) +
geom_sf(
data = sea,
fill = "navy",
color = NA
) +
geom_sf(
data = water$osm_multipolygons,
fill = "navy",
color = NA
)
Created on 2022-09-26 with reprex v2.0.2

Adjust centroids spatial polygons using sf

I have a shapefile of local government regions. I use sf_read() to import it into R as an SF object. I want to compute the distance between the local government regions. st_centroid() gives me polygon centroids and I can compute distance using st_distance().
regions <- st_read("~/Downloads/regions.shp")
regions_with_centroids <- st_centroid(regions)
extract_centroids <- regions_with_centroids %>%
st_drop_geometry() %>%
as_tibble() %>%
select(region_name, centroid)
# create edge list
edge_list <- extract_centroids %>%
select(region_name) %>%
expand(from = region_name, to = region_name) %>%
filter(from < to) %>%
left_join(extract-centroids, by = c("from" = "region_name) %>%
rename(from_centroid = centroid) %>%
left_join(extract-centroids, by = c("to" = "region_name) %>%
rename(to_centroid = centroid) %>%
mutate(distance = st_distance(from_centroid, to_centroid)
However, I really want to analyze the commuting distance between major urban areas in each government region. I need to shift the centroids to the population "centre of gravity".
I can use a shapefile of census enumerator areas to help me with this. The enumerator areas are sized by population. Using st_intersection() I can intersect the enumerator areas with the government regions. This gives me sub-regions within each government region. I can compute centroids for all the sub-regions. Grouping by region, I can compute the mean centroid for all the sub-regions in a region. The mean centroid = "centre of gravity", which gives a more realistic commute distance between regions.
regions <- st_read("~/Downloads/regions.shp")
ea <- st_read("~/Downloads/enumerator_areas.shp")
intersected <- st_intersection(regions, ea)
sub_region_centroids <- st_centroids(intersected)
Where I run into difficulty is how to find the mean centroid. Grouping by region is not working.
mean_centroid <- sub_region_centroids %>%
group_by(region_name) %>%
summarise(mean_centroid = mean(geometry))
Warning messages:
1: In mean.default(geometry) :
the argument is not numeric or logical: returning NA
Where am I going wrong?
I also do not know how to add the mean centroid back to the original region's object.
I hope someone can assist me.
Computing a population weighted average of multiple centroids is an interesting problem.
You can consider approach like this - where I calculate the weighted centroid of three cities in North Carolina (to make use of the well known & much loved nc.shp file that ships with {sf}).
The workflow uses tidyr::uncount() to first multiply the city points per population, the (many) multiplied points are then united to a single multipoint feature. And multipoint features have defined sf::st_centroid() operation (QED). The final sf::st_as_sf() is just a polish.
library(sf)
library(dplyr)
library(ggplot2)
# included with sf package
shape <- st_read(system.file("shape/nc.shp", package="sf"))
# dramatis personae; population as per Wikipedia
cities <- data.frame(name = c("Raleigh", "Greensboro", "Wilmington"),
x = c(-78.633333, -79.819444, -77.912222),
y = c(35.766667, 36.08, 34.223333),
population = c(467665, 299035, 115451)) %>%
st_as_sf(coords = c("x", "y"), crs = 4326)
# a quick overview of facts on ground
ggplot() +
geom_sf(data = shape) + # polygon of North Carolina
geom_sf(data = cities, color = "red") # 3 cities
# unweighted centroid / a baseline
plain_center <- cities %>%
st_geometry() %>% # pull just geometry
st_combine() %>% # from many points to a single multipoint
st_centroid() %>% # compute centroid of the multipoint
st_as_sf() # make it a sf object again
# the fun is here!!
center_of_centers <- cities %>%
tidyr::uncount(population) %>% # multiply rows according to population
st_geometry() %>% # pull just geometry
st_combine() %>% # from many points to a single multipoint
st_centroid() %>% # compute centroid of the multipoint
st_as_sf() # make it a sf object again
# finished result
ggplot() +
geom_sf(data = shape, color = "gray75") + # polygon of North Carolina
geom_sf(data = cities, color = "red") + # 3 cities
geom_sf(data = plain_center, color = "green") + # unweighted center
geom_sf(data = center_of_centers, color = "blue", pch = 4) # population weighted center
Following #Jindra Lacko's nice example, here is how it can be done taking the weighted mean of the lat and long.
library(sf)
library(dplyr)
library(ggplot2)
# weighted mean of lat and long
center_weighted <- cities %>%
mutate(lon = sf::st_coordinates(.)[,1],
lat = sf::st_coordinates(.)[,2]) %>%
st_drop_geometry() %>%
summarize(across(c(lon, lat), weighted.mean, w = population)) %>%
st_as_sf(coords = c("lon", "lat"), crs = 4326)
# plot it
ggplot() +
geom_sf(data = shape, color = "gray75") +
geom_sf(data = cities, color = "red") +
geom_sf(data = center_weighted, color = "blue", pch = 4)
Data
# set up example data
shape <- st_read(system.file("shape/nc.shp", package="sf"))
cities <- data.frame(name = c("Raleigh", "Greensboro", "Wilmington"),
x = c(-78.633333, -79.819444, -77.912222),
y = c(35.766667, 36.08, 34.223333),
population = c(467665, 299035, 115451)) %>%
st_as_sf(coords = c("x", "y"), crs = 4326)

Overlapping shp maps in sf

I have two shapefiles that I read into R with sf.
The first shp file covers regions.
The second shp file covers administrative districts.
The electoral districts are nested into regions.
I would like to overlay the two maps, then coloring each electoral district in a shade of the same color, having one color for each region.
I can plot the two and play around with colors but cannot overlay and coloring.
Files can be accessed here from the Italian National Institute of Statistics :
Reg1991_WGS84.shp:
http://www.istat.it/storage/cartografia/confini_amministrativi/non_generalizzati/Limiti1991.zip
CAMERA_PLURI_2017.shp: https://www.istat.it/storage/COLLEGI_ELETTORALI_2017.zip
library(sf)
italia_regions_1991<- read_sf("Limiti1991/Reg1991/Reg1991_WGS84.shp") %>% select(geometry)
italia_camera_pluri <- read_sf("COLLEGI_ELETTORALI_2017/CAMERA_PLURI_2017.shp") %>% select(geometry)
This will get you started....
I used the leafgl library, since you are plotting alot of polylines/plygons... This performs (pretty)fast...
library(sf)
library(devtools)
library(leaflet)
#install leaflet with gl-suport
devtools::install_github("r-spatial/leafgl")
library(leafgl)
library(colourvalues)
#read shapefile regions and cast to polygons
sf1 <- st_read( "e:/two_shapes/Limiti1991/Reg1991/Reg1991_WGS84.shp" ) %>% st_cast( "POLYGON", warn = FALSE )
#read shapefile and cast to POLYGON and then to LINESTRING
sf2 <- st_read( "e:/two_shapes/COLLEGI_ELETTORALI_2017/COLLEGI_ELETTORALI_2017.shp") %>%
st_cast( "POLYGON", warn = FALSE ) %>%
st_cast( "LINESTRING", warn = FALSE )
#creaae color matrix for the regions( depending om DEN_REG), and for the polylines (=black)
col_region <- colour_values_rgb(sf1$DEN_REG, include_alpha = FALSE) / 255
col_lines <- matrix(data = c(0,0,0), nrow = 1 )
#plot leaflet (takes some time)
leaflet() %>% addTiles() %>%
addGlPolygons(data = sf1, color = col_region) %>%
addGlPolylines( data = sf2, color = col_lines)
result
Consider intersecting the regions & districts via sf::st_intersection - note however that there seems to be some overlap, as the regions and districts do not align perfectly (they mostly do, but not quite...)
I have also transformed the CRS to WGS84; perhaps not necessary, but works better with leaflet and the like...
library(sf)
library(dplyr)
library(ggplot2)
italia_regions_1991<- read_sf("Reg1991_WGS84.shp") %>%
select(region = DEN_REG) %>% # this, and geometry by default
st_transform(4326)
italia_camera_pluri <- read_sf("CAMERA_PLURI_2017.shp") %>%
select(geometry) %>% # only geometry...
st_transform(4326)
result <- italia_camera_pluri %>%
st_intersection(italia_regions_1991)
ggplot(data = result, aes(fill = region)) +
geom_sf()

Create a map with colored polygons and coordinate points by using a .shp file in combination with another dataframe with coordinates

I have the following map boundaries in this .gdb folder
and here I have a csv which contains the variables that I want to plot and the coordinates of the points that need to be displayed on the map. My final goal is to create a map with polygons and inside every polygon there should be points according to the coordinates. Every polygon should be colored according to the count of studentid (students) for the year 2019. Any alternative is accepted
I believe that the 1st code chunk below is correct:
library(sf)
library(tidyverse)
library(data.table)
library(tigris)
library(leaflet)
library(mapview)
options(tigris_use_cache = TRUE)
# To keep enough digits on coords
options(digits = 11)
#coordinate reference system (long-lat system)
cr_sys = 4326
# Shp file for hs boundaries (constitutes overall district bounds)
hs_bounds <- st_read("C:/Users/makis/Documents/school/TPS_schools.shp")
# Read the feature class
#fc <- readOGR(dsn=fgdb )
#fc <- spTransform(fc, CRS("+proj=longlat +datum=WGS84 +no_defs"))
# Convert hs_bounds into longlat coord system
hs_bounds <- hs_bounds %>%
st_transform(4326)
tmp <- list.files(pattern = "school_report_data_fake.csv")
raw_master <- lapply(tmp,
function(x) read_csv(x,guess_max = 5000)) %>%
rbindlist(., fill = TRUE)
# r blocks in tps
tps_blocks <- blocks(state = "OK") %>%
st_as_sf() %>%
st_transform(crs = 4326) %>%
st_intersection(hs_bounds)
tps_bgs <- block_groups(state = "OK") %>%
st_as_sf() %>%
st_transform(crs = 4326) %>%
st_intersection(hs_bounds)
mapview(hs_bounds)
# Display all tps block groups on interactive map
tps_blocks_map <- mapview(tps_bgs) %>%
addFeatures(., hs_bounds)
# convert to df and remove geometry bc its a list col
tps_blocks_df <- tps_blocks %>%
as.data.frame() %>%
select(-geometry)
# Export blocks in tps. GEOID10 is the unique identifier for the block
write_csv(tps_blocks_df, path = "C:/Users/makis/Documents/school/tps_blocks.csv")
Here Im trying to include the student data as well but Im concluding in adataframe with zero data
#r students by geography
student_geos <- raw_master %>%
#filter for students active in a given year
filter(year == 2019) %>%
# filter(row_number() %in% sample(length(year), 20000)) %>%
# Parse lat/long. I believe that I should do something here with the lat and long
#and some variable of the csv like the geocode variable that is used here
#a similar should be present in my csv file as well
#mutate(lat = as.numeric(str_extract(geocode, "[0-9]+.[0-9]+"))) %>%
#mutate(lon = as.numeric(str_extract(geocode, "-[0-9]+.[0-9]+"))) %>%
# Please don't ask me why this rowwise is necessary
rowwise() %>%
# Create sf point for each set of coords
mutate(pt = st_sfc(st_point(x = c(lon, lat)), crs = 4326)) %>%
# Turn df into sfc then take intersection of pts and blocks
st_as_sf() %>%
st_intersection(tps_blocks)
# convert to df and remove geometry bc its a list col
student_geos_df <- student_geos %>%
as.data.frame() %>%
select(-pt)
If everything above is correct i should do something like:
# enrollment by tract
tract_enrol <- student_geos %>%
as.data.frame() %>%
group_by(year, TRACTCE10) %>%
summarize(enrollment = n())
# convert list of tracts into sfc
tracts <- tracts(state = "OK",
county = c("Tulsa", "Osage", "Wagoner", "Creek"),
year = 2010) %>%
st_as_sf() %>%
as.data.frame() %>%
#I guess student id instead of TRACTE10 here
inner_join(tract_enrol, by = "TRACTCE10") %>%
st_as_sf()
mapview(tracts, zcol = "enrollment", legend = TRUE)
Your file still doesn't download.
I can give you a generic guide to use ggplot2 to make a map. This will draw the polygons and the points.
You need to modify the Spatial_DataFrames with fortify() to get them into a format ggplot2 can use.
library(ggplot2)
hs_b2 <- fortify(hs_bounds) #or instead of "hs_bounds", "tracts" or whatever your polygon
#is called. If that doesn't work you need "<-as.data.frame()".
#Make sure the output has a separate column for x and y.
#repeat for the points (student) object.
student_2 <- fortify(<studentpointsobject>)
ggplot(data=<student_2>, aes(x=x,y=y)) +
geom_polygon(data=hs_b2, aes(x=long, y=lat, group=group) , #this will create the polygon
colour="grey90", alpha=1, #one of your color options for polygons
fill="grey40") + #one of your color options for polygons
theme(axis.ticks.y = element_blank(),axis.text.y = element_blank(), # get rid of x ticks/text
axis.ticks.x = element_blank(),axis.text.x = element_blank()) + # get rid of y ticks/text
geom_point(aes(color="grey20")) #to get the points drawn. mess with "fill" and "color"
You can customize the plot with 'color' or 'fill' in the aes().

Proximity Maps using R

I'm looking to create some proximity maps using R, which show how far areas are from certain points. I can't find any examples in R code, but I've found an output which is the sort of thing I want:
It doesn't necessarily have to have all the labelling/internal boundaries wizardry, but I'd like it to stop at the sea border (thinking of using the rgeos function gintersection - see here).
I've tried doing a density plot as 'heatmaps' (this would be a pretty good solution/alternative) and putting a shapefile over the top (following this suggestion, but they're not lining up and I can't do a gintersection, probably because there's not a coordinate system attached to the density plot.
I used your question to play a little with new libraries...
Get a UK map and define random points
library(raster)
library(sf)
library(ggplot2)
library(dplyr)
library(tidyr)
library(forcats)
library(purrr)
# Get UK map
GBR <- getData(name = "GADM", country = "GBR", level = 1)
GBR_sf <- st_as_sf(GBR)
# Define 3 points on the UK map
pts <- matrix(c(-0.4966766, -2.0772529, -3.8437793,
51.91829, 52.86147, 56.73899), ncol = 2)
# Project in mercator to allow buffer with distances
pts_sf <- st_sfc(st_multipoint(pts), crs = 4326) %>%
st_sf() %>%
st_transform(27700)
ggplot() +
geom_sf(data = GBR_sf) +
geom_sf(data = pts_sf, colour = "red")
Calculate buffer areas
We create a list of multipolygons for each buffer distance. The point dataset must be in projected coordinates (here mercator) as buffer distance is in the scale of the coordinates system.
# Define distances to buffer
dists <- seq(5000, 150000, length.out = 5)
# Create buffer areas with each distances
pts_buf <- purrr::map(dists, ~st_buffer(pts_sf, .)) %>%
do.call("rbind", .) %>%
st_cast() %>%
mutate(
distmax = dists,
dist = glue::glue("<{dists/1000} km"))
# Plot: alpha allows to see overlapping polygons
ggplot() +
geom_sf(data = GBR_sf) +
geom_sf(data = pts_buf, fill = "red",
colour = NA, alpha = 0.1)
Remove overlapping
Buffer areas are overlapping. On the figure above, the more intense red color is due to multiple overlapping layers of transparent red. Let's remove the overlapping. We need to remove from larger areas, the buffer with the lower size. I then need to add again the smallest area to the list.
# Remove part of polygons overlapping smaller buffer
pts_holes <- purrr::map2(tail(1:nrow(pts_buf),-1),
head(1:nrow(pts_buf),-1),
~st_difference(pts_buf[.x,], pts_buf[.y,])) %>%
do.call("rbind", .) %>%
st_cast() %>%
select(-distmax.1, -dist.1)
# Add smallest polygon
pts_holes_tot <- pts_holes %>%
rbind(filter(pts_buf, distmax == min(dists))) %>%
arrange(distmax) %>%
mutate(dist = forcats::fct_reorder(dist, distmax))
# Plot and define color according to dist
ggplot() +
geom_sf(data = GBR_sf) +
geom_sf(data = pts_holes_tot,
aes(fill = dist),
colour = NA) +
scale_fill_brewer(direction = 2)
Remove areas in the sea
If you want to find proximity area on terrestrial parts only, we need to remove buffer areas that are in the sea. Intersection is computed between multipolygons with the same projection. I previously realize an union of the UK map.
# Remove part of polygons in the sea
# Union and projection of UK map
GBR_sf_merc <- st_transform(st_union(GBR_sf), 27700)
pts_holes_uk <- st_intersection(pts_holes_tot,
GBR_sf_merc)
ggplot() +
geom_sf(data = GBR_sf) +
geom_sf(data = pts_holes_uk,
aes(fill = dist),
colour = NA) +
scale_fill_brewer(direction = 2)
And here is the final proximity map using sf, ggplot2 and a few other libraries...
Based on Sébastien's example, a more old-fashioned approach:
library(raster)
GBR <- getData(name = "GADM", country = "GBR", level = 1)
pts <- matrix(c(-0.4966766, -2.0772529, -3.8437793, 51.91829, 52.86147, 56.73899), ncol = 2)
r <- raster(GBR, res=1/12)
d <- distanceFromPoints(r, pts)
m <- mask(d, GBR)
plot(m)

Resources