I want to produce a 2d-density plot based on spatial point data. In the background I want to show an open map (e.g. stamen terrain). Besides I want to plot the borders of Austria. Both datasets (data points and border) are shapefiles in EPSG 4326.
I managed to produce such a plot (see screenshots and code V1 below), but the problem is that there is a shift between the map in the background on the one side and the plotted points and the borders of Austria on the other side, as you can see below.
2D-Density Plot V1 - full
2D-Density Plot V1 - detail
Here is the code (V1):
library(sf)
library(rgdal)
library(ggplot2)
library(ggmap)
# Read point data (EPSG: 4326)
sk <- st_read("points.shp")
# Read country border polygon (EPSG: 4326)
blogr4326 <- readOGR(<path>, <layer name>)
bl4326_df <- fortify(blogr4326)
# Austria box (extent)
# Longitude: 9.6 to 16.94504
# Latitude: 46.52694 to 48.81667
map <- get_map(c(left = +9.6, bottom = 46.52694, right = +16.90, top = 48.99), color = "color", crop = FALSE)
hm_sk <- ggmap(map, extent = "panel", maprange=FALSE, darken=0.0) +
geom_point(data = sk, aes(x=X_WGS84, y=Y_WGS84)) +
stat_density2d(data = sk, aes(x=X_WGS84, y=Y_WGS84, fill = ..density.., alpha=cut(..density..,breaks=c(-Inf,0.08,Inf))), contour = FALSE, bins=16, geom = 'raster', n=500) +
ggtitle("Schwarzkiefer 2016/2020") + xlab("X_WGS84") + ylab("X_WGS84") +
scale_fill_distiller(palette= "Spectral", direction=-1, limits = c(0.08, 8.50)) +
scale_alpha_manual(values=c(0,0.7), guide="none") +
geom_polygon(data=bl4326_df, aes(long, lat, group=group), color='black', fill='NA', inherit.aes = TRUE) +
coord_fixed(1.5)
hm_sk
I found out that the shift is caused by the fact that the map in the background is in the projection EPSG:3857 and my shapefiles are in the projection EPSG:4326, as explained in this post. So I projected my shapefiles to EPSG 3857 and inserted the provided code into my code, as you can see here (V2):
library(sf)
library(rgdal)
library(ggplot2)
library(ggmap)
# Read point data (EPSG: 3857)
sk <- st_read("points.shp")
# Read country border polygon (EPSG: 3857)
blogr3857 <- readOGR(<path>, <layer name>)
bl3857_df <- fortify(blogr3857)
# Austria box (extent)
# Longitude: 9.6 to 16.94504
# Latitude: 46.52694 to 48.81667
map <- get_map(c(left = +9.6, bottom = 46.52694, right = +16.90, top = 48.99), color = "color", crop = FALSE)
#-------------------------------------------------------------------------------------------------
# Following code according to this link to avoid the shift between map and country border polygon:
# https://stackoverflow.com/questions/47749078/how-to-put-a-geom-sf-produced-map-on-top-of-a-ggmap-produced-raster
# Define a function to fix the bbox to be in EPSG:3857
ggmap_bbox <- function(map) {
if (!inherits(map, "ggmap")) stop("map must be a ggmap object")
# Extract the bounding box (in lat/lon) from the ggmap to a numeric vector,
# and set the names to what sf::st_bbox expects:
map_bbox <- setNames(unlist(attr(map, "bb")),
c("ymin", "xmin", "ymax", "xmax"))
# Convert the bbox to an sf polygon, transform it to 3857,
# and convert back to a bbox (convoluted, but it works)
bbox_3857 <- st_bbox(st_transform(st_as_sfc(st_bbox(map_bbox, crs = 4326)), 3857))
# Overwrite the bbox of the ggmap object with the transformed coordinates
attr(map, "bb")$ll.lat <- bbox_3857["ymin"]
attr(map, "bb")$ll.lon <- bbox_3857["xmin"]
attr(map, "bb")$ur.lat <- bbox_3857["ymax"]
attr(map, "bb")$ur.lon <- bbox_3857["xmax"]
map
}
# Use the function:
map <- ggmap_bbox(map)
#-------------------------------------------------------------------------------------------------
hm_sk <- ggmap(map,extent = "device", maprange=FALSE) +#, extent = "panel", maprange=FALSE, darken=0.0) +
coord_sf(crs = st_crs(3857)) + # f867orce the ggplot2 map to be in 3857
geom_point(data = sk, aes(x=X_PM, y=Y_PM)) +
stat_density2d(data = sk, aes(x=X_PM, y=X_PM, fill = ..density.., alpha=cut(..density..,breaks=c(-Inf,0.08,Inf))), contour = FALSE, bins=16, geom = 'raster', n=500) +
scale_fill_distiller(palette= "Spectral", direction=-1, limits = c(0.08, 8.50)) +
scale_alpha_manual(values=c(0,0.7), guide="none") +
geom_polygon(data=bl3857_df, aes(long, lat, group=group), color='black', fill='NA', inherit.aes = FALSE) +
ggtitle("Schwarzkiefer 2016/2020") + xlab("X_3857") + ylab("X_3857")
hm_sk
Now, the problem with the shift is solved, but the density plot is not visible anymore (only map, points and borders are plotted), as you can see here:
2D-Density Plot V2 - full
2D-Density Plot V2 - detail
Any suggestions, how I can produce a plot that is proper aligned AND includes the density plot? Thanks a lot in advance!
Related
I am trying to calculate distance of points within a country to country boundary
library(terra)
library(geodata)
library(ggplot2)
library(geodata)
# get a shapefile of a country
gabon <- geodata::gadm('GAB', level = 0, path = getwd())
canvas <- terra::rast(xmin = ext(gabon)[1],
xmax = ext(gabon)[2],
ymin = ext(gabon)[3],
ymax = ext(gabon)[4],
resolution = 0.08,
crs = crs(gabon),
vals = 0)
pts <- as.points(canvas)
pts <- terra::crop(pts, gabon) # extract the points in the limits of Gabon
plot(pts)
plot(gabon, border = "blue", add = T)
I want to calculate shortest distance of each point in pts to the boundary of the country
gabon_lines <- terra::as.lines(gabon)
# calculation of the distance between the boundary and points
dis_pts <- terra::distance(pts, gabon_lines, pairwise = FALSE, unit="km")
range(dis_pts)
# 0.00000046 1.63706213. seems quite low
dat <- data.frame(dist = as.vector(dis_pts),
crds(pts))
col_dist <- brewer.pal(11, "RdGy")
ggplot(dat, aes(x, y, fill = dist)) + #variables
geom_tile() + #geometry
scale_fill_gradientn(colours = rev(col_dist))+ # colors for plotting the distance
labs(fill = "Distance (km)")+ #legend name
theme_void()+ #map theme
theme(legend.position = "bottom") #legend position
I think the range of distance I am getting is very low since Gabon is quite big so I was expecting
distance of points in the middle to be larger. Is there anything I am doing wrong here?
The problem seems to be with the crs used. The result you have above is accurate, but the units are in degrees (latitude & longitude). A relatively quick fix is to reproject the data using crs 5223.
Most of the code below is copied, changes are below ####
library(terra)
library(geodata)
library(ggplot2)
library(scales)
library(RColorBrewer)
# get a shapefile of a country
gabon <- geodata::gadm('GAB', level = 0, path = getwd())
canvas <- terra::rast(xmin = ext(gabon)[1],
xmax = ext(gabon)[2],
ymin = ext(gabon)[3],
ymax = ext(gabon)[4],
resolution = 0.08,
crs = crs(gabon),
vals = 0)
pts <- as.points(canvas)
pts <- terra::crop(pts, gabon) # extract the points in the limits of Gabon
plot(pts)
plot(gabon, border = "blue", add = T)
gabon_lines <- terra::as.lines(gabon)
####
# reproject pts & gabon lines to this new crs:
new_crs <- "+proj=tmerc +lat_0=0 +lon_0=12 +k=0.9996 +x_0=500000 +y_0=500000 +datum=WGS84 +units=m +no_defs +type=crs"
pts2 <- terra::project(pts, new_crs)
gabon_lines2 <- terra::project(gabon_lines, new_crs)
# calculation of the distance between the boundary and points
dis_pts <- terra::distance(pts2, gabon_lines2, pairwise = FALSE, unit="km")
range(dis_pts)
## Now from 1 to about 180 km
## a quick check on google maps & the interior of Gabon is ~180km from the nearest border
dat <- data.frame(dist = as.vector(dis_pts),
crds(pts))
col_dist <- brewer.pal(11, "RdGy")
## Not much change from the plot before, but lat & lon degrees are approximately the same near the equator
ggplot(dat, aes(x, y, fill = dist)) + #variables
geom_tile() + #geometry
scale_fill_gradientn(colours = rev(col_dist))+ # colors for plotting the distance
labs(fill = "Distance (km)")+ #legend name
theme_void()+ #map theme
theme(legend.position = "bottom") #legend position
The dimensions come out a little wonky since the plot isn't using a crs. Changing the data to sf points makes things look a little better:
library(sf)
st_as_sf(dat, coords = c("x", "y")) %>%
ggplot() +
geom_sf(aes(color = dist)) +
scale_color_gradientn(colours = rev(col_dist))
That needs to be fixed, but you can do this
library(terra)
library(geodata)
# get a shapefile of a country
gabon <- geodata::gadm('GAB', level = 0, path = getwd())
canvas <- terra::rast(gabon, resolution = 0.08, vals=0)
m <- mask(canvas, gabon, inverse=TRUE)
d <- distance(m)
plot(d)
When plotting my map of NLCD tree canopy data (a raster), the map is not straight or aligned with the map border. The skew seems to occur when using the projectRaster function. Is there a way to adjust the raster to be level and even with the border? When running the code below/looking at the uploaded image, the skew that I would like to remove is the dark grey uneven border while having the tree data fill the entire frame. I have been unable to find any solutions to this, so I appreciate any advice!
Picture of skew: 1
library(sf)
#devtools::install_github("ropensci/FedData")
library(FedData)
library(ggplot2)
library(raster)
library(RColorBrewer)
ext <- extent(734666.5, 795961.6, 4017870, 4053632)
extent <- polygon_from_extent(raster::extent(ext), proj4string='+proj=utm +datum=NAD83 +zone=16N')
WMAtc <- get_nlcd(template = polygon_from_extent(ext, (proj4string = "+proj=utm +zone=16N ellps+NAD83")), year = 2016, dataset = "Tree_Canopy", label = "Can", force.redo = T)
WMAtc_projected <- projectRaster(WMAtc, crs = "+proj=utm +zone=16N +datum=NAD83")
tc_df <- as.data.frame(WMAtc_projected, xy=TRUE)
ggplot() +
geom_raster(data = tc_df, aes(x = x, y = y, fill=tc_df[,3])) +
scale_fill_gradientn(colors = brewer.pal(8, "Greens"), name = "Tree Canopy Cover")
A professor graciously helped me fix the problem. With his code you can see where the extent bounding box is in relation to the downloaded NLCD data. When projected, the NLCD data is adjusted and extended past the original extent. To fix this, you can crop the extra data and it will realign the imagery of your maps.
library(FedData)
library(ggplot2)
library(raster)
library(RColorBrewer)
library(sf)
ext <- extent(734666.5, 795961.6, 4017870, 4053632)
extent <- polygon_from_extent(raster::extent(ext), proj4string='+proj=utm +datum=NAD83 +zone=16N')
WMAtc <- get_nlcd(template = polygon_from_extent(ext, (proj4string = "+proj=utm +zone=16N ellps+NAD83")),year = 2016, dataset = "Tree_Canopy", label = "Can")
WMAtc_projected <- projectRaster(WMAtc, crs = "+proj=utm +zone=16N +datum=NAD83")
tc_df <- as.data.frame(WMAtc_projected, xy=TRUE)
ggplot() +
geom_raster(data = tc_df, aes(x = x, y = y, fill=tc_df[,3])) +
scale_fill_gradientn(colors = brewer.pal(8, "Greens"), name = "Tree Canopy Cover") +
geom_sf(data=st_as_sf(extent), fill=NA, col="red") # show desired raster extent
WMAtc_projected_cropped = crop(WMAtc_projected, extent) #crop to remove extra bits
tc_df_cropped <- as.data.frame(WMAtc_projected_cropped, xy=TRUE)
ggplot() +
geom_raster(data = tc_df_cropped, aes(x = x, y = y, fill=tc_df_cropped[,3])) +
scale_fill_gradientn(colors = brewer.pal(8, "Greens"), name = "Tree Canopy Cover") +
geom_sf(data=st_as_sf(extent), fill=NA, col="red")
I have a data set of latitude/longitude points that seek to convert to a simple feature (sf) in R.
My goal is to plot these locations on a US map with polygons retrieved from the urbnmapr library.
Plotting with our a geographic reference, as shown in the code, results in all points being displayed.
When the points are plotted using geom_sf() they end up in South Dakota. It seems the latitude/longitude points are not being converted into the correct coordinate reference system, despite what I think is the correct use of the st_as_sf() function.
What correction needs to be made to this code to show the distribution of wind turbine locations properly on the US map?
# Map the locations of US Wind Turbines
library(urbnmapr)
library(ggplot2)
library(readr)
library(dplyr)
library(sf)
# This file is available from https://eerscmap.usgs.gov/uswtdb/assets/data/uswtdbCSV.zip
turbine <- read_csv("C:\\mydir\\uswtdb_v3_1_20200717.csv")
# Convert lat/long to a sf
turbine_sf <- turbine %>%
st_as_sf(coords = c("xlong", "ylat"), crs=2163)
# obtain state geometries
states_sf <- get_urbn_map(map = "states", sf = TRUE)
# Remove AK, HI from state and PR and GU from turbines as well
states_sf <- states_sf[!(states_sf$state_abbv %in% c("HI","AK")),]
turbine <- turbine [!(turbine$t_state %in% c('HI','AK','PR','GU')),]
# simple plot shows all locations
ggplot(turbine, aes(x=xlong, y=ylat)) + geom_point()
#plot locations over map
ggplot() +
geom_sf(data = turbine_sf) +
geom_sf(data = states_sf, fill = NA, color = "black", size = 0.15, alpha = 0) +
coord_sf(datum = st_crs(2163)) +
labs(fill = "",
title = "",
caption='') +
theme_bw()
Your turbine dataset contains "xlong" and "ylat" in degrees i.e. geographic coordinate system with WGS84 datum (EPSG code: 4326). So, first, make it as crs = 4326 and then use st_transform(turbine_sf, crs=2163) to make same coordinate system with states_sf. You can use the following code
# Map the locations of US Wind Turbines
library(urbnmapr)
library(ggplot2)
library(readr)
library(dplyr)
library(sf)
# This file is available from https://eerscmap.usgs.gov/uswtdb/assets/data/uswtdbCSV.zip
turbine <- read_csv("uswtdb_v3_1_20200717.csv")
# Convert lat/long to a sf
turbine_sf <- turbine %>%
st_as_sf(coords = c("xlong", "ylat"), crs=4326)
turbine_sf_t <- st_transform(turbine_sf, crs=2163)
# obtain state geometries
states_sf <- get_urbn_map(map = "states", sf = TRUE)
st_crs(states_sf)
# Remove AK, HI from state and PR and GU from turbines as well
states_sf <- states_sf[!(states_sf$state_abbv %in% c("HI","AK")),]
turbine <- turbine [!(turbine$t_state %in% c('HI','AK','PR','GU')),]
# simple plot shows all locations
ggplot(turbine, aes(x=xlong, y=ylat)) + geom_point()
#plot locations over map
ggplot() +
geom_sf(data = turbine_sf_t) +
geom_sf(data = states_sf, fill = NA, color = "black", size = 0.15, alpha = 0) +
coord_sf(datum = st_crs(2163)) +
labs(fill = "",
title = "",
caption='') +
theme_bw()
By doing st_as_sf(coords = c("xlong", "ylat"), crs=2163) you're saying that the original long, lat from your turbine table are based on CRS of 2163. I think you want to set them as 4326 which is the long lat under WGS84.
After setting the initial CRS, use st_transform() to transform the CRS of your shape to new CRS, e.g. turbine_sf <- st_transform(turbine_sf, crs=2163)
Is it possible to have a layer in ggplot that acts as a mask for a ggmap layer? Here they added a country polygon on top of a ggmap.
What I am looking for is that the country would be a "hole" in a layer (with alpha) covering everything but the country. In a way the inverse of the example above. The code from that answer (with transparency added and updated to use geom_cartogram).
library(mapdata)
library(ggmap)
library(ggplot2)
library(ggalt)
# Get Peru map
Peru <- get_map(location = "Peru", zoom = 5, maptype="satellite")
# This is the layer I wish to put over the top
coast_map <- fortify(map("worldHires", fill = TRUE, plot = FALSE))
# Subset data for Peru
peru.coast <- subset(coast_map, region == "Peru")
# Draw a graphic
ggmap(Peru) +
geom_cartogram(data = peru.coast, map = peru.coast, aes(x = long, y = lat, map_id = region),
fill="white", color="grey", alpha=.1) +
xlim(-86, -68) +
ylim(-20, 0) +
labs(x = "Longitude", y = "Latitude") +
coord_map() +
theme_classic()
Is there a way to fill everything but a polygon in ggplot2?
Is there a way to fill everything but a polygon in ggplot2?
This method may be a bit unorthodox, but anyway:
library(mapdata)
library(ggmap)
library(ggplot2)
library(raster)
ggmap_rast <- function(map){
map_bbox <- attr(map, 'bb')
.extent <- extent(as.numeric(map_bbox[c(2,4,1,3)]))
my_map <- raster(.extent, nrow= nrow(map), ncol = ncol(map))
rgb_cols <- setNames(as.data.frame(t(col2rgb(map))), c('red','green','blue'))
red <- my_map
values(red) <- rgb_cols[['red']]
green <- my_map
values(green) <- rgb_cols[['green']]
blue <- my_map
values(blue) <- rgb_cols[['blue']]
stack(red,green,blue)
}
Peru <- get_map(location = "Peru", zoom = 5, maptype="satellite")
data(wrld_simpl, package = "maptools")
polygonMask <- subset(wrld_simpl, NAME=="Peru")
peru <- ggmap_rast(Peru)
peru_masked <- mask(peru, polygonMask, inverse=T)
peru_masked_df <- data.frame(rasterToPoints(peru_masked))
ggplot(peru_masked_df) +
geom_point(aes(x=x, y=y, col=rgb(layer.1/255, layer.2/255, layer.3/255))) +
scale_color_identity() +
coord_quickmap()
Via this, this, and this questions/answers.
What I am looking for is the surroundings with a transparent fill
layer and Peru with alpha=1
If first thought this is easy. However, then I saw and remembered that geom_polygon does not like polygons with holes very much. Luckily, geom_polypath from the package ggpolypath does. However, it will throw an "Error in grid.Call.graphics(L_path, x$x, x$y, index, switch(x$rule, winding = 1L..." error with ggmaps default panel extend.
So you could do
library(mapdata)
library(ggmap)
library(ggplot2)
library(raster)
library(ggpolypath) ## plot polygons with holes
Peru <- get_map(location = "Peru", zoom = 5, maptype="satellite")
data(wrld_simpl, package = "maptools")
polygonMask <- subset(wrld_simpl, NAME=="Peru")
bb <- unlist(attr(Peru, "bb"))
coords <- cbind(
bb[c(2,2,4,4)],
bb[c(1,3,3,1)])
sp <- SpatialPolygons(
list(Polygons(list(Polygon(coords)), "id")),
proj4string = CRS(proj4string(polygonMask)))
sp_diff <- erase(sp, polygonMask)
sp_diff_df <- fortify(sp_diff)
ggmap(Peru,extent="normal") +
geom_polypath(
aes(long,lat,group=group),
sp_diff_df,
fill="white",
alpha=.7
)
I'm trying to limit a map plot to a specific area. coord_map is preferred to scale_x_continuous and y equivalent, since the latter mess up the polygons. But here I'm finding it fails for some reason. Here's my code (downloads a 57kb shapefile):
require(maptools)
require(ggplot2)
download.file('https://dl.dropboxusercontent.com/u/46043231/UK.zip', "uk.zip", method="internal", mode="wb")
unzip('uk.zip')
uk = readShapePoly('uk_outline_1000m.shp')
print(bbox(uk))
min max
x 259.9625 655566.4
y 7211.7025 1218558.9
uk2 = fortify(uk)
(p = ggplot(uk2, aes(x=long, y=lat, group=group)) + geom_polygon() + coord_equal())
But when coord_map is used the plot disappears:
p + coord_map(xlim=c(0, 630000), ylim=c(0, 1000000))
Any idea what's going on??
I would try something like this to test a few options.
library(maptools)
library(ggplot2)
library(rgdal)
library(raster)
library(latticeExtra)
Download and read the data
download.file('https://dl.dropboxusercontent.com/u/46043231/UK.zip',
"uk.zip", method="internal", mode="wb")
unzip('uk.zip')
uk <- readOGR(dsn = getwd(), layer = 'uk_outline_1000m')
Data is assumed to use OSGB 1936 / British National Grid.
More about at SpatialReference
proj4string(uk) <- CRS('+init=epsg:27700') # EPSG 27700
extent(uk)
bb.uk <- as(extent(uk), 'SpatialPolygons') # a spatial object
proj4string(bb.uk) <- CRS('+init=epsg:27700')
Write projected shapefile of uk bbox. I'll write it out to map layers with QGIS. It will be my reference system.
writeOGR(as(bb.uk, 'SpatialPolygonsDataFrame'),
dsn = getwd(),
layer = 'bbuk2_bng', driver = 'ESRI Shapefile')
The desired bounding box. Using projected coordinates
bb.uk2 <- as(extent(c(0, 630000), c(0, 1000000)), 'SpatialPolygons')
proj4string(bb.uk2) <- CRS('+init=epsg:27700')
Write projected shapefile of user bbox
writeOGR(as(bb.uk2, 'SpatialPolygonsDataFrame'),
dsn = getwd(),
layer = 'bbuk2user_bng', driver = 'ESRI Shapefile')
QGis map using British National Grid EPSG:27700
Plot projected layers
Base plot
plot(uk, col = 'grey50', axes = T, xlim=c(-50000, 705566.4),
ylim=c(-50000, 1325000))
plot(bb.uk, add = T)
plot(bb.uk2, border = 'red', add = T)
spplot
I took a arbitrary window to expand plot area.
sp::spplot(uk, zcol = 'NAME_ISO', scales = list(draw = TRUE),
xlim=c(-50000, 705566.4), ylim=c(-50000, 1325000),
col.regions="grey90") +
latticeExtra::layer(sp.polygons(bb.uk, fill = NA, col = 'blue')) +
latticeExtra::layer(sp.polygons(bb.uk2, fill = NA, col = 'red'))
ggmap with projected layers
uk.df = fortify(uk) # admin
bbuk.df <- fortify(bb.uk) # country bbox extent
bbuk2.df <- fortify(bb.uk2) # user bbox extent
plot it
p <- ggplot() + geom_polygon(data = uk.df, aes(x=long, y=lat, group=group)) +
geom_polygon(data = bbuk.df, aes(x=long, y=lat, group=group),
colour = 'blue', fill = NA) +
geom_polygon(data = bbuk2.df, aes(x=long, y=lat, group=group),
colour = 'red', fill = NA) +
coord_equal() # cartesian
p
plot it with user bounding box
p + coord_equal(xlim=c(0, 630000), ylim=c(0, 1000000))
Now ggplot with geographic (unprojected) coordinates
WGS84 Unprojected Coordinate Reference System
p.wgs84 <- CRS("+init=epsg:4326") # WGS84 Long Lat
Convert projected layer to WGS84
uk.wgs89 <- spTransform(uk, p.wgs84)
Geographic bbox uk
bbuk.wgs84 <- as(as(extent(uk.wgs89), 'SpatialPolygons'),
'SpatialPolygonsDataFrame')
Geographic bbox of user extent
bbuk2.wgs84 <- spTransform(bb.uk2, p.wgs84)
bbuk2.wgs84 <- as(bbuk2.wgs84, 'SpatialPolygonsDataFrame')
Plot it with ggplot and cartesian map
uk.df = fortify(uk.wgs89) # admin
bbuk.df <- fortify(bbuk.wgs84) # country bbox extent
bbuk2.df <- fortify(bbuk2.wgs84) # user bbox extent
The result is not what I'd expect. I don't figure out why the red box is distorted.