Plotting latitude and longitude points in r - r

I am really struggling plotting these spatial data points in r. I have tried using ggmap, sf, and sp but I can't get it to cooperate with me.
I have a table, tbl, that has the following makeup:
tbl
| lat | long | alive| species|
where lat and long are the latitude and longitude respectively. "alive" is boolean so is "TRUE" or "FALSE" and "species" is a species code for one of the three species in the data set.
I am trying to get a graph that has points where all of the animals were found, with the color of the point denoting if the animal was found alive or dead and the shape of the point denoting the species. So I could include a key.
I am more familiar with python, and I understand how I could do this in python. But I am really struggling with doing this in r. Could all of these options be passed in the 'aes' parameter? How ould I do that?
Most success doing:
mapplot(longitude=table_1$lon,latitude=table_1$lat,type="p")
mapplot <- get_map(center= c(lon=mean(tbl$lon),lat=mean(tbl$lat)),zoom=2,maptype="satellite",scale=2)
ggmap(mapplot) + geom_point(data=tbl, aes(x=lon,y=lat))

Yo, you didn't give us much to go on... so I've taken the liberty of making up some data.
First a table, as you described with lat/lon and alive, species.
Then turn it into an sf object for plotting.
Finally, get some border data to show it on a map.
library(tidyverse)
library(sf)
#> Linking to GEOS 3.8.0, GDAL 3.0.4, PROJ 6.3.1; sf_use_s2() is TRUE
set.seed(3) # for reproducibility
# making up data
lat <- rnorm(10, mean = 36, sd = 4)
long <- rnorm(10, mean = -119, sd = 2)
alive <- sample(c(T, F), 10, replace = T)
species <- sample(c('frog', 'bird', 'rat'), 10, replace = T)
# crate a tibble with the made up data
my_table <- tibble(lat = lat, long = long, alive = alive, species = species)
# turn it into an sf object, for spatial plotting
my_sf <- my_table %>%
st_as_sf(coords = c('long', 'lat')) %>%
st_set_crs(4326) # using 4326 for lat/lon decimal
# ggplot2 of the data
ggplot() +
geom_sf(data = my_sf, aes(color = alive, shape = species), size = 3)
# Getting a little fancier with it by adding the state borders
ca_nv_map <- rnaturalearth::ne_states(country = 'United States of America', returnclass = 'sf') %>%
filter(name %in% c("California", "Nevada"))
ggplot() +
geom_sf(data = my_sf, aes(color = alive, shape = species), size = 3) +
geom_sf(data = ca_nv_map, fill = NA)
Created on 2022-11-09 by the reprex package (v2.0.1)

Here's a super simple example to get you started, using the sf package for spatial data and ggplot2 for plotting:
require('tidyverse')
require('sf')
# generate some sample data
sampleDF <- data.frame(
lat=c(-17.4, -17.1, -17.8),
lon=c(158.2, 158.9, 157.9),
alive=c(T, F, T),
species=c('sp1', 'sp2', 'sp3')
) %>%
dplyr::mutate(species = as.factor(species)) %>%
st_as_sf(coords=c('lon', 'lat'), crs=4326)
# We converted the species column to a factor
# and converted the dataframe to an sf object,
# specifying the X and Y columns and the
# coordinate reference system (4326 is WGS84)
# Have a look at the sample data
sampleDF
> Simple feature collection with 3 features and 2 fields
> Geometry type: POINT
> Dimension: XY
> Bounding box: xmin: 157.9 ymin: -17.8 xmax: 158.9 ymax: -17.1
> Geodetic CRS: WGS 84
> alive species geometry
> 1 TRUE sp1 POINT (158.2 -17.4)
> 2 FALSE sp2 POINT (158.9 -17.1)
> 3 TRUE sp3 POINT (157.9 -17.8)
# Now we plot it
# (Note that alive and species are within the aes() function,
# because we want those drawn from the data itself.
# size is outside aes() because we're using a constant of 4.)
ggplot() +
geom_sf(data=sampleDF, aes(col=alive, shape=species), size=4) +
theme_classic(base_size=14)
Result:
(You can make it prettier by removing the axes, adding a basemap, etc.)
You can also view it interactively with a useful basemap using the mapview package. This will open up a page in your default web browser and let you zoom in/out and change the basemap.
mapview::mapviewOptions(viewer.suppress = TRUE, fgb=FALSE)
mapview::mapview(sampleDF, zcol='alive')
(Note: my random points are in the middle of the Pacific Ocean, so this is not the most useful map.)

Related

redlistr::getAreaEOO from degree minute data input

I have been trying to calculate the EOO area for a species using the redlistr package. In the example, the authors used raster data. However, I have observation points of the species in the degree minute format.
I created a subset of data for reference:
dt <- data.frame(lon_x = c(168.36085, 151.228745, 144.984577, 144.984287, 144.984201),
lat_y = c(-46.59179, -34.005291, -37.926258, -37.919514, -37.923407),
species = "seahorse_spp1")
coords <- cbind(dt$lon_x, dt$lat_y)
dt_spdf <- SpatialPointsDataFrame(coords, dt)
# now add a coordinate reference system to the sp dataframe
prj4string <- "+proj=longlat +ellps=WGS84 +datum=WGS84 +units=km +no_defs"
crs(dt_spdf) <- prj4string
# unit is in meter like required
# now create EOO polygon
dt.polygon <- redlistr::makeEOO(dt_spdf)
# now visually check the points and polygon to make sure they look correct
leaflet() %>%
addTiles() %>%
addCircles(data = dt_spdf, ~ lon_x, ~ lat_y, color = "red") %>%
addPolygons(data = dt.polygon)
# calculate EOO
redlistr::getAreaEOO(dt.polygon)
#> [1] 0.0003264353
And it keeps giving this very small, unrealistic value.
Does anyone have any idea where I did wrong?
Thank you!

Filter / subset data between two polygons in R - SF (concentric circle polygons)

Not sure if there's a function i'm missing, but i'm having trouble filtering / checking if point geometries fall between two polygons (concentric circles).
Can you create a mask between two concentric polygons, and then use this to filter out the point geometries that contain the feature data of interest?
I tried subsetting between two polygons using the sf_filter package in R. This did not work.
reproducible code below:
library(sf)
library(tidyverse)
library(sp)
#Create fake data
my.data <- data.frame(replicate(2,sample(-88: -14,100,rep=TRUE))) # Point data
d <- cbind(seq(-180,180,length.out=360),rep(-88,360))
e <- cbind(seq(-180,180,length.out=360),rep(-30,360))
#Project fake data
d = SpatialPoints(cbind(d[,1], d[,2]), proj4string=CRS("+proj=longlat"))
d <- spTransform(d, CRS("+init=epsg:3976"))
e = SpatialPoints(cbind(e[,1], e[,2]), proj4string=CRS("+proj=longlat"))
e <- spTransform(e, CRS("+init=epsg:3976"))
my.data = SpatialPoints(cbind(my.data[,1], my.data[,2]), proj4string=CRS("+proj=longlat"))
my.data <- spTransform(my.data, CRS("+init=epsg:3976"))
d <- sf::st_as_sf(d, coords = c("X1", "X2"),
remove = FALSE,
crs = st_crs("epsg:3976"))
e <- sf::st_as_sf(e, coords = c("X1", "X2"),
remove = FALSE,
crs = st_crs("epsg:3976"))
my.data <- sf::st_as_sf(my.data, coords = c("X1", "X2"),
remove = FALSE,
crs = st_crs("epsg:3976"))
# Create linestrings from circle
d <- d %>%
summarise(do_union = FALSE) %>%
st_cast("LINESTRING")
e <- e %>%
summarise(do_union = FALSE) %>%
st_cast("LINESTRING")
#Join geometries
nst <- rbind(d,e)
#Create polygon
nst <- nst %>%
st_cast("POLYGON")
#Filtering between polygons doesn't return anything
PFz <- st_filter(my.data,nst)
Consider this approach; it builds on three semi random cities in North Carolina (because I love the nc.shp that ships with {sf})
What it does is that it builds two buffers as sf objects, and then constructs two logical vectors - sf::st_contains() for the big circle, and small circle.
Then it is a simple logical operation of checking for points that:
are contained within the big circle, and at the same time
are not contained within the small circle
Should you want to get more fancy you could run sf::st_difference() on the two buffer objects, and get the mask directly & check for sf::st_contains() only once for the "rim" object.
library(sf)
library(dplyr)
# 3 semi rancom cities in NC (because I *deeply love* the nc.shp file)
cities <- data.frame(name = c("Raleigh", "Greensboro", "Wilmington"),
x = c(-78.633333, -79.819444, -77.912222),
y = c(35.766667, 36.08, 34.223333)) %>%
st_as_sf(coords = c("x", "y"), crs = 4326)
# small buffer - Greensboro will be in; Wilmington not
small_buffer <- cities %>%
filter(name == "Raleigh") %>%
st_geometry() %>%
st_buffer(units::as_units(100, "mile"))
# big buffer - both Greensboro & Wilmington are in
big_buffer <- cities %>%
filter(name == "Raleigh") %>%
st_geometry() %>%
st_buffer(units::as_units(150, "mile"))
# a visual overview
mapview::mapview(list(big_buffer, small_buffer, cities))
# vector of cities in big buffer
in_big_buffer <- st_contains(big_buffer,
cities,
sparse = F) %>%
t()
# vector of cities in small buffer
in_small_buffer <- st_contains(small_buffer,
cities,
sparse = F) %>%
t()
# cities in concentric circle = in big, and not in small
in_concentric_circle <- in_big_buffer & !in_small_buffer
# check - subset of cities by logical vector
cities %>%
filter(in_concentric_circle)
# Simple feature collection with 1 feature and 1 field
# Geometry type: POINT
# Dimension: XY
# Bounding box: xmin: -77.91222 ymin: 34.22333 xmax: -77.91222 ymax: 34.22333
# Geodetic CRS: WGS 84
# name geometry
# 1 Wilmington POINT (-77.91222 34.22333)

Problem joining different SpatialPolygonsDataFrame objects in R

I have a shape file of towns in the north of Spain that I have to join into groups (municipalities or comarcas in Spanish). I've used st_union from the sf package to join them successfully (and each one is their own SpatialPolygonsDataFrame object with a single polygon). I plot each of the municipalities individually and they look fine.
However, once I want to combine the municipalities into a single SpatialPolygonsDataFrame object with multiple polygons, I can't for the life of me manage to do it. I've tried three approaches mostly based on this answer: https://gis.stackexchange.com/questions/155328/merging-multiple-spatialpolygondataframes-into-1-spdf-in-r and this one https://gis.stackexchange.com/questions/141469/how-to-convert-a-spatialpolygon-to-a-spatialpolygonsdataframe-and-add-a-column-t
– If I use raster::union it throws out the error
Error in .rowNamesDF<-(x, value = value) : invalid 'row.names' length
– If I use a simple rbind it throws out the error
Error in SpatialPolygonsDataFrame(pl, df, match.ID = FALSE) :
Object length mismatch:
pl has 7 Polygons objects, but df has 4 rows
Or something similar for 6/11 of the municipalities.
– If I try a lapply approach (more convoluted) it seems to work but one I plot it using leaflet the municipalities that gave the error when trying to raster::union or rbind don't look as they should/don't look as they do when I plot them individually.
** Municipalities 1 and 2 work fine. 3 and 4 for example do not. **
Here's a link to the two files needed to reproduce my code below:
– Link to shape files: https://www.dropbox.com/sh/z9632hworbbchn5/AAAiyq3f_52azB4oFeU46D5Qa?dl=0
– Link to xls file that contains the mapping from towns to municipalities: https://www.dropbox.com/s/4w3fx6neo4t1l3d/listado-comarcas-gipuzkoa.xls?dl=0
And my code:
library(tidyverse)
library(magrittr)
library(sf)
library(ggplot2)
library(lwgeom)
library(readxl)
library(raster)
#Read shapefile
mapa_municip <- readOGR(dsn = "UDALERRIAK_MUNICIPIOS/UDALERRIAK_MUNICIPIOS.shp")
mapa_municip <- spTransform(mapa_municip, CRS('+proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0'))
mapa_municip <- st_as_sf(mapa_municip)
#Read excel that contains mapping from town to municioalities
muni2com <- read_excel("listado-comarcas-gipuzkoa.xls",
sheet=1,
range="A1:C91",
col_names = T)
comarcas <- list()
count <- 0
for (i in unique(muni2com$Comarca)[1:4]){
count <- count + 1
for (k in unique(muni2com$Municipios[muni2com$Comarca==i])){
if (k == unique(muni2com$Municipios[muni2com$Comarca==i])[1]){ # if 1st case, keep this town
temp <- mapa_municip[mapa_municip$MUNICIPIO==k,]
}
if (k != unique(muni2com$Municipios[muni2com$Comarca==i])[1]){ # otherwise, join w previous ones
temp <- sf::st_union(temp, mapa_municip[mapa_municip$MUNICIPIO==k,])
}
}
comarcas[[count]] <- spTransform(as(temp, "Spatial"), CRS('+proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0'))
comarcas[[count]]#data <- data.frame(comarca = i)
}
IDs <- sapply(comarcas, function(x)
slot(slot(x, "polygons")[[1]], "ID"))
#Checking
length(unique(IDs)) == length(comarcas)
dfIDs <- data.frame(comarca = IDs)
#Making SpatialPolygons from list of polygons
comarcas2 <- SpatialPolygons(lapply(comarcas,
function(x) slot(x, "polygons")[[1]]))
# Try to coerce to SpatialPolygonsDataFrame (will throw error)
p.df <- data.frame( comarca = unique(muni2com$Comarca)[1:4])
p <- SpatialPolygonsDataFrame(comarcas2, p.df)
# Extract polygon ID's
( pid <- sapply(slot(comarcas2, "polygons"), function(x) slot(x, "ID")) )
# Create dataframe with correct rownames
( p.df <- data.frame( comarca = unique(muni2com$Comarca)[1:4], row.names = pid) )
# Try coertion again and check class
comarcas3 <- SpatialPolygonsDataFrame(comarcas2, p.df)
class(comarcas3)
#Leaflet map
leaflet( options = leafletOptions(zoomControl = F,
zoomSnap = 0.1 ,
zoomDelta = 1
),
data = comarcas3,
) %>%
addProviderTiles(provider="CartoDB.Positron") %>%
htmlwidgets::onRender("function(el, x) {
L.control.zoom({ position: 'topright' }).addTo(this)
}") %>%
clearShapes() %>%
addPolygons(fillColor = "gray",
opacity = 0.8,
weight = 0.3,
color = "white",
fillOpacity = 0.95,
smoothFactor = 0.5,
label = ~comarca,
highlight = highlightOptions(
weight = 1.5,
color = "#333333",
bringToFront = T),
layerId = ~comarca
)
** Note how if you plot comarcas[[3]] or comarcas[[4]] above instead of comarcas3 the shape of those municipalities is completely different.**
I'd really appreciate any tips you can give me, I've been at it for days and I can't solve it. I assume the problem is due to the error given by the rbind, which seems to be the most informative one, but I don't know what it means. Thank you very much in advance.
Are you absolutely positively required to use the older {sp} package workflow?
If not it may be easier to dissolve the municipalities into comarcas using a pure {sf} based workflow - grouping by a comarca column, and then summarising will do the trick.
Consider this code:
library(tidyverse)
library(sf)
library(readxl)
library(leaflet)
#Read shapefile
mapa_municip <- st_read("UDALERRIAK_MUNICIPIOS.shp") %>%
st_transform(4326)
#Read excel that contains mapping from town to municioalities
muni2com <- read_excel("listado-comarcas-gipuzkoa.xls",
sheet=1,
range="A1:C91",
col_names = T)
# dissolving comarcas using sf / dplyr based workflow
comarcas <- mapa_municip %>%
inner_join(muni2com, by = c("MUNICIPIO" = "Municipios")) %>%
group_by(Comarca) %>%
summarise() %>% # magic! :)))
ungroup()
leaflet(comarcas) %>%
addProviderTiles("CartoDB.Positron") %>%
addPolygons(color = "red",
label = ~ Comarca)

Using a shape file to download MODIS product data for country in R

Is there any way that can be used to parse a shapefile of a country and download MODIS product data within that country using R?
I tried different approaches using the MODIStsp package (https://docs.ropensci.org/MODIStsp/) as well as the MODISTools package (https://docs.ropensci.org/MODISTools/articles/modistools-vignette.html) and they both only allow me to download MODIS product data for a defined site, but not a country.
Here's an example of how you might achieve this.
Firstly, download the MODIS data that you require, in this example I'm using MCD12Q1.006
begin_year and end_year are in the format: Year.Month.Days.
shape_file is the shapefile you're using, presumably the extent of the shapefile is the country you're after. Though, I'm only going off by the minimal information you have provided.
library(MODIS)
tifs <- runGdal(product = "MCD12Q1", collection = "006", SDSstring = "01",
extent = shape_file %>% st_buffer(dist = 10000),
begin = begin_year, end = end_year,
outDirPath = "data", job = "modis",
MODISserverOrder = "LPDAAC") %>%
pluck("MCD12Q1.006") %>%
unlist()
# rename tifs to have more descriptive names
new_names <- format(as.Date(names(tifs)), "%Y") %>%
sprintf("modis_mcd12q1_umd_%s.tif", .) %>%
file.path(dirname(tifs), .)
file.rename(tifs, new_names)
landcover <- list.files("data/modis", "^modis_mcd12q1_umd",
full.names = TRUE) %>%
stack()
# label layers with year
landcover <- names(landcover) %>%
str_extract("(?<=modis_mcd12q1_umd_)[0-9]{4}") %>%
paste0("y", .) %>%
setNames(landcover, .)
Also, if you require a particular cell size, then you could follow this procedure to get a 5x5 modis cell size.
neighborhood_radius <- 5 * ceiling(max(res(landcover))) / 2
agg_factor <- round(2 * neighborhood_radius / res(landcover))
r <- raster(landcover) %>%
aggregate(agg_factor)
r <- shape_file %>%
st_transform(crs = projection(r)) %>%
rasterize(r, field = 1) %>%
# remove any empty cells at edges
trim()
Here's an example using MODISTools to automate downloading the correct tiles for the country.
First let's generate a polygon of a country to demonstrate (using Luxembourg as an example):
library(maptools)
library(sf)
data(wrld_simpl)
world = st_as_sf(wrld_simpl)
lux = world[world$NAME=='Luxembourg',]
Now we find the location (centroid) and size of the country:
#find centroid of polygon in long-lat decimal degrees
lux.cent = st_centroid(lux)
#find width and height of country in km
lux.proj = st_transform(lux,
"+proj=moll +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84 +units=km +no_defs")
lux.km_lr = diff(st_bbox(lux.proj)[c(1,3)])
lux.km_ab = diff(st_bbox(lux.proj)[c(2,4)])
Using this info, we can download the correct Modis data (using leaf-area index, lai, as an example):
#download the MODIS tiles for the area we defined
library(MODISTools)
lux_lai <- mt_subset(product = "MOD15A2H",
lat = lux.cent$LAT, lon = lux.cent$LON,
band = "Lai_500m",
start = "2004-01-01", end = "2004-01-01",
km_lr = lux.km_lr, km_ab = lux.km_ab,
site_name = "Luxembourg",
internal = TRUE, progress = TRUE)
# convert to a spatial raster
lux.rast = mt_to_raster(df = lux_lai, reproject = TRUE)
lux.rast = raster::mask(lux.rast, lux)
plot(lux.rast)
plot(st_geometry(lux),add=T)

dotsInPolys length mismatch using data downloaded via tidycensus

Can you help figure out the best way to resolve the length mismatch error thrown by dotsInPolys? I think it is because there are NA's or NULLs or some funk in the polygon data that makes it too long. Here's code that reproduces the error. Ultimately, I want to plot multiple races using Leaflet, but I can't produce the lat/lon needed for the random dots at this point.
require(maptools)
require(tidycensus)
person.number.divider <- 1000
census_api_key("ENTER KEY HERE", install = TRUE)
racevars <- c(White = "B02001_002", #"P005003"
Black = "B02001_003", #Black or African American alone
Latinx = "B03001_003"
)
nj.county <- get_acs(geography = "county", #tract
year = 2015,
variables = racevars,
state = "NJ", #county = "Harris County",
geometry = TRUE,
summary_var = "B02001_001")
library(sf)
st_write(nj.county, "nj.county.shp", delete_layer = TRUE)
nj <- rgdal::readOGR(dsn = "nj.county.shp") %>%
spTransform(CRS("+proj=longlat +datum=WGS84"))
nj#data <- nj#data %>%
tidyr::separate(NAME,
sep =",",
into = c("county", "state")) %>%
dplyr::select(estimat,variabl, GEOID, county) %>%
spread(key = variabl, value = estimat) %>%
mutate(county = trimws(county))
black.dots <- dplyr::select(nj#data, Black) / person.number.divider #%>%
black.dots <- dotsInPolys(nj, as.integer(black.dots$Black), f="random")
# Error in dotsInPolys(nj, as.integer(black.dots$Black), f = "random") :
# different lengths
length(nj) # 63 This seems too many, because I believe NJ has 21 counties.
length(black.dots$Black) # 21
This post (Advice on troubleshooting dotsInPolys error (maptools)) came close to helping me, but I couldn't see how to apply it to my case.
I can change the length of the nj spatialpolygonsdataframe by removing NA's and counties with a black pop greater than 0, but then the map doesn't plot multiple counties (maybe there is something wrong with the census download?).
It looks like you might have gotten this figured out, but I wanted to share another approach that uses sf::st_sample() instead of maptools::dotsInPolys(). One advantage of this is that you don't need to convert the sf object you get from tidycensus to a sp object.
In the following example I split the census data by race into a list three sf objects then perform st_sample() on each element of the list (each race). Next, I recombine the sampled points into one sf object with a new race variable for each point. Finally, I use tmap to make a map, though you could use ggplot2 or leaflet to map as well.
library(tidyverse)
library(tidycensus)
library(sf)
library(tmap)
person.number.divider <- 1000
racevars <- c(White = "B02001_002", #"P005003"
Black = "B02001_003", #Black or African American alone
Latinx = "B03001_003"
)
# get acs data with geography in "tidy" form
nj.county <- get_acs(geography = "county", #tract
year = 2015,
variables = racevars,
state = "NJ", #county = "Harris County",
geometry = TRUE,
summary_var = "B02001_001"
)
# split by race
county.split <- nj.county %>%
split(.$variable)
# randomly sample points in polygons based on population
points.list <- map(county.split, ~ st_sample(., .$estimate / person.number.divider))
# combine points into sf collections and add race variable
points <- imap(points.list, ~ st_sf(tibble(race = rep(.y, length(.x))), geometry = .x)) %>%
reduce(rbind)
# map!
tm_shape(nj.county) +
tm_borders(col = "darkgray", lwd = 0.5) +
tm_shape(points) +
tm_dots(col = "race", size = 0.01, pal = "Set2")
I don't have enough rep to post the map image directly, but here it is.

Resources