This is my first time doing any sort of spatial data visualization in R, and I'm stuck on a particular issue. I would like to clip a spatial polygon (specified by a series of lat/long coordinates) according to a world map, such that any part of the polygon which overlaps with a map polygon is removed. Using what I have in the below code as an example, I want to clip the rectangular spatial polygon so that only oceanic portions of the polygon remain.
I've found examples of how to retain the intersection between two spatial polygons, but I want to do the opposite. Perhaps there is a way to define the intersection, then "subtract" that from the polygon I wish to clip?
This might be a really basic question, but any tips will be appreciated!
Specify lat/long data:
x_coord <- c(25, 25, 275, 275)
y_coord <- c(20, -50, -50, 20)
xy.mat <- cbind(x_coord, y_coord)
xy.mat
Convert to spatial polygons object:
library(sp)
poly = Polygon(xy.mat)
polys = Polygons(list(poly),1)
spatial.polys = SpatialPolygons(list(polys))
proj4string(spatial.polys) = CRS("+proj=longlat +datum=WGS84 +no_defs
+ellps=WGS84 +towgs84=0,0,0")
Convert to spatial polygons data frame and export as shapefile:
df = data.frame(f=99.9)
spatial.polys.df = SpatialPolygonsDataFrame(spatial.polys, df)
spatial.polys.df
library(GISTools)
library(rgdal)
writeOGR(obj=spatial.polys.df, dsn="tmp", layer="polygon",
driver="ESRI Shapefile")
Plot world map and add .shp file:
map("world", wrap=c(0,360), resolution=0, ylim=c(-60,60))
map.axes()
shp <- readOGR("polygon.shp")
plot(shp, add=TRUE, col="blue", border=FALSE)
Here is a solution that stays in sf the entire time (I don't know sp), and illustrates constructing an sf object from scratch. st_difference create the geometry you want exactly, and then plotting can be done with the base plot method or the development version of ggplot which has geom_sf. I used map data from maps and rnaturalearth for this, you can adapt to your particular situation. Wrapping around the dateline is a little finicky regardless unfortunately.
library(tidyverse)
library(sf)
#> Linking to GEOS 3.6.1, GDAL 2.2.0, proj.4 4.9.3
library(rnaturalearth)
library(maps)
#>
#> Attaching package: 'maps'
#> The following object is masked from 'package:purrr':
#>
#> map
x_coord <- c(25, 25, 275, 275)
y_coord <- c(20, -50, -50, 20)
polygon <- cbind(x_coord, y_coord) %>%
st_linestring() %>%
st_cast("POLYGON") %>%
st_sfc(crs = 4326, check_ring_dir = TRUE) %>%
st_sf() %>%
st_wrap_dateline(options = c("WRAPDATELINE=YES", "DATELINEOFFSET=180"))
land <- rnaturalearth::ne_countries(returnclass = "sf") %>%
st_union()
ocean <- st_difference(polygon, land)
#> although coordinates are longitude/latitude, st_difference assumes that they are planar
plot(st_geometry(land))
plot(st_geometry(polygon), add = TRUE)
plot(st_geometry(ocean), add = TRUE, col = "blue")
ggplot() +
theme_bw() +
borders("world") +
geom_sf(data = ocean)
Created on 2018-03-13 by the reprex package (v0.2.0).
If I understand correctly what you want you can do it with the sf package using st_difference() and st_union()`.
Base on your code here is what you can do.
# world data
data("wrld_simpl", package = 'maptools')
# load sf package
library('sf')
# coerce sp object to sf
world <- st_as_sf(wrld_simpl)
rectangle <- st_as_sf(spatial.polys)
# difference between world polygons and the rectangle
difference <- st_difference(rectangle, st_union(world))
# coerce back to sp
difference <- as(difference, 'Spatial')
# plot the result
plot(difference)
Related
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.
I am trying to rasterize some points and am getting a mismatch between the points and the rasters despite the crs being the same. If I convert the raster to polygons it lines up perfectly with the sf points data, but I can't figure out why the raster doesn't.
library(spData)
library(sf)
library(raster)
library(mapview)
## import some data
cycle_hire_osm = spData::cycle_hire_osm
## project to metres
cycle_hire_osm_projected = st_transform(cycle_hire_osm, crs = 27700)
## create raster template to rasterize to
raster_template <- raster(extent(cycle_hire_osm_projected), nrows = 10, ncols = 10, crs = 27700)
## rasterize the points
ch_raster1 = rasterize(cycle_hire_osm_projected, raster_template, field = 'capacity',
fun = sum, crs = 27700)
## convert raster to polygons
ch_poly <- rasterToPolygons(ch_raster1)
If these are plotted there are raster cells that have a value but have no points in.
## plot on a map
mapview(ch_poly)+cycle_hire_osm_projected+ch_raster1
Additional example based on reply to show the output as base, mapview and leaflet (note: I had to install the development versions of mapview and leaflet in order to plot SpatRasts)
library(spData)
library(sf)
library(terra)
library(dplyr)
# remove NAs so they are not considered
dat <- spData::cycle_hire_osm %>% filter(!is.na(capacity))
v <- vect(dat)
r <- rast(v, nrows=10, ncols=10)
chr <- rasterize(v, r, field="capacity", fun=sum, na.rm=TRUE)
## base plot
plot(chr)
points(v, cex=.5)
points(v[is.na(v$capacity)], cex=.5, col="red")
## mapview
library(mapview)
mapview(v)+chr
##leaflet
library(leaflet)
leaflet() |>
addProviderTiles(providers$CartoDB.Positron) |>
addCircles(data = v) |>
addRasterImage(chr)
All three plots are the same raster but the raster appears to have a different number of cells with values in each plot?
Adding example with project = FALSE as explain by #RobertHijmans
leaflet() |>
addProviderTiles(providers$CartoDB.Positron) |>
addCircles(data = v) |>
addRasterImage(chr, project = FALSE)
I do not see that issue with "raster" nor with its replacement, "terra"
when using base-plot
library(spData)
library(terra)
# using a SpatVector for easier plotting; results are the same
v <- vect(spData::cycle_hire_osm)
v <- project(v, "epsg:27700")
r <- rast(v, nrows=10, ncols=10)
chr <- rasterize(v, r, field="capacity", fun=sum, na.rm=TRUE)
plot(chr)
points(v, cex=.5)
points(v[is.na(v$capacity)], cex=.5, col="red")
But note that there are some cells with values of zero where all values of v$capacity are NA. That is because
sum(NA, na.rm=TRUE)
#[1] 0
To avoid that from happening you could do
vv <- v[!is.na(v$capacity)]
chr <- rasterize(vv, r, field="capacity", fun=sum, na.rm=TRUE)
The reason you see differences when using mapview/leaflet is that these use transform your data to the crs that they use. To avoid that use the
Pseudo-Mercator (EPSG:3857) crs, and, in leaflet, use project=FALSE when adding the raster data.
addRasterImage(chr, project=FALSE)
I have a raster and a shapefile:
library(cartography)
library(sf)
library(raster)
r <- raster(matrix(rnorm(10*12), nrow=10), xmn = -180, xmx= 180, ymn = -90, ymx= 90)
mtq <- st_read(system.file("gpkg/mtq.gpkg", package="cartography"), quiet = TRUE)
I would like to intersect the raster r with the shapefile mtq and make the corresponding pixels to the all polygons as NA (replace the values of the pixels in the raster by NA) and return the raster.
You are likely looking for mask; it lives in both oldish {raster} and shiny new {terra}.
Note that I had to rewrite your r object a bit, as it was not quite compatible with the Martinique vector object from {cartography}.
Edit: if, as seems to be indicated in the comments, you are looking for replacing with NAs the values inside the polygon (and not outside) my answer is still raster::mask(), only with a little tweaking of the masking object (you need the inverse of the polygon over the extent of your raster).
library(cartography)
library(sf)
library(raster)
mtq <- st_read(system.file("gpkg/mtq.gpkg", package="cartography"), quiet = TRUE) %>%
dplyr::summarise() # dissolve internal boundaries
r <- raster(matrix(rnorm(10*12), nrow=10),
xmn = st_bbox(mtq)["xmin"],
xmx= st_bbox(mtq)["xmax"],
ymn = st_bbox(mtq)["ymin"],
ymx= st_bbox(mtq)["ymax"],
crs = st_crs(mtq))
plot(r) # original raster - full extent + range
# the masking object:
mask <- st_bbox(r) %>% # take extent of your raster...
st_as_sfc() %>% # make it a sf object
st_set_crs(st_crs(mtq)) %>% # in CRS of your polygon
st_difference(mtq) %>% # intersect with the polygon object
st_as_sf() # interpret as sf (and not sfc) object
result <- r %>%
mask(mask)
plot(result)
plot(st_geometry(mtq), add = T)
I would like to generate a Voronoi diagram around 2D polygons. This question is somehow similar to this one here addressed for Python.
Is straightforward how this works for points, below is an example with sf::st_voronoi() function:
library(sf)
#> Linking to GEOS 3.8.0, GDAL 3.0.4, PROJ 6.3.1
p <- rbind(c(3.2,4),
c(3,4.6),
c(3.8,4.4),
c(3.5,3.8),
c(3.4,3.6),
c(3.9,4.5))
plot(p, pch = 16)
p %>% st_multipoint() %>% st_voronoi() %>% plot(col = NA, add = TRUE)
Created on 2020-05-28 by the reprex package (v0.3.0)
But when I try the same function for some generated polygons, I do not get the results I would like:
library(sf)
#> Linking to GEOS 3.8.0, GDAL 3.0.4, PROJ 6.3.1
p1 <- rbind(c(0,0), c(1,0), c(3,2), c(2,4), c(1,4), c(0,0))
p2 <- rbind(c(3,0), c(4,0), c(4,1), c(3,1), c(3,0))
pol <- st_multipolygon(list(list(p1), list(p2)))
plot(st_voronoi(pol), col = NA, lwd = 2, lty = 3)
plot(pol, col = rgb(1,0,0, alpha = 0.3), add = TRUE)
Created on 2020-05-28 by the reprex package (v0.3.0)
It seems that the Voronoi grid is based on the vertices of the polygons, which makes sense. However, I would like to get a Voronoi grid surrounding the red polygons and not intersecting them, that is, to treat a polygon as a point. Note that, getting the centroids of the polygons (e.g. with sf::st_centroid) and then generating a Voronoi grid is a path I tried, but the Voronoi grid will still intersect the polygons.
Here is my attempt. This is mostly relying on sf but I use smoothr::densify() to add vertices to straight edges of polygons (since the voronoi polygons are initially built around the polygon vertices), and I rely on a function from data.table to combine sf objects. There are probably ways to make this more efficient.
You would probably also want to simplify input polygons, although that is not needed in this test case.
The one really unresolved issue is for when two polygons share a boundary. The polygon-based voronoi should just follow that boundary, but currently does not.
library(sf)
# additionally requires:
## smoothr to densify polygons
## data.table to combine results
## poly = input sf polygons
## clip = polygon to be used as an extent for the output
## max_distance = argument for smoothr::densify, what max distance to have between vertices of a polygon. For breaking up long edges. In map units.
polyVoronoi <- function(poly, clip = NULL, max_distance = NULL) {
# add vertices to polygons to have voronoi polygons along straight edges of polygon
if (!is.null(max_distance)) {
poly <- smoothr::densify(poly, max_distance = max_distance)
}
# generate voronoi polygons for all vertices
vv <- st_voronoi(st_combine(poly))
vv <- st_collection_extract(vv, 'POLYGON')
# deal with geom validity issues
if (!all(st_is_valid(vv))) {
for (i in 1:length(vv)) {
vv[i] <- st_make_valid(vv[i])
if (!all(st_is_valid(vv[i]))) stop()
}
}
# determine which voronoi polygons intersect with input polygons
ii <- st_intersects(poly, vv)
# union/dissolve voronoi polygons that belong to the same inputs
resList <- vector('list', length(ii))
for (i in 1:length(ii)) {
xx <- vv[ii[[i]]]
xx <- st_combine(xx)
if (!all(st_is_valid(xx))) {
xx <- st_make_valid(xx)
}
resList[[i]] <- st_union(xx)
}
res <- st_as_sf(data.table::rbindlist(lapply(resList, st_as_sf)))
res <- res[1:nrow(res),]
res <- st_geometry(res)
if (!is.null(clip)) {
res <- st_intersection(res, clip)
}
return(res)
}
Example using built in dataset from sf
nc = st_read(system.file("shape/nc.shp", package="sf"))
# project to North America Albers Equal Area
nc <- st_transform(nc, crs = "+proj=aea +lat_1=20 +lat_2=60 +lat_0=40 +lon_0=-96 +x_0=0 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs")
poly = st_geometry(nc)
# sample just a few polygons
poly <- poly[sample(1:length(poly), 12)]
# define bounds that we want for the output
e <- st_buffer(st_make_grid(poly, n = 1), 50000)
vv <- polyVoronoi(poly, clip = e, max_distance = 10000)
plot(vv, border = 'blue')
plot(poly, add = TRUE)
As you can see, there is a problem when polygons are in contact, and I haven't resolved this yet. Suggestions welcome!
I am trying to manipulate some Brazilian Census data in R using the new "sf" package. I am able to import the data, but I get an error when I try to create the centroids of the original polygons
library(sf)
#Donwload data
filepath <- 'ftp://geoftp.ibge.gov.br/organizacao_do_territorio/malhas_territoriais/malhas_de_setores_censitarios__divisoes_intramunicipais/censo_2010/setores_censitarios_shp/ac/ac_setores_censitarios.zip'
download.file(filepath,'ac_setores_censitarios.zip')
unzip('ac_setores_censitarios.zip')
d <- st_read('12SEE250GC_SIR.shp',stringsAsFactors = F)
Now I try to create a new geometry column containing the centroid of column "geometry", but get an error:
d$centroid <- st_centroid(d$geometry)
Warning message:
In st_centroid.sfc(d$geometry) :
st_centroid does not give correct centroids for longitude/latitude data
How can I solve this?
All the GEOS functions underlying sf need projected coordinates to work properly, so you should run st_centroid on appropriately projected data. I don't know much about Brazil's available CRS's, but EPSG:29101 appears to work fine:
library(tidyverse)
d$centroids <- st_transform(d, 29101) %>%
st_centroid() %>%
# this is the crs from d, which has no EPSG code:
st_transform(., '+proj=longlat +ellps=GRS80 +no_defs') %>%
# since you want the centroids in a second geometry col:
st_geometry()
# check with
plot(st_geometry(d))
plot(d[, 'centroids'], add = T, col = 'red', pch = 19)