Trouble overlaying kriged data on google map - r

In the code below, I am trying to overlay kriged meuse data on to google map usingggmap().The code seems to work ok all the way down toget_map(),but gets stuck in some kind of huge computation (RAM usage jumps to 7G on my 8G machine - be warned!) at the lastprint(ggmap())execution. What am I doing wrong?
UPDATE: I think I figured out the problem =>stat_contour()does not seem to work with the kriged data (still not sure why). When I replace it with the binning functionstat_summary_2d(),the code now works. I'd still like to show contour lines, if possible. Can someone help with that?
Thanks
# transform meuse data to SpatialPointsDataFrame
suppressMessages(library(sp))
data(meuse)
coordinates(meuse) <- ~ x + y
proj4string(meuse) <- CRS("+proj=stere
+lat_0=52.15616055555555 +lon_0=5.38763888888889
+k=0.999908 +x_0=155000 +y_0=463000
+ellps=bessel +units=m +no_defs
+towgs84=565.2369,50.0087,465.658,
-0.406857330322398,0.350732676542563,-1.8703473836068, 4.0812")
# define sample grid for kriging
set.seed(42)
grid <-spsample(meuse, type = "regular", n = 10000)
# do kriging
suppressMessages(library(automap))
krg <- autoKrige(formula = copper ~ 1,
input_data = meuse,
new_data = grid)
# extract kriged data
krg_df <- data.frame(krg$krige_output#coords,
pred = krg$krige_output#data$var1.pred)
names(krg_df) <- c("x", "y", "pred")
# transform to SpatialPointsDF & assign original (meuse) projection
krg_spdf <- krg_df
coordinates(krg_spdf) <- ~ x + y
proj4string(krg_spdf) <- proj4string(meuse)
# transform again to longlat coordinates (for overlaying on google map below)
krg_spdf <- spTransform(krg_spdf, CRS("+init=epsg:4326"))
krg_df <- data.frame(krg_spdf#coords, pred = krg_spdf#data$pred)
# get meuse map and overlay kriged data
suppressMessages(library(ggmap))
lon <- range(krg_df$x)
lat <- range(krg_df$y)
meuse_map <- get_map(location = c(lon = mean(lon), lat = mean(lat)),
zoom = 13)
print(ggmap(meuse_map, extent = "normal", maprange = F) +
#### old code (does not work) ####
#stat_contour(aes(x = x, y = y, z = pred, fill = ..level..),
# alpha = 0.5,
# color = "gray80",
# geom = "polygon",
# data = df) +
#### replaced with stat_summary_2d() ####
stat_summary_2d(aes(x = x, y = y, z = pred),
binwidth = c(0.0005,0.0005),
alpha = 0.5,
data = krg_df) +
scale_fill_gradient(low = "yellow",
high = "red",
name = "Copper") +
coord_cartesian(xlim = lon, ylim = lat, expand = 0) +
theme(aspect.ratio = 1))

Related

Create random points based in distance and boundary conditions

In my example, I have:
# Packages
library(sf)
library(ggplot2)
# Create some points
set.seed(1)
df <- data.frame(
gr = c(rep("a",5),rep("b",5)),
x = rnorm(10),
y = rnorm(10)
)
df <- st_as_sf(df,coords = c("x","y"),remove = F, crs = 4326)
df.laea = st_transform(
df,
crs = "+proj=laea +x_0=4600000 +y_0=4600000 +lon_0=0.13 +lat_0=0.24 +datum=WGS84 +units=m"
)
# Create a countour of the area
ch <- st_convex_hull(st_union(df.laea))
ggplot() +
geom_sf(data = ch, fill = "white", color = "black") +
geom_sf(data = df.laea,color = "black")
Now, I'd like to create 10 random points but the conditions are that this points must be inside the ch boundaries and a minimum distance of 10 meters of each df.laea points that exist inside this ch area.
Please, any help with it?
I think the only tricky thing here is that a simple st_difference() of your polygon and the buffered points will return ten polygons, each with one of the points removed. Thus you have to either use a for loop or reduce() to remove one buffered point after the other from the polygon. To use reduce() you have to transform the vector to a proper list of sf instead of an sfc vector. This is what I did below.
# Packages
library(sf)
library(ggplot2)
library(purrr)
ch_minus <- df.laea$geometry |>
st_buffer(10000) |>
{\(vec) map(seq_along(vec), \(x) vec[x])}() |> # Transform buffered points to reducible list
reduce(.init = ch, st_difference)
sampled_points <- st_sample(ch_minus, 10)
ch_minus |>
ggplot() +
geom_sf() +
geom_sf(data = sampled_points)
You can buffer the points by the distance you'd like, then intersect those polygons with the ch polygon. From there, use st_sample and the associated arguments to get the points you want.
Example code:
## buffer df.laea 10m
laea_buff <- st_buffer(df.laea, dist = 10000) #changed dist to 10km to make it noticable in plot
# area to sample from:
sample_area <- st_intersection(ch, laea_buff)
# sample above area, all within 10km of a point and inside the `ch` polygon
points <- st_sample(sample_area, size = 10)
#plotting:
ggplot() +
geom_sf(data = points, color = 'red') +
geom_sf(data = laea_buff, color = 'black', fill = NA) +
geom_sf(data = ch, color = 'black', fill = NA) +
geom_sf(data = sample_area, color = 'pink', fill = NA) +
geom_sf(data = df.laea, color = 'black', size = .5)
Created on 2023-02-14 by the reprex package (v2.0.1)
As a comment on the nice answer by shs: it is possible to first use a sf::st_combine() call on the df.laea object & merge the 10 points to a single multipoint geometry.
This, when buffered, will work as an input for the necessary sf::st_difference() call to form a sampling area with holes, removing the need for a for cycle / map & reduce call.
# Packages
library(sf)
library(ggplot2)
# Create some points
set.seed(1)
df <- data.frame(
gr = c(rep("a",5),rep("b",5)),
x = rnorm(10),
y = rnorm(10)
)
df <- st_as_sf(df,coords = c("x","y"),remove = F, crs = 4326)
df.laea = st_transform(
df,
crs = "+proj=laea +x_0=4600000 +y_0=4600000 +lon_0=0.13 +lat_0=0.24 +datum=WGS84 +units=m"
)
# merge 10 points to 1 multipoing
mod_laea <- df.laea %>%
st_combine()
# sampling area = difference between hull and buffered points
sampling_area <- mod_laea %>%
st_convex_hull() %>%
st_difference(st_buffer(mod_laea, 10000))
# sample over sampling area
sampled_points <- st_sample(sampling_area, 10)
# a visual overview
ggplot() +
geom_sf(data = sampling_area, fill = "white", color = "black") +
geom_sf(data = df.laea, color = "black") +
geom_sf(data = sampled_points, color = "red", pch = 4)

Using gBuffer from rgeos with correct projection

I want to show 15 mile radius circles around points in a map using gBuffer. As far as I can tell I have the points and the map in the same projection, but when I produce the circles on the map, they are too large. Here is my code. The tigerline files for the state and counties can be found at https://www.census.gov/cgi-bin/geo/shapefiles/index.php.
library(tidyverse)
library(rgdal)
library(rgeos)
library(ggplot2)
state <- readOGR('C:\\Users\\Mesonet\\Desktop\\map_folder\\tl_2020_us_state\\tl_2020_us_state.shp')
state <- state[which(state$STATEFP == '46'),]
state <- spTransform(state, CRS("+init=epsg:3857"))
counties <- readOGR('C:\\Users\\Mesonet\\Desktop\\map_folder\\tl_2020_us_county\\tl_2020_us_county.shp')
counties <- counties[which(counties$STATEFP == '46'),]
counties <- spTransform(counties, CRS("+init=epsg:3857"))
sites <- data.frame(Lon = c(-98.1096,-98.27935), Lat = c(43.9029, 43.717258))
coordinates(sites) <- ~Lon + Lat
proj4string(sites) <- CRS("+proj=longlat")
sites <- spTransform(sites, CRS = CRS("+init=epsg:3857"))
# Miles to meters conversion
mile2meter <- function(x){x * 1609.344}
# Buffer creation
site_buffer <- gBuffer(sites, width = mile2meter(15))
png('C:\\Users\\Mesonet\\Desktop\\map_folder\\new_test.png', height = 3000, width = 42*100, res = 100)
ggplot() + geom_path(counties, mapping = aes(x = long, y = lat, group = group), size = 1.75,
alpha = 0.45, col = 'darkgreen') + geom_path(state, mapping = aes(x = long, y = lat, group =
group), size = 0.8) + theme(axis.text = element_blank()) + geom_polygon(site_buffer, mapping
= aes(x = long, y = lat, group = group), fill = '#0000FF', alpha = 1, size = 2)
dev.off()
These two locations are 15.35 miles apart, but the plot shows two circles that overlap each other by a couple miles. I can't figure out why, since from what I can see everything is in the same projection, but I might be wrong. Thank you.

Create shaded polygons around points with ggplot2

I saw yesterday this beautiful map of McDonalds restaurants in USA. I wanted to replicate it for France (I found some data that can be downloaded here).
I have no problem plotting the dots:
library(readxl)
library(ggplot2)
library(raster)
#open data
mac_do_FR <- read_excel("./mcdo_france.xlsx")
mac_do_FR_df <- as.data.frame(mac_do_FR)
#get a map of France
mapaFR <- getData("GADM", country="France", level=0)
#plot dots on the map
ggplot() +
geom_polygon(data = mapaFR, aes(x = long, y = lat, group = group),
fill = "transparent", size = 0.1, color="black") +
geom_point(data = mac_do_FR_df, aes(x = lon, y = lat),
colour = "orange", size = 1)
I tried several methods (Thiessen polygons, heat maps, buffers), but the results I get are very poor. I can't figure out how the shaded polygons were plotted on the American map. Any pointers?
Here's my result, but it did take some manual data wrangling.
Step 1: Get geospatial data.
library(sp)
# generate a map of France, along with a fortified dataframe version for ease of
# referencing lat / long ranges
mapaFR <- raster::getData("GADM", country="France", level=0)
map.FR <- fortify(mapaFR)
# generate a spatial point version of the same map, defining your own grid size
# (a smaller size yields a higher resolution heatmap in the final product, but will
# take longer to calculate)
grid.size = 0.01
points.FR <- expand.grid(
x = seq(min(map.FR$long), max(map.FR$long), by = grid.size),
y = seq(min(map.FR$lat), max(map.FR$lat), by = grid.size)
)
points.FR <- SpatialPoints(coords = points.FR, proj4string = mapaFR#proj4string)
Step 2: Generate a voronoi diagram based on store locations, & obtain the corresponding polygons as a SpatialPolygonsDataFrame object.
library(deldir)
library(dplyr)
voronoi.tiles <- deldir(mac_do_FR_df$lon, mac_do_FR_df$lat,
rw = c(min(map.FR$long), max(map.FR$long),
min(map.FR$lat), max(map.FR$lat)))
voronoi.tiles <- tile.list(voronoi.tiles)
voronoi.center <- lapply(voronoi.tiles,
function(l) data.frame(x.center = l$pt[1],
y.center = l$pt[2],
ptNum = l$ptNum)) %>%
data.table::rbindlist()
voronoi.polygons <- lapply(voronoi.tiles,
function(l) Polygon(coords = matrix(c(l$x, l$y),
ncol = 2),
hole = FALSE) %>%
list() %>%
Polygons(ID = l$ptNum)) %>%
SpatialPolygons(proj4string = mapaFR#proj4string) %>%
SpatialPolygonsDataFrame(data = voronoi.center,
match.ID = "ptNum")
rm(voronoi.tiles, voronoi.center)
Step 3. Check which voronoi polygon each point on the map overlaps with, & calculate its distance to the corresponding nearest store.
which.voronoi <- over(points.FR, voronoi.polygons)
points.FR <- cbind(as.data.frame(points.FR), which.voronoi)
rm(which.voronoi)
points.FR <- points.FR %>%
rowwise() %>%
mutate(dist = geosphere::distm(x = c(x, y), y = c(x.center, y.center))) %>%
ungroup() %>%
mutate(dist = ifelse(is.na(dist), max(dist, na.rm = TRUE), dist)) %>%
mutate(dist = dist / 1000) # convert from m to km for easier reading
Step 4. Plot, adjusting the fill gradient parameters as needed. I felt the result of a square root transformation looks quite good for emphasizing distances close to a store, while a log transformation is rather too exaggerated, but your mileage may vary.
ggplot() +
geom_raster(data = points.FR %>%
mutate(dist = pmin(dist, 100)),
aes(x = x, y = y, fill = dist)) +
# optional. shows outline of France for reference
geom_polygon(data = map.FR,
aes(x = long, y = lat, group = group),
fill = NA, colour = "white") +
# define colour range, mid point, & transformation (if desired) for fill
scale_fill_gradient2(low = "yellow", mid = "red", high = "black",
midpoint = 4, trans = "sqrt") +
labs(x = "longitude",
y = "latitude",
fill = "Distance in km") +
coord_quickmap()

Boundary polygon of lat lon collection

I have a table containing all the latitudes and longitudes of some locations in a city called queryResult and I do the following:
1 - Get the Raster map of the city[Blackpool for instance]
cityMapRaster = get_map(location = 'Blackpool', zoom = 12, source = 'google', maptype = 'roadmap')
dataToShow <- ggmap(cityMapRaster) + geom_point(aes(x = Longitude, y = Latitude), data = queryResult, alpha = .5, color = "darkred", size = 1)
print(dataToShow)
and this will return the following points on the map
Now I want to draw the outer boundary [city border line] of all these latitude and longitudes similar to the following expected result
Update 1 : Providing input data and applying suggested ahull solution:
ggmap(cityMapRaster) + geom_point(aes(x = Longitude, y = Latitude), data = queryResult, alpha = .5, color = "darkred") + ahull.gg
I applied the ahull solution suggested by #spacedman and #cuttlefish44 and got the following result which is far different than the expected polygon:
You can download the .csv file containing all latitudes and longitudes from the following link : Blackpool Lat,Lon
Googles suggested area boundary looks like the following :
If you don't want a simple convex hull (and the polygon you've drawn is far from convex) then look at alpha-shapes in the alphahull package.
I wrote an example of how to get a polygon from an alpha-shape using that package and some points that make up a complex Norway boundary:
http://rpubs.com/geospacedman/alphasimple
You should be able to follow that to get a polygon for your data, and it might even be simpler now since that was a few years ago.
Here's a reproducible example of how to use chull to calculate a convex hull solution to this. I just generate some random points for queryResult, as you did not provide data.
If you prefer a concave hull boundary, then see the answer from #Spacedman
library(ggmap)
cityMapRaster = get_map(location = 'Blackpool', zoom = 12, source = 'google', maptype = 'roadmap')
extent = attr(cityMapRaster, "bb")
queryResult = data.frame(Longitude = rnorm(200, as.numeric(extent[2] + extent[4])/2, 0.01),
Latitude = rnorm(200, as.numeric(extent[1] + extent[3])/2, 0.02))
boundary = chull(as.matrix(queryResult))
ggmap(cityMapRaster) +
geom_point(aes(x = Longitude, y = Latitude),
data = queryResult, alpha = .5, color = "darkred", size = 2) +
geom_path(aes(x = Longitude, y = Latitude), data = queryResult[c(boundary, boundary[1]),])
I suppose queryResult is x and y datasets. As far as I see, your boundary isn't convex hull, so I used alphahull package.
## example `queryResult`
set.seed(1)
df <- data.frame(Longitude = runif(200, -3.05, -2.97), Latitude = rnorm(200, 53.82, 0.02))
library(alphahull)
ahull.obj <- ahull(df, alpha = 0.03)
plot(ahull.obj) # to check
# ahull_track() returns the output as a list of geom_path objs
ahull.gg <- ahull_track(df, alpha=0.03, nps = 1000)
## change graphic param
for(i in 1:length(ahull.gg)) ahull.gg[[i]]$aes_params$colour <- "green3"
ggmap(cityMapRaster) +
geom_point(aes(x = Longitude, y = Latitude), data = df, alpha = .5, color = "darkred") +
ahull.gg
## if you like not curve but linear
ashape.obj <- ashape(df, alpha = 0.015)
plot(ashape.obj) # to check
ashape.df <- as.data.frame(ashape.obj$edge[,c("x1", "x2", "y1", "y2")])
ggmap(cityMapRaster) +
geom_point(aes(x = Longitude, y = Latitude), data = df, alpha = .5, color = "darkred") +
geom_segment(aes(x = x1, y = y1, xend = x2, yend = y2), data = ashape.df, colour="green3", alpha=0.8)

Plot circle with a certain radius around point on a map in ggplot2

I have a map with the 8 points plotted on it:
library(ggplot2)
library(ggmap)
data = data.frame(
ID = as.numeric(c(1:8)),
longitude = as.numeric(c(-63.27462, -63.26499, -63.25658, -63.2519, -63.2311, -63.2175, -63.23623, -63.25958)),
latitude = as.numeric(c(17.6328, 17.64614, 17.64755, 17.64632, 17.64888, 17.63113, 17.61252, 17.62463))
)
island = get_map(location = c(lon = -63.247593, lat = 17.631598), zoom = 13, maptype = "satellite")
islandMap = ggmap(island, extent = "panel", legend = "bottomright")
RL = geom_point(aes(x = longitude, y = latitude), data = data, color = "#ff0000")
islandMap + RL + scale_x_continuous(limits = c(-63.280, -63.21), expand = c(0, 0)) + scale_y_continuous(limits = c(17.605, 17.66), expand = c(0, 0))
Now I want to plot a circle around each of the 8 plotted locations. The circle has to have a radius of 450 meters.
This is what I mean, but then using ggplot: https://gis.stackexchange.com/questions/119736/ggmap-create-circle-symbol-where-radius-represents-distance-miles-or-km
How can I achieve this?
If you only work on a small area of the earth, here is a approximation. Each degree of the latitude represents 40075 / 360 kilometers. Each degrees of longitude represents (40075 / 360) * cos(latitude) kilomemters. With this, we can calculate approximately a data frame including all points on circles, knowing the circle centers and radius.
library(ggplot2)
library(ggmap)
data = data.frame(
ID = as.numeric(c(1:8)),
longitude = as.numeric(c(-63.27462, -63.26499, -63.25658, -63.2519, -63.2311, -63.2175, -63.23623, -63.25958)),
latitude = as.numeric(c(17.6328, 17.64614, 17.64755, 17.64632, 17.64888, 17.63113, 17.61252, 17.62463))
)
#################################################################################
# create circles data frame from the centers data frame
make_circles <- function(centers, radius, nPoints = 100){
# centers: the data frame of centers with ID
# radius: radius measured in kilometer
#
meanLat <- mean(centers$latitude)
# length per longitude changes with lattitude, so need correction
radiusLon <- radius /111 / cos(meanLat/57.3)
radiusLat <- radius / 111
circleDF <- data.frame(ID = rep(centers$ID, each = nPoints))
angle <- seq(0,2*pi,length.out = nPoints)
circleDF$lon <- unlist(lapply(centers$longitude, function(x) x + radiusLon * cos(angle)))
circleDF$lat <- unlist(lapply(centers$latitude, function(x) x + radiusLat * sin(angle)))
return(circleDF)
}
# here is the data frame for all circles
myCircles <- make_circles(data, 0.45)
##################################################################################
island = get_map(location = c(lon = -63.247593, lat = 17.631598), zoom = 13, maptype = "satellite")
islandMap = ggmap(island, extent = "panel", legend = "bottomright")
RL = geom_point(aes(x = longitude, y = latitude), data = data, color = "#ff0000")
islandMap + RL +
scale_x_continuous(limits = c(-63.280, -63.21), expand = c(0, 0)) +
scale_y_continuous(limits = c(17.605, 17.66), expand = c(0, 0)) +
########### add circles
geom_polygon(data = myCircles, aes(lon, lat, group = ID), color = "red", alpha = 0)
Well, as the referred posting already suggests - switch to a projection that is based in meters, and then back:
library(rgeos)
library(sp)
d <- SpatialPointsDataFrame(coords = data[, -1],
data = data,
proj4string = CRS("+init=epsg:4326"))
d_mrc <- spTransform(d, CRS("+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=#null +no_defs"))
Now, the width can be specified in meters:
d_mrc_bff_mrc <- gBuffer(d_mrc, byid = TRUE, width = 450)
Transform it back and add it to the plot using geom_path:
d_mrc_bff <- spTransform(d_mrc_bff_mrc, CRS("+init=epsg:4326"))
d_mrc_bff_fort <- fortify(d_mrc_bff)
islandMap +
RL +
geom_path(data=d_mrc_bff_fort, aes(long, lat, group=group), color="red") +
scale_x_continuous(limits = c(-63.280, -63.21), expand = c(0, 0)) +
scale_y_continuous(limits = c(17.605, 17.66), expand = c(0, 0))
Calculating distance in km given latitude and longitude isn't super straightforward; 1 degree lat/long is a greater distance at the equator than at the poles, for example. If you want an easy workaround that you can eyeball for accuracy, you might try:
islandMap + RL +
scale_x_continuous(limits = c(-63.280, -63.21), expand = c(0, 0)) +
scale_y_continuous(limits = c(17.605, 17.66), expand = c(0, 0)) +
geom_point(aes(x = longitude, y = latitude), data = data, size = 20, shape = 1, color = "#ff0000")
You'll need to adjust the size paramter in the 2nd geom_point to get closer to what you want. I hope that helps!
An accurate solution is using the geosphere::destPoint() function. This works without switching projections.
Define function to determine 360 points with a certain radius around one point:
library(dplyr)
library(geosphere)
fn_circle <- function(id1, lon1, lat1, radius){
data.frame(ID = id1, degree = 1:360) %>%
rowwise() %>%
mutate(lon = destPoint(c(lon1, lat1), degree, radius)[1]) %>%
mutate(lat = destPoint(c(lon1, lat1), degree, radius)[2])
}
Apply function to each row of data and convert to data.frame:
circle <- apply(data, 1, function(x) fn_circle(x[1], x[2], x[3], 450))
circle <- do.call(rbind, circle)
Then the map can be easily obtained by:
islandMap +
RL +
scale_x_continuous(limits = c(-63.280, -63.21), expand = c(0, 0)) +
scale_y_continuous(limits = c(17.605, 17.66), expand = c(0, 0)) +
geom_polygon(data = circle, aes(lon, lat, group = ID), color = "red", alpha = 0)
A solution using st_buffer() from the sf package.
library(ggmap)
library(ggplot2)
library(sf)
data <- data.frame(
ID = 1:8,
longitude = c(-63.27462, -63.26499, -63.25658, -63.2519,
-63.2311, -63.2175, -63.23623, -63.25958),
latitude = c(17.6328, 17.64614, 17.64755, 17.64632,
17.64888, 17.63113, 17.61252, 17.62463)
)
Convert data.frame to sf object:
points_sf <- sf::st_as_sf(data, coords = c("longitude", "latitude"), crs = 4326)
For this example we use UTM zone 20, which contains the coordinates of the island:
data_sf_utm <- sf::st_transform(points_sf, "+proj=utm +zone=20")
Now we can buffer the point by 450 meters:
circle <- sf::st_buffer(data_sf_utm, dist = 450)
ggmap seems to have some issues with geom_sf. Setting inherit.aes to FALSE returns the desired map.
island <- ggmap::get_map(location = c(lon = -63.247593, lat = 17.631598), zoom = 14, maptype = "satellite")
ggmap(island, extent = "panel", legend = "bottomright") +
geom_sf(data = points_sf, color = "red", inherit.aes = FALSE) +
geom_sf(data = circle, color = "red", alpha = 0, inherit.aes = FALSE)
Created on 2020-10-11 by the reprex package (v0.3.0)

Resources