Isolating a line from a polygon and measuring its length - r

I am trying to measure the length of a coastline. This will be used as a metric for position along a coastline in an analysis. For example, imagine I have data on the location of all public beaches in a region, and I want to describe how they are distributed in space by measuring their distance from a reference point on the coast.
I have followed this extremely helpful tutorial on calculating the length of a coastline using rulers of different lengths. However, it is only accurate if you want to measure the entire polygon length, that is, the geographic object you are interested in is an island.
To get a shapefile of a coastline (note that in the ne_countries call I am using a coarse scale on purpose, to make the coastline smoother, and only keeping the first shape returned--the "scalerank" name is not important):
library(raster)
library(sf)
library(rnaturalearth)
basemap <- rnaturalearth::ne_countries(scale = 110, country = "united states of america", returnclass = "sf")[1]
bbox <- extent(-82, -65, 27, 35)
cropmap <- st_crop(basemap, bbox)
plot(cropmap)
This returns a shape showing the South Atlantic coast down into Florida. However, if I measure the length of this shape, it will include all sides of the polygon--not just the coastline. How do I isolate the coastline (see below for a map of what part of the polygon is actually coastal) and just measure its length in R?
ggplot() +
geom_sf(data=basemap) +
geom_sf(data=cropmap, color="blue", fill="blue")

Here's a version similar to Robert's, staying in sf. I converted the original basemap from polygons to lines and then "cut out" the bbox like you did. Then you can just measure with st_length.
library(sf)
library(rnaturalearth)
basemap <- rnaturalearth::ne_countries(scale = 110, country = "united states of america", returnclass = "sf")[1]
Convert from polygon to lines to avoid the parts of the polygon you don't want
basemap_lines <- basemap %>% st_cast("MULTILINESTRING")
plot(basemap_lines)
Basemap as multilinestring
Then create the cookie cutter polygon, setting the crs to lat/lon
xmin <- -82
xmax <- -65
ymin <- 27
ymax <- 35
bbox <- st_polygon(list(rbind(c(xmin,ymin), c(xmin,ymax), c(xmax,ymax), c(xmax,ymin),c(xmin,ymin)))) %>% st_sfc()
st_crs(bbox) <- 4326
Then just crop with st_intersection
crop_lines <- st_intersection(basemap_lines, bbox)
plot(crop_lines)
st_length(crop_lines)
Cropped line
For your bbox dimensions I get 1162849 meters (~723 miles), which aligns with the other answer.

It is pretty easy if you transform the polygons to lines before the cropping
Example data
library(raster)
library(rnaturalearth)
m <- rnaturalearth::ne_countries(scale = 110, country = "united states of america", returnclass = "sp")
ext <- extent(-82, -65, 27, 35)
Transform and crop
m <- as(m, "SpatialLines")
croplines <- crop(m, ext)
Because the CRS is longitude/latitude, use geosphere, not rgeos, to compute the length
library(geosphere)
lengthLine(croplines)
#[1] 1162849
(i.e. 1162.8 km)
In some cases you may need intersect with a polygon instead of an extent (see raster::drawPoly and raster::intersect) or perhaps use crop, disaggregate, and visually select to get the lines you want.

Related

R: Convert sf polygon defined using lat/long to UTM

I have the following polygon, defined using degrees latitude/longitude:
## Define latitude/longitude
lats <- c(64.25086, 64.24937, 63.24105, 63.22868)
lons <- c(-140.9985, -136.9171, -137.0050, -141.0260)
df <- data.frame(lon = lons, lat = lats)
polygon <- df %>%
## EPSG 3578; Yukon Albers projection
st_as_sf(coords = c('lon', 'lat'), crs = 3578) %>%
summarise(geometry = st_combine(geometry)) %>%
st_cast('POLYGON')
When I plot it on a map using Tmap, it appears in the Pacific Ocean off the coast of British Columbia, rather than in the middle of the Yukon:
library(sf)
library(sp)
library(tmap)
library(dplyr)
library(magrittr)
library(leaflet)
m <- tm_shape(data$study_boundary) + tm_borders(col = 'black',
lwd = 5,
zindex = 1000)
m
I am guessing that the problem is in using lat/long rather than UTMs because I have other polygons defined using UTMs that do appear where they (and the polygon defined above) are supposed to be. I found several other posts going the other way (UTM to lat/long) using spTransform, but I haven't been able to go lat/long to UTM with spTransform. I tried the code below:
poly_utm <- st_transform(polygon, crs = "+proj=utm+7")
But that didn't work either.
Thanks!
This (which I've improved by removing the pipe):
st_as_sf(df, coords = c('lon', 'lat'), crs = 3578)
creates a spatial points data frame using the numbers in the data frame for the coordinates, and the crs code of 3578 as the label for what those numbers represent. It does not change the numbers.
It looks like those numbers are actually lat-long coordinates, which means they are probable crs code 4326, the lat-long system used for GPS, also known as WGS 84. But it might not be. But probably is. Do check. So anyway, you should do:
df_unprojected = st_as_sf(df, coords = c('lon', 'lat'), crs = 4326)
df_projected = st_transform(df_unprojected, 3578)
The st_transform function does the actual change of the coordinate numbers and assigns the new CRS code to the spatial data metadata. That should give you a set of points you can then plot and check they are in the right place before you throw it into summarise and st_cast.

st_transform appears to change the area of the geometry of a shapefile

Shouldn't the area of polygons remain the same when you transform the shapefile projection? I am currently working with the shapefile here (https://data.humdata.org/dataset/f814a950-4d4e-4f46-a880-4da5522f14c4/resource/731e11cb-be02-46cf-8347-db0a07abff4e/download/gin_admbnda_adm4_2021_ocha.zip) for the country GIN which I call gin_shp after I read it in with the st_read function in R as follows:
gin_shp <- st_read(dsn = "INPUT FOLDER", layer = "sous_prefectures")
gin_shp ##this shows the crs is WGS84 obviously in degrees
##compute the area of the multipolygon geometries
gin_shp$area <- sf::st_area(gin_shp) ##computes areas in m^2
gin_shp$area <- units::set_units(gin_shp$area, "km^2") ##converting the area to square kms
sum(gin_shp$area) ##compute total area of the entire country (google seems to agree!)
245084.2 [km^2]
I need to create 1sqkm grids (using sf::st_make_grid) so I tried to transform crs projection from degrees to UTM by calling the st_transform function as follows:
crs_dt <- rgdal::make_EPSG() ##first load the dataframe of CRS projections available in R
gin_shp <- sf::st_transform(gin_shp, crs = crs_dt$prj4[crs_dt$code == 4328]) ##select one and assign it to gin_shp
#now I try to compute total area under new projection regime by running the same code as before
gin_shp$area <- sf::st_area(gin_shp) ##computes areas in m^2
gin_shp$area <- units::set_units(gin_shp$area, "km^2") #converting the area to square kms
#compute total area of the entire country
sum(gin_shp$area)
44363.83 [km^2] (way way off)
Do you know why this might be happening? Any ideas how to fix it?
No, the area of polygons is not guaranteed to remain constant when transforming between different CRSes. The Earth is round, maps & computer screens are flat - something has to give; either area or shape has to be distorted somewhat.
There are some area preserving projections - such as the Mollweide - but these are more of exception than rule.
For an exaggerated example consider the world dataset, taken from {giscoR} package. Greenland (close to the pole) and Congo (on equator) have roughly the same area on sphere (calculated on WGS84 using spherical geometry tools) but wildly different one when projected. Especially when projected to Mercator and its derivatives (e.g. Web Mercator as used by Google maps).
library(sf)
library(dplyr)
library(giscoR)
library(ggplot2)
# the world in, 1: 20M
svet <- gisco_get_countries(resolution = "20")
# Greenland & Congo - to be drawn in red
glmd <- svet %>%
filter(CNTR_ID %in% c('GL', 'CD'))
# web mercator = default for google maps and other web based tools
ggplot() +
geom_sf(data = glmd, fill = "red", color = NA) +
geom_sf(data = svet, fill = NA, color = "gray45") +
coord_sf(crs = st_crs("EPSG:3857"),
ylim = c(-20e6, 20e6))
# Mollweide - equal area projection
ggplot() +
geom_sf(data = glmd, fill = "red", color = NA) +
geom_sf(data = svet, fill = NA, color = "gray45") +
coord_sf(crs = st_crs("ESRI:53009"))

R: scaling Alaska using maptools::elide

I'm building a shapefile of states where Alaska and Hawaii are represented as being somewhere south of Texas, for ease of making an illustrative map. Using maptools package and some code from https://rud.is/b/2014/11/16/moving-the-earth-well-alaska-hawaii-with-r/, I have been able to do this with shapes from TIGER.
However, I am running into trouble now that I want to add cities to my map. Making my shapes into sp objects and then using maptools::elide works fine for Alaska before, but elide with scale doesn't work the same way on a collection of points so my cities wind up in the wrong place:
library(maptools)
library(sf)
library(tmap)
library(tidyverse)
library(tidygeocoder)
ak_city_sf <-
tribble(~city_name, ~city_search_string,
"Juneau", "Juneau, Alaska, United States",
"Anchorage", "Anchorage, Alaska, United States",
"Utqiagvik", "Utqiagvik, Alaska, United States",
"Scammon Bay", "Scammon Bay, Alaska, United States") %>%
geocode(city_search_string, method = 'osm', lat = latitude , long = longitude) %>%
st_as_sf(coords = c("longitude","latitude"))
st_crs(ak_city_sf) <- 4326
ak_city_sf <-
ak_city_sf %>%
st_transform(2163)
ak_state_sf <-
tigris::states(cb = T) %>%
filter(STUSPS == "AK") %>%
st_transform(2163)
# before transformation, everything looks fine...
tm_shape(ak_state_sf) +
tm_borders() +
tm_shape(ak_city_sf) +
tm_dots(size = .1) +
tm_text("city_name",
size = .5)
SCALE_FACTOR <- 10000
ak_state_sf_scaled <-
ak_state_sf %>%
as("Spatial") %>%
elide(scale = SCALE_FACTOR) %>%
st_as_sf()
st_crs(ak_state_sf_scaled) <- 2163
ak_city_sf_scaled <-
ak_city_sf %>%
as("Spatial") %>%
elide(scale = SCALE_FACTOR) %>%
st_as_sf()
st_crs(ak_city_sf_scaled) <- 2163
# after scaling, things don't look so good
tm_shape(ak_state_sf_scaled) +
tm_borders() +
tm_shape(ak_city_sf_scaled) +
tm_dots(size = .1) +
tm_text("city_name",
size = .5)
maptools::elide seems to be the best command for doing anything like this (even though it forces me to convert to an sp object). The documentation for scale doesn't mean much to me. (I don't think I can combine them in a single object because they are points for the cities and multipolygons for the state). How can I scale the points the same way I've scaled the state?
To scale or rotate two separate geometries so that they can be mapped together, it's necessary to define a centroid that both your state geometries and city geometries will be rotated or scaled around. This makes some logical sense, since scaling or rotating a collection of points (in this case representing cities) can't happen unless you define (implicitly or explicitly) the center of the scaling or rotating.
Once you've defined a common centroid (in my case, I just used the centroid of the states I was transforming), you can use the affine transformations shown here:
https://geocompr.robinlovelace.net/geometric-operations.html#affine-transformations.

create evenly spaced polylines over counties using R

I would like to create evenly spaced polylines going North to South with 50 mile spacing between each line and 10 miles long. Not sure if this is possible using sf package. In the example below, I would like to have the lines filling the counties across the state of Washington.
library(tigris)
library(leaflet)
states <- states(cb = TRUE)
counties<-counties(cb=TRUE)
counties<- counties%>%filter(STATEFP==53)
states<- states%>%filter(NAME=="Washington")
leaflet(states) %>%
addProviderTiles("CartoDB.Positron") %>%
addPolygons(fillColor = "white",
color = "black",
weight = 0.5) %>%
addPolygons(data=counties,color='red',fillColor = 'white')%>%
setView(-120.5, 47.3, zoom=8)
I've updated to include an image of what I'd like to do below.
You can create a multilinestring sf object from scratch by specifying coordinates.
You can get these coordinates from the extent (bounding box) of Washington, but you may also be interested in knowing how to create a grid, which I will demonstrate below because it may be helpful.
Copy and paste this reproducible example:
library(tidyverse)
library(tigris)
library(leaflet)
library(sf)
library(raster)
states <- states(cb = TRUE)
# subset for WA and transform to a meter-based CRS
states <- states %>%
filter(NAME == "Washington") %>%
st_transform(crs = 3857) # Mercator
# fifty miles in meters
fm <- 80467.2
# subset for Washington
states_sp <- as(states, "Spatial")
# create a grid, convert it to polygons to plot
grid <- raster(extent(states_sp),
resolution = c(fm, fm),
crs = proj4string(states_sp))
grid <- rasterToPolygons(grid)
plot(states_sp)
plot(grid, add = TRUE)
# find the top y coordinate and calculate 50 mile intervals moving south
ty <- extent(grid)[4] # y coordinate along northern WA edge
ty <- ty - (fm * 0:7) # y coordinates moving south at 10 mile intervals
# create a list of sf linestring objects
l <- vector("list", length(ty))
for(i in seq_along(l)){
l[[i]] <-
st_linestring(
rbind(
c(extent(grid)[1], ty[i]),
c(extent(grid)[2], ty[i])
)
)
}
# create the multilinestring, which expects a list of linestrings
ml <- st_multilinestring(l)
plot(states_sp)
plot(as(ml, "Spatial"), add = TRUE, col = "red")
As you can see, I switch back and forth between sf and sp objects using the functions as(sf_object, "Spatial") and st_as_sf(sp_object). Use these to transform the data to your needs.

Is there an R function to convert numerical values into coordinates?

I am working with a dataset that features chemical analyses from different locations within a cave, with each analysis ordered by a site number and that sites latitude and longitude. This first image is what I had done originally simply using ggplot.
Map of site data, colored by N concentration
But what I want to do is use the shapefile of the cave system from which the data is sourced from and do something similar by plotting the points over the system and then coloring them by concentration. This below is the shapefile that I uploaded Cave system shapefile
Cave system shapefile
So basically I want to be able to map the chemical data from my dataset used to map the first figure, but on the map of the shapefile. Initially it kept on saying that it could not plot on top of it. So I figured I had to convert the latitude and longitude into spatial coordinates that could then be mapped on the shapefile.
Master_Cave_data <- Master_cave_data %>%
st_as_sf(MastMaster_cave_data, agr = "identity", coord = Lat_DD)
This was what I had thought to use in order to convert the numerical Latitude cooridnates into spatial data.
I assume your coordinates are in WSG84 projection system (crs code 4326). You can create your sf object the following way:
Master_Cave_data <- st_as_sf(MastMaster_cave_data, coords = c('lon', 'lat'), crs = 4326)
Change lon and lat columns to relevent names. To plot your points with your shapefile, you need them both in the same projection system so reproject if needed:
Master_Cave_data <- Master_cave_data %>% st_transform(st_crs(shapefile))
Example
Borrowed from there
df <- data.frame(place = "London",
lat = 51.5074, lon = 0.1278,
population = 8500000) # just to add some value that is plotable
crs <- 4326
df <- st_as_sf(x = df,
coords = c("lon", "lat"),
crs = crs)
And you can have a look at the map:
library(tmap)
data("World")
tm_shape(World[World$iso_a3 == "GBR", ]) + tm_polygons("pop_est") +
tm_shape(df) + tm_bubbles("population")

Resources