Find centre of polygons using dplyr - r

I'm making a map with arc lines connecting between counties for the US state of Missouri. I've calculated the 'good enough' centres of each county by taking the mean of each polygon's long/lat. This works good for the more or less square-shaped counties, but less so for the more intricately shaped counties. I think that this must be a common occurrence, but I can't find the answer online or with any function I've created. I'd like to use a tidyverse work flow (i.e. not transform to spatial objects if I can help it). Are there any tidyverse solutions to the problem at hand.
You can see the problem in the examples below.
library(tidyverse)
# import all state/county fortified
all_states <- as_tibble(map_data('county'))
# filter for missouri
mo_fortify <- all_states %>%
filter(region == 'missouri')
## Pull Iron county, which is relatively oddly shaped
mo_iron_fortify <- mo_fortify %>%
group_by(subregion) %>%
mutate(long_c = mean(long),
lat_c = mean(lat),
iron = ifelse(subregion == 'iron','Iron','Others')) %>%
ungroup()
# map a ggplot2 map
mo_iron_fortify %>%
ggplot(aes(long, lat, group = group))+
geom_polygon(aes(fill = iron),
color = 'white')+
geom_point(aes(long_c, lat_c))+
scale_fill_grey('Iron county is\na good example')+
coord_map()+
theme_bw()

Related

How to intersect maps using Tigris (and keeping all maps boundaries)?

Sorry for this very basic question but I'm new using Tigris. I would like create a shapefile (and then plot it) of county boundaries + places boundaries for the state of Minnesota.
Here is my code to get the counties:
mn_counties = tigris::counties(cb = T) %>%
filter(STUSPS == 'MN')
And here is my code to get the intersection between places and counties:
mn_places = tigris::places(cb = T) %>%
filter(STUSPS == 'MN') %>%
sf::st_intersection(mn_counties)
However, when I plot the intersection of these maps (counties and places), I just can see the polygons for the places map, but not for the counties.
tm_shape(mn_places) + tm_polygons()
Can anyone please tell me how to get an intersection of counties and places: 1. using tigris and, 2. that I'm able to see both places and county boundaries?
Many thanks in advance!!!
If I am understanding you correctly, you want places and counties in the same dataset. This is accomplished with dplyr::bind_rows():
library(tigris)
library(dplyr)
library(tmap)
mn_counties_and_places <- counties(state = "MN", cb = TRUE) %>%
bind_rows(
places(state = "MN", cb = TRUE)
)
tm_shape(mn_counties_and_places) +
tm_polygons()

How to plot geom_line features in ggplot2 map?

I want to plot rivers (lines) in a map containing polygons (counties, etc) from South Dakota. The river data is here, https://www.weather.gov/gis/Rivers. Use the subset of rivers data set. The county download can be obtained from here, https://www2.census.gov/geo/tiger/TIGER2020/COUNTY/.
I only want the rivers that lie within the county boundaries of South Dakota, so I am using rgeos::intersection to perform that, which produces a Large SpatialLines object, which ggplot2 doesn't like when I try to plot it with geom_line (I get an error that says "Error: data must be a data frame, or other object coercible by fortify(), not an S4 object with class SpatialLines.")
Here is my code:
library(rgdal)
library(raster)
counties <- readOGR('D:\\Shapefiles\\Counties\\tl_2020_us_county.shp')
counties <- counties[which(counties$STATEFP == '46'),]
counties <- spTransform(counties, CRS("+init=epsg:3395"))
rivers <- readOGR('D:\\Shapefiles\\Main_Rivers\\rs16my07.shp')
proj4string(rivers) <- CRS("+proj=longlat")
rivers <- spTransform(rivers, CRS("+init=epsg:3395"))
rivers <- as.SpatialLines.SLDF(rgeos::gIntersection(counties, rivers))
The raster packages "intersect" function does not work for doing the intersection. I think I need to change the SpatialLines object to a spatialLinesDataFrame object to get ggplot2 to plot the rivers. How do I do that? The as.SpatialLines.SLDF function is not doing it. Is there another way to get this to plot? My plotting code is here:
ggplot() +
geom_path(counties, mapping = aes(x = long, y = lat, group = group, col = 'darkgreen')) +
geom_path(rivers, mapping = aes(x = long, y = lat, color = 'blue'))
I would recommend handling your spatial data with the sf library. Firstly, it plays well with ggplot. Also, according to my very much infant understanding of GIS and spatial data in R, I believe that the idea is the sf will eventually take over from sp and the Spatial* data formats. sf is I think a standard format across multiple platforms. See this link for more details on sf.
Onto your question - this is quite simple using sf. To find the rivers inside a specific county, we use st_intersection() (the sf version of gIntersection).
library(sf)
# read in the rivers data
st_read(dsn = 'so_data/rs16my07', layer = 'rs16my07') %>%
{. ->> my_rivers}
# set the CRS for the rivers data
st_crs(my_rivers) <- crs('+proj=longlat')
# transform crs
my_rivers %>%
st_transform('+init=epsg:3395') %>%
{. ->> my_rivers_trans}
# read in counties data
st_read(dsn = 'so_data/tl_2020_us_county') %>%
{. ->> my_counties}
# keep state 46
my_counties %>%
filter(
STATEFP == 46
) %>%
{. ->> state_46}
# transform crs
state_46 %>%
st_transform('+init=epsg:3395') %>%
{. ->> state_46_trans}
# keep only rivers inside state 46
my_rivers_trans %>%
st_intersection(state_46_trans) %>%
{. ->> my_rivers_46}
Then we can plot the sf objects using ggplot and geom_sf(), just like you would plot lines using geom_line() etc. geom_sf() seems to know if you are plotting point data, line data or polygon data, and plots accordingly. It is quite easy to use.
# plot it
state_46_trans %>%
ggplot()+
geom_sf()+
geom_sf(data = my_rivers_46, colour = 'red')
Hopefully this looks right - I don't know my US states so have no idea if this is South Dakota or not.

How to create State & district level map in using GADM and ggplot?

I am using Covid data & looking to plot State & district level Indian data on map.
I have State, District Name of India along with Cases but do not have needed lat, long for them.
I came across this so post How to map an Indian state with districts in r?
and tried raster::getData("GADM", country = "India", level = 2) %>% as_tibble() but this doesn't work as it doesnt have lat,lon, shapefile etc.
library(raster)
library(rgdal)
library(rgeos)
state_level_map <- raster::getData("GADM", country = "India", level = 1) %>%
as_tibble() %>%
filter(NAME_1 == "Rajasthan") %>%
fortify()
ggplot() +
geom_map(data= state_level_map, map = state_level_map,
aes(x = long, y = lat, map_id = id, group = group))
I am new to spatial data / maps and not sure how exactly I can proceed in this situation. Is it possible to get lat, lon, shapefile etc. for State/districts name's info from any r packages or the only way is to manually google them for lat,lon ?
Appreciate any help.
You were almost there. Use sf for that.
library(raster)
library(sf)
library(rgeos)
library(dplyr)
state_level_map <- raster::getData("GADM", country = "India", level = 1) %>%
st_as_sf() %>%
filter(NAME_1 == "Rajasthan")
ggplot() +
geom_sf(data = state_level_map)
you can then easily use aes() to change your aesthetics of the ggplot as you normally would using variables.
sf uses a dataframe-like notation that incorporates both attribute data as well as geometries into a single and easy to use dataframe. just have a look at print(state_level_map). That is, you could join data using district names to augment you attributes and visualize them through aes(color = yourjoinedvar).

Split a map into two separate maps by latitude or longitude

Is there a way to slice a ggplot2 map into two separate maps? I have one large map with id labels that are illegible. I want to split the map vertically into two distinct maps, preferably with an overlapping area so that each polygon would show up whole in at least one map.
Here's a reproducible example. I would want to split the map into a northern one at 35 degrees north and then into a southern one at 35.5 degrees north (giving an overlap between 35 and 35.5 in both). (While I realize it might make more sense with this example to split the other way, my actual map is long vertically.)
library(sf)
library(ggplot2)
sf_nc <- sf::st_read(system.file("shape/nc.shp", package = "sf"), quiet = TRUE)
plot <- ggplot2::ggplot(sf_nc) +
geom_sf(aes(color = NAME)) +
geom_sf_text(aes(label = NAME))
Maybe this is what you are looking for.
Following this post I first make use of st_crop to split the sf df by latitude and extract the FIPS codes for south and north regions.
The FIPS codes are then used to split the sf dataframe into two which ensures that regions on the dividing line are shown in total in both maps.
Finally, I add an ID and bind both dfs back together for easy plotting with facet_wrap
library(sf)
library(ggplot2)
library(dplyr)
sf_nc <- sf::st_read(system.file("shape/nc.shp", package = "sf"), quiet = TRUE)
# Get FIPS/regiona codes for north regions
south <- st_crop(sf_nc, xmin=-180, xmax=180, ymin=-90, ymax=35.5) %>%
pull(FIPS)
north <- st_crop(sf_nc, xmin=-180, xmax=180, ymin=35.5, ymax=90) %>%
pull(FIPS)
# Make sf df for north and south
sf_nc_1 <- filter(sf_nc, FIPS %in% south) %>%
mutate(id = "South")
sf_nc_2 <- filter(sf_nc, FIPS %in% north) %>%
mutate(id = "North")
# Bind together for using facet_wrap
sf_nc_split <- rbind(sf_nc_1, sf_nc_2)
ggplot2::ggplot(sf_nc_split) +
geom_sf(aes(color = NAME)) +
geom_sf_text(aes(label = NAME), size = 2) +
guides(color = FALSE) +
facet_wrap(~id, ncol = 1) +
theme_void()

Hot to get clean polygons in R after st_combine/st_union with sf package?

I have a tidy dataset for census sectors in sf format (setores_sp_ok.rda), which has polygons for two different territorial models, indicated by variable modelo. I want to aggregate census sectors by modelo and cnes, to create another dataset with new boundaries.
I can do this using group_by() + summarise() technique, which automatically uses st_union() to aggregate polygons. But the result is poor, with many internal boundaries.
# load packages
library(dplyr)
library(ggplot2)
library(sf)
library(lwgeom)
# import data
load(url("https://github.com/bruno-pinheiro/app_acesso_saude/raw/master/data/setores_sp_ok.rda"))
# combine polygons
ubs_malhas <- setores_sp %>%
st_make_valid() %>%
group_by(cnes, modelo) %>%
summarise(area = sum(area)) %>%
ungroup()
# plot
ggplot(ubs_malhas[ubs_malhas$modelo == "vigente", ]) +
geom_sf(lwd = .2)
I know that is possible to realize this kind of operation combining st_combine, st_union and st_intersect, but I'm not realizing how to make it.
How to combine polygons by modelo and cnes and get clean aggregated polygons, without internal boundaries?
Someone has any tip?
Many thanks!
Your data may be tidy in a tidyverse sense, but the geometries certainly aren't. The borders between the "vigente" modelo units don't quite line up in many cases, hence you get these little "leftovers" caused by gaps between units. I would snap those to a grid of, say 1cm, and then call st_union.
# load packages
library(dplyr)
library(ggplot2)
library(sf)
library(lwgeom)
# import data
load(url("https://github.com/bruno-pinheiro/app_acesso_saude/raw/master/data/setores_sp_ok.rda"))
# combine polygons
ubs_malhas <- setores_sp %>%
st_snap_to_grid(size = 0.01) %>%
st_make_valid() %>%
group_by(cnes, modelo) %>%
summarise(area = sum(area)) %>%
ungroup()
# plot
ggplot(ubs_malhas[ubs_malhas$modelo == "vigente", ]) +
geom_sf(lwd = .2)
In case you still have unwanted polygons left, you may have to increase the grid size or delete those manually, e.g. in QGIS or thelike.
The function
nngeo::st_remove_holes(your_sf_object)
Solve your problem.

Resources