fit two sf polygons seamlessly - r

The problem
Suppose we have two shapefiles that should border seamlessly. Only, they don't. Is there a way to force them to stick to one another without gaps?
The specific case
I have two shapefiles: one for European regions -- REG, the other for the neighbouring countries -- NEI. Both shapefiles are taken from Eurostat repository and should fit together nicely; but there are small gaps. Also, I need to simplify the polygons, and then the gaps become really notable.
The best I can think of
I've tried several approaches but with no success. The only way to achieve the desired result that I see requires following steps:
create a line sf with just the border between my shapefiles;
from this line create a buffer polygon just big enough to cover all gaps;
join and dissolve this buffer to the shapefile for neighbours -- NEI;
clip off the expanded NEI with the REG shapefile.
Obviously, this is a rather clumsy workaround.
Is there a better way to go?
Reproducible example in this gist
A minimal example
# install dev version of ggplot2
devtools::dev_mode()
devtools::install_github("tidyverse/ggplot2")
library(tidyverse)
library(sf)
library(rmapshaper)
library(ggthemes)
# load data
source(file = url("https://gist.githubusercontent.com/ikashnitsky/4b92f6b9f4bcbd8b2190fb0796fd1ec0/raw/1e281b7bb8ec74c9c9989fe50a87b6021ddbad03/minimal-data.R"))
# test how good they fit together
ggplot() +
geom_sf(data = REG, color = "black", size = .2, fill = NA) +
geom_sf(data = NEI, color = "red", size = .2, fill = NA)+
coord_sf(datum = NA)+
theme_map()
ggsave("test-1.pdf", width = 12, height = 10)
# simplify
REGs <- REG %>% ms_simplify(keep = .5, keep_shapes = TRUE)
NEIs <- NEI %>% ms_simplify(keep = .5, keep_shapes = TRUE)
ggplot() +
geom_sf(data = REGs, color = "black", size = .2, fill = NA) +
geom_sf(data = NEIs, color = "red", size = .2, fill = NA)+
coord_sf(datum = NA)+
theme_map()
ggsave("test-2.pdf", width = 12, height = 10)

ms_simplify seems to work on your minimal example but you need first to group your 2 "shapefiles" into one "shapefile". If needed it would be easy to split them after the simplification of the boundaries.
(note : my version of rmapshaper returns an error when ms_simplify is used with an sf object. This is why I have transformed my tmp object in a sp object with as(tmp, "Spatial"))
NEI <- st_transform(NEI, st_crs(REG)$epsg)
tmp <- rbind(REG , NEI)
tmp <- ms_simplify(as(tmp, "Spatial"), keep = .1, keep_shapes = T)
ggplot() + geom_sf(data = st_as_sf(tmp)) + theme_bw()

Related

Is there any way to add a color palette into the size aesthetic?

I am working in the next code: ggplot()+ geom_sf(data = DPEst_DH, aes(size = di1a), col="orangered") that works.
But I want a colored legend.
In other words, I want apply the same field at two aesthetics, size and color but keeping one legend.
...+ geom_sf(data = DPEst_DH, aes(color=di1a, size=di1a))
With that code line I have the next output but I want to know if is possible to have something like the last image.
One option to achieve your desired result would be to make your di1a column a discrete variable using e.g. cut and to set the colors and sizes via scale_xxx_manual.
Making use of the nc shape file shipped with the sf package as example data:
library(ggplot2)
library(dplyr)
# Example data
nc_center <- sf::st_read(system.file("shape/nc.shp", package = "sf"), quiet = TRUE) %>%
sf::st_centroid()
# Bin numeric variable
labels <- pretty(range(nc_center$AREA))
breaks <- c(labels, Inf)
nc_center <- nc_center %>%
mutate(area = cut(AREA, breaks = breaks, labels = labels, right = FALSE))
# Color and size palette
colors <- c("#132B43", "#56B1F7") # Default ggplot2 blue colors used for color gradient
pal <- colorRampPalette(colors)(length(labels))
pal_size <- seq(1, 6, length.out = 5) # c(1, 6): Default range for size scale
ggplot() +
geom_sf(data = nc_center, aes(color = area, size = area)) +
scale_color_manual(values = pal) +
scale_size_manual(values = pal_size)

ggplot2 and sf: geom_sf_text within limits set by coord_sf

I am using sf and ggplot2 to read shapefiles as simple features and plot various maps. I have been working through the maps chapter in the ggplot2 book but could not really find an answer to the following issue:
Plotting a map using geom_sf and labelling its features with geom_sf_text is a pretty straightforward task.
library(ggplot2)
library(sf)
library(ozmaps)
oz_states <- ozmaps::ozmap_states
ggplot() +
geom_sf(data = oz_states) +
geom_sf_text(data = oz_states, aes(label = NAME))
Once we zoom in on a section of the previous map, not all labels of the features present in the plot are visible.
xlim <- c(120.0, 140.0)
ylim <- c(-40, -24)
ggplot() +
geom_sf(data = oz_states) +
geom_sf_text(data = oz_states, aes(label = NAME)) +
coord_sf(xlim = xlim, ylim = ylim)
I have found a workaround to zoom in on sections of the map and still be able to label the features present in the plot by calculating the centroids of the features, extracting the coordinates as separate columns, selecting the elements I would like to be displayed in the final map, and using ggrepel to label them.
library(dplyr)
library(ggrepel)
oz_states_labels <- oz_states %>% st_centroid()
oz_states_labels <- do.call(rbind, st_geometry(oz_states_labels)) %>%
as_tibble() %>%
rename(x = V1) %>%
rename(y = V2) %>%
cbind(oz_states_labels) %>%
slice(4,5,7,3)
ggplot() +
geom_sf(data = oz_states) +
geom_text_repel(data = oz_states_labels, aes(label = NAME, x = x, y = y)) +
coord_sf(xlim = xlim, ylim = ylim)
Naturally, if possible, I would like to avoid the workaround of first having to calculate the centroids, extract the coordinates from the resulting sf and select the labels to be shown in the final map.
Hence my question: Is there a faster way of labelling all elements visible in the plot for example by specifying this either in geom_sf_text or coord_sf?
Thanks in advance for your tips and answers!
I believe the issue you are facing is caused by your applying the crop at presentation level / the actual data underlying your ggplot object is not cropped.
I suggest applying the crop at data level, for example via sf::st_crop(). In this example I am using the values of your xlim and ylim objects to create a bounding box (called crop_factor for no good reason) to limit the extent of the oz_states at the data level, by creating a new object called oz_cropped & continuing in your original workflow.
All the centroids and labels and what not will be much better behaved now.
library(ggplot2)
library(sf)
library(ozmaps)
oz_states <- ozmaps::ozmap_states
crop_factor <- st_bbox(c(xmin = 120,
xmax = 140,
ymax = -24,
ymin = -40),
crs = st_crs(oz_states))
oz_cropped <- st_crop(oz_states, crop_factor)
ggplot() +
geom_sf(data =oz_cropped) +
geom_sf_text(data = oz_cropped, aes(label = NAME))

Limit Simple Features Plot based on geojson Polygon

I'd like to limit a plot based on a polygon defined in geojson, so that it only shows the area shaded blue here.
i.e. just plot the features inside and including the ring road.
The geojson is available here.
It would also be great to add a buffer around the edge to include the ring road.
My code to draw all the features (unlimited by the geojson is below).
library(tidyverse)
library(osmdata)
bounding_box <- getbb("Birmingham", featuretype = "city")
streets <- bounding_box %>%
opq()%>%
add_osm_feature(key = "highway",
value = c("motorway", "trunk", "primary", "secondary", "tertiary")) %>%
osmdata_sf()
ggplot() +
geom_sf(data = streets$osm_lines,
inherit.aes = FALSE,
color = "grey",
size = 1) +
theme_void() +
theme(
plot.background = element_rect(fill = "white"),
legend.position = "none"
) +
coord_sf(xlim = c(-1.933, -1.869),
ylim = c(52.46, 52.496),
expand = FALSE)
I assume in the following that the object streets has already been defined by running the first few lines of the code in the question. The next step is then to read the polygon using read_sf() from the sf package. The next line converts to a more suitable coordinate system (OSGB 1936 / British National Grid) because adding a buffer in meters is not possible in lon/lat-coordinates. A buffer of 40 meters is added using st_buffer() and finally the coordinates are transformed back to WGS84:
library(sf)
area <- read_sf("~/Birmingham CAZ 2020.GeoJSON") %>%
st_transform(27700) %>%
st_buffer(units::set_units(40, m)) %>%
st_transform(4326)
Of course, you need to adapt the path to where you have actually stored the file. Then I use st_intersection() to extract the part of streets$osm_lines that lies inside the polygon:
streets_area <- st_intersection(poly, streets$osm_lines)
And finally I produce the plot using the code from your question. Note that I have added a layer with the polygon in the second line in order to demonstrate that the streets indeed lie inside the polygon:
ggplot() +
geom_sf(data = area) +
geom_sf(data = streets_area,
inherit.aes = FALSE,
color = "grey",
size = 1) +
theme_void() +
theme(
plot.background = element_rect(fill = "white"),
legend.position = "none"
) +
coord_sf(xlim = c(-1.933, -1.869),
ylim = c(52.46, 52.496),
expand = FALSE)

Insert geom_sf layer underneath existing geom_sf layers

I have a basic map of India with states and borders, some labels, and a number of other specifications stored as a gg object. I'd like to generate a number of maps with a district layer, which will bear data from different variables.
To prevent the district maps overwriting state and country borders, it must be before all the previous code, which I'd like to avoid repeating.
I thought I could do this by calling on $layers for the gg object as per this answer. However, it throws an error. Reprex is below:
library(ggplot2)
library(sf)
library(raster)
# Download district and state data (should be less than 10 Mb in total)
distSF <- st_as_sf(getData("GADM",country="IND",level=2))
stateSF <- st_as_sf(getData("GADM",country="IND",level=1))
# Add border
countryborder <- st_union(stateSF)
# Basic plot
basicIndia <- ggplot() +
geom_sf(data = stateSF, color = "white", fill = NA) +
geom_sf(data = countryborder, color = "blue", fill = NA) +
theme_dark()
basicIndia
# Data-bearing plot
districts <- ggplot() +
geom_sf(data = distSF, fill = "gold")
basicIndia$layers <- c(geom_sf(data = distSF, fill = "gold"), basicIndia$layers)
basicIndia
#> Error in y$layer_data(plot$data): attempt to apply non-function
Intended outcome
Any help would be much appreciated!
I'm still not sure if I'm missing a detail of what you're looking for, but ggplot2 draws layers in the order you provide them. So something like
ggplot(data) +
geom_col() +
geom_point(...) +
geom_line(...)
will draw columns, then points on top of those, then lines on top of the previous layers.
Same goes for sf plots, which makes it easy to make a plot like this of multiple geographic levels.
(I'm using rmapshaper::ms_simplify on the sf objects just to simplify them and speed things up for plotting.)
library(dplyr)
library(ggplot2)
library(sf)
library(raster)
distSF <- st_as_sf(getData("GADM",country="IND",level=2)) %>% rmapshaper::ms_simplify()
...
Then you can plot by adding up the layers in the order you need them displayed. Keep in mind that if you needed to do other calculations with any of these sfs, you could do that in advance or inside your geom_sf.
ggplot() +
geom_sf(data = distSF, fill = "gold", size = 0.1) +
geom_sf(data = stateSF, color = "white", fill = NA) +
geom_sf(data = countryborder, color = "blue", fill = NA)
Regarding trying to add one plot to another: ggplot2 works in layers, so you create a single base ggplot object, then add geometries on top of it. So you could make, for example, two valid plots:
state_plot <- ggplot(stateSF) +
geom_sf(color = "white", fill = NA)
country_plot <- ggplot(countryborder) +
geom_sf(color = "blue", fill = NA)
But you can't add them, because you would have 2 base ggplot objects. This should be the error you mentioned:
state_plot +
country_plot
#> Error: Don't know how to add country_plot to a plot
Instead, if you need to make a plot, then add something else on top of it, make the base ggplot, then add geometry layers, such as a geom_sf with a different set of data.
state_plot +
geom_sf(data = countryborder, fill = NA, color = "blue")
Created on 2018-10-29 by the reprex package (v0.2.1)
If you look at geom_sf(data=distSF) you'll see that it is a list made up of two elements - you want the first one which contains the layer information, so geom_sf(data = distSF, fill = "gold")[[1]] should work.
districts <- ggplot() +
geom_sf(data = distSF, fill = "gold")
basicIndia$layers <- c(geom_sf(data = distSF, fill = "gold")[[1]], basicIndia$layers)

How can I map raster and vector data together?

I want to map raster and vector data in R
Data
class(Africa)# SpatialPolygonsDataFrame
class(Rift)#SpatialLinesDataFrame
class(Data.SP)#SpatialPointsDataFrame
class(An_Precip_subTest)#RasterLayer`
Code I am trying to run
tm_shape(Africa) +
tm_raster(An_Precip_subTest)+
tm_shape(Data.SP) +
tm_dots(col="CS", auto.palette.mapping = FALSE, palette="-RdYlBu",
breaks=Spectrum, title="Ventral Centroid Size", size=0.3)+
tm_shape(Rift) + tm_lines(col = "black") +
tm_legend(legend.outside=TRUE)
I get this error message
Error: Africa consists of polygons, so it cannot accept tm_raster.
I have modeled my code after the tmap in a nutshell examples
(https://cran.r-project.org/web/packages/tmap/vignettes/tmap-nutshell.html)
tm_shape(land) +
tm_raster("trees", breaks=seq(0, 100, by=20), legend.show = FALSE) +
tm_shape(Europe, is.master = TRUE) +
tm_borders() +
tm_shape(rivers) +
tm_lines(lwd="strokelwd", scale=5, legend.lwd.show = FALSE) +
tm_shape(metro) +
tm_bubbles("pop2010", "red", border.col = "black", border.lwd=1,
size.lim = c(0, 11e6), sizes.legend = c(1e6, 2e6, 4e6, 6e6, 10e6),
title.size="Metropolitan Population") +
tm_text("name", size="pop2010", scale=1, root=4, size.lowerbound = .6,
bg.color="white", bg.alpha = .75,
auto.placement = 1, legend.size.show = FALSE) +
tm_format_Europe() +
tm_style_natural()
They just drop a raster file in after a shape file and everything works out fine.
I do not understand how my code is different and incorrect.
I used this example
(https://gis.stackexchange.com/questions/61243/clipping-a-raster-in-r) to make my raster file
I wonder if others have had trouble recreating this example
How can I map raster and vector data together?
I am up for trying new packages, converting data to different formats etc
Any help is appreciated.
Although an old question. But maybe the solution is helpful for other people. To plot the vector (shape file overlaid with raster), first run tm_raster() + tm_shape (your shapefile (sp,sf object)) + tm_dots (in case your shape file is points object)

Resources