R: Polygon combining Algorithm - r

I have a file with different polygons, defined as a data.frame with an id, lng and lat. With the following code I can plot these polygons on a map.
library(ggmap)
library(ggplot2)
map <- get_googlemap(center = c(lon = 9.26, lat = 47.3), zoom=10)
xy <- data.frame(id = c(rep(1,4), rep(2,4)), lng = c(9,9.5,9.5,9,9.25,9.7,9.7,9.24), lat= c(47.1,47.1,47.4,47.4,47.2,47.2,47.5,47.5))
p <- ggmap(map) + geom_polygon(data=xy, aes(x=xy$lng, y=xy$lat,group=xy$id),fill='red',alpha= 0.2)
print(p)
Is there a function to merge polygons before I plot them?
I want to plot one polygon which covers everything that's under at least one of the polygons. Therefore I need to create new points at intersections and the convex hull won't cut.

If you mean:
I want to plot one polygon which covers everything that's under AT
LEAST one of the polygons.
then chull (convex hull) might be what you are after.
As an example (not ggplot though):
# create data: (x,y)-coords
x<-c(1:9,9:1)
y<-runif(length(x))*10
# plot `em
plot(x,y,type="l")
# these are the indexes of points that are the convex hull
print(chull(x,y))
these.idx<-chull(x,y)
# plot the convex hull using the indexes
lines(x[these.idx],y[these.idx],type="l",col="red",lwd=2)
For your ggplot scenario, if your code is modified like below it plots a single polygon (although it's hard to know whether that's what you are after):
library(ggmap)
library(ggplot2)
map <- get_googlemap(center = c(lon = 9.26, lat = 47.3), zoom=10)
xy <- data.frame(id = c(rep(1,4), rep(2,4))
, lng = c(9,9.5,9.5,9,9.25,9.7,9.7,9.24)
, lat= c(47.1,47.1,47.4,47.4,47.2,47.2,47.5,47.5))
ch.idx<-chull(xy$lng,xy$lat)
xy<-xy[ch.idx,]
# removing:: ,group=xy$id
p <- ggmap(map)
p <- p + geom_polygon(data=xy, aes(x=xy$lng, y=xy$lat),fill='red',alpha= 0.2)
print(p)
(This is more a comment not an answer but I don't have the 'rep' to do that sorry)

Related

Rayshader path has offset when plotting a ggplot2 object

I am trying to plot a world map and a path between Berlin and Cancun.
So far I managed to plot a world map using library rnaturalearth and convert it to a ggplot2 object which is plotted using rayshader plot_gg function.
After that I tried to add a rayshader path overlay from Berlin (52.5200° N, 13.4050° E) to Cancun (21.1619° N, -86.8515° E). Here is the result:
As you can see the path is up too high and also a bit too long. My best guess right now is that it has to do something with the offset of the rayshader plot and the ggplot object (The white border around the world map).
Here is my code:
#load libraries
library(sf)
library(rayshader)
library(rayrender)
library(rnaturalearth)
library(rnaturalearthdata)
library(tidyverse)
#get ggplot object of world
world <- ne_countries(scale = "medium", returnclass = "sf")
ggData = ggplot(data = world) + geom_sf()
#latitudes, longitudes of Berlin and Cancun as list for render_path()
lats = list()
lats[1] = 52.5
lats[2] = 21.1
longs = list()
longs[1] = 13.4
longs[2] = -86.8
#Altitude of the path
alts = list()
alts[1] = 100
alts[2] = 100
#plot with rayshader, width and height ratio of bbox of ggData
plot_gg(ggData, scale=10, width=3.6, height=1.7359854)
render_path(lat = unlist(lats), long = unlist(longs), zscale=50, color="red", antialias=TRUE, linewidth=3, extent=st_bbox(world), altitude=unlist(alts))
I tried it with different width and height ratios to match the ggplot object as I thought it might have to do something with wrong aspect ratios.
One option is to render the travel path not via rayshader but include it already in your ggplot object like so (required libraries as in OP):
create feature world:
world = ne_countries(scale = "medium", returnclass = "sf")
create feature my_path (the travel path) from linestring from point coordinates:
my_path <-
c(13.4, 52.5, ## longs
-86.6, 21.1 ## lats
) |>
matrix(ncol = 2, byrow = TRUE) |>
st_linestring() |>
st_sfc() |>
st_set_crs(4326) ## set CRS to geographic (degrees)
add features world and my_path as layers to ggplot object:
ggData <-
ggplot() +
geom_sf(data = world) +
geom_sf(data = my_path, col = 'red') +
coord_sf(
## see ?coord_sf to set map limits etc.
)
plot
plot_gg(ggData)
EDITED: The original plots were for a slightly modified ggData. This post corrects things.
I don't know what the cause is, but the extent argument in your render_path() call is wrong. You can see this if you try plotting straight lines at known latitudes or longitudes. The grid lines are at latitudes -45, 0, 45 and longitudes -120, -60, 0, 60, 120. If you plot paths along those lines, the latitude paths are okay, but the longitude paths are wrong. For example, using this path:
render_path(lat = c(-45, 45), long = c(-60, -60),
zscale=50, color="red", antialias=TRUE, linewidth=3,
extent=st_bbox(world), altitude=unlist(alts))
gives this on the map:
with the path drawn too far west, and too long. If I change the extent argument using
bbox <- st_bbox(world)
bbox[c(1,3)] <- 1.2*bbox[c(1,3)]
bbox[c(2,4)] <- 1.1*bbox[c(2,4)]
it looks better. I found those numbers by trial and error; you (or someone else) will have to do some research if you want to explain them. It's still not perfect, so your path still misses Berlin and Cancun, but not by as much.

eqscplot equivalent in ggplot2 for maps (geom_sf & coord_sf)

I'm looking for a ggplot2 equivalent for eqscplot (package MASS) that can fill the entire plot window while maintaining the scale within a map in a ggplot.
There is coord_fixed where cartesian coordinates can be set with fixed ratios (argument aspect =), but it doesn't work if I'm using geom_sf(); geom_sf() requires coord_sf for a plot to be drawn and can't be a ggplot layer if coord_sf is a layer.
There is this answer (ggplot2: Flip axes and maintain aspect ratio of data) but it's not automatic, which is a problem when there are a lot of maps to be made and where automated map making is the ideal workflow. It also doesn't zoom into the points like eqscplot does.
eqscplot is able to maintain an aspect ratio when the range of x and y values are widely different, which is helpful for plotting maps.
library(rnaturalearth)
library(rnaturalearthdata)
library(MASS)
library(ggplot2)
world <- ne_countries(scale = 'medium', returnclass = 'sf',
country = c('India', 'Sri Lanka', 'Pakistan', 'Myanmar',
'Malaysia', 'Indonesia', 'Thailand',
'Nepal', 'Bhutan', 'Laos', 'China', 'Iran',
'Maldives'))
RNGkind('Mers')
set.seed(42)
lonlat <- data.frame(lon = rnorm(10, 80, 5),
lat = rnorm(10, 12, 5))
apply(lonlat, 2, range)
lon lat
[1,] 77.17651 9.343545
[2,] 90.09212 14.286645
eqscplot(lonlat$lon, lonlat$lat, type = 'n')
plot(world, add = T, col = NA)
points(lonlat$lon, lonlat$lat, col = 'red', pch = 16)
#Compare the lon/lat ranges on the map with the ranges of the data's longitude and latitude
I was was looking to see if there was a way to do it ggplot2, but if I don't specify the limits of the longitude and latitude, it takes the extent of the countries I specified in the world object.
ggplot(data = world) +
geom_sf() +
geom_point(data = lonlat, aes(x = lon, y = lat))
I've got a few hundred maps to make so automating the process with apply family of functions or for loops is preferred. But I need something that can automatically zoom into the points that I'm plotting while maintaining the the aspect ratio. I'm looking to do it in ggplot2 because my collaborators aren't as experienced with R and were taught how to use the tidyverse, so anything I can do to make the code easier for them to understand can go a long way.
My question is: Is there an eqscplot equivalent in ggplot2? Something where the aspect ratio can be set automatically, zooms into where the points are instead of the extent of the world polygon, and fills the entire plot window while maintaining the scales?
Using geom_sf it looks like the plots will adjust to show the extent of the largest object. You can use st_crop to adjust the data that is too large. It might help to make all of your objects sf collections, rather than using both geom_sf and geom_point.
Below is a quick example that you can adjust to change the bounding box of the maps. A good post by #Jindra-Lacko on adjusting bounding boxes can be found here: https://www.jla-data.net/eng/adjusting-bounding-box-of-a-tmap-map/
Though most of it is using tmap, it should apply to ggplot2 as well.
library(rnaturalearth)
library(rnaturalearthdata)
library(MASS)
library(ggplot2)
library(tidyverse)
library(sf)
#> Linking to GEOS 3.6.2, GDAL 2.2.3, PROJ 4.9.3
world <- ne_countries(scale = 'medium', returnclass = 'sf',
country = c('India', 'Sri Lanka', 'Pakistan', 'Myanmar',
'Malaysia', 'Indonesia', 'Thailand',
'Nepal', 'Bhutan', 'Laos', 'China', 'Iran',
'Maldives'))
RNGkind('Mers')
set.seed(42)
lonlat <- data.frame(lon = rnorm(10, 80, 5),
lat = rnorm(10, 12, 5))
#apply(lonlat, 2, range)
# lon lat
# [1,] 77.17651 9.343545
# [2,] 90.09212 14.286645
# eqscplot(lonlat$lon, lonlat$lat, type = 'n')
# plot(world, add = T, col = NA)
#points(lonlat$lon, lonlat$lat, col = 'red', pch = 16)
#Compare the lon/lat ranges on the map with the ranges of the data's longitude and latitude
#make lonlat an sf object
lonlat_sf <- st_as_sf(lonlat,
coords = c('lon', 'lat')) %>%
st_set_crs(st_crs(world))
ggplot() +
geom_sf(data = lonlat_sf, color = 'red') +
geom_sf(data = world %>% st_crop(lonlat_sf, world ), fill = NA) +
coord_sf()
#> although coordinates are longitude/latitude, st_intersection assumes that they are planar
#> Warning: attribute variables are assumed to be spatially constant throughout all
#> geometries
Created on 2020-04-14 by the reprex package (v0.3.0)
When I have this issue with different aspect ratios. I create a buffer for the output dimension i want and save using the same scales similar to below..
library(buffers)
#Find bbox and bbox_centroid
bbox <- st_as_sfc(st_bbox(df))
bbox_centroid<-st_centroid(bbox)
#Calculate Max distance from centroid
max_dist=as.numeric(max(st_distance(bbox_centroid,bbox)))
#Create buffer (In this case I wanted a rectangular image output)
#Notice I'm using a 1.5 ratio for x_length to y_length, 3/2=1.5)
rectangle_buf=buffer_rectangle(bbox_centroid,x_length=max_dist*2,y_length=max_dist*3)
#Find bbox minumum and maximum X and Y values
bbox_rect<-st_as_sf(rectangle_buf)
bbox<-st_bbox(bbox_rect)
#Create ggplot2 image and save with correct dimensions
ggplot(df)+geom_sf()+
#Important part of code
coord_sf(xlim = c(bbox[1], bbox[3]), ylim = c(bbox[2], bbox[4]), expand = T)
##Save ggplot with correct dimensions (In my case this is would be a 1 to 1.5 ratio)
ggsave(plot=last_plot(),width=3,height= 3* 1.5)

How to draw Polygon for lots of lat/long coordinates and calculate surface are?

I have the following data: https://ufile.io/p9s0le6g
...which consists of 485 lat/long observations. I ultimately want to calculate the surface area (in m2 or km2) of this figure. I wanted to create a polygon with the data points (the outer points) and then calculate the surface are. However, I cannot come up with a nice polygon.
My data points as coordinates look like:
I want a polygon which would look like that (or similarly):
Here is what I tried:
library(sp)
df <- read.csv('data.csv')
p = Polygon(df)
ps = Polygons(list(p),1)
sps = SpatialPolygons(list(ps))
plot(sps)
This results in (clearly not a nice polygon):
I also tried to implement the answer of Create polygon from set of points distributed but this results in just a rectangular for me:
Anyone an idea how I can optimize my polygon to get a figure which looks like the outer shape of my data points and how to then calculate the surface are of that optimized polygon?
Using concaveman and sf we can create a concave hull around your set of points:
library(sf)
library(concaveman)
pts <- st_as_sf(df, coords=c('LONG','LAT'), crs=4326 )
conc <- concaveman(pts)
conc %>% st_area()
# 4010443 [m^2]
library(ggplot2)
ggplot() +
geom_sf(data=pts, col = 'red', pch=3) +
geom_sf(data = conc, fill = NA)
Consider using functions from the excellent {sf} package instead of the older {sp}.
Such as this example, built on sf::st_convex_hull
library(sf)
library(tidyverse)
points <- read_csv("data.csv") %>%
st_as_sf(coords = c("LONG","LAT"), crs=4326)
polygon <- st_union(points) %>% # unite the points to 1 object
st_convex_hull() # calculate the convex hull
# verify the results by drawing a map
ggplot() +
geom_sf(data = points, col = "red", size = 2, pch = 4) +
geom_sf(data = polygon, col = "grey45", fill = NA)
# as a bonus: area of the polygon
area <- st_area(polygon)
area

Issue plotting (simple) polygons with leaflet in r

I'd like to use leaflet to help me plot heatmap polygons over the UK, but leaflet()%>%addPolygons(...) is giving me weird output, even for a very simple case.
I'm using the following code to plot a simple triangle in leaflet and then ggplot2. ggplot2 has no issues, but leaflet will sometimes just give me a straight line instead of a triangle.
The leaflet code:
l <- leaflet()
shapes <- unique(df$shape)
for (shape in shapes) {
d <- df[df$shape == shape, , drop = FALSE]
l <- l %>% addPolygons(lat = d$lat, lng = d$lng,noClip=TRUE)
}
l
The geom_polygon code:
qplot(lng, lat, data=df, geom="polygon", group = shape)
If I use this input, then I get sensible output from both pacakges:
df <- data.frame(lat = c(1, 2, 3), lng = c(2, 3, 1), shape = c("triangle", "triangle", "triangle"))
However, using even a simple modification leads to a simple horizontal line in leaflet (but a correct triangle in ggplot2):
df <- data.frame(lat = c(100, 200, 300), lng = c(200, 300, 100), shape = c("triangle", "triangle", "triangle"))
It sounds to me like I'm missing a parameter or something, but I can't for the life of my figure out what's going on. Any help appreciated.
Thanks,
Tom
Apparently leaflet only deals with long/lat, even if there is no base maps.
Starting with a shape file (with Eastings & Northings), the following will read in a shapefile, convert to longlat and then successfully plot the polygons using leaflet:
shapefile <- readOGR("filepath_here", "shapefile_here")
shapeData <- spTransform(shapefile, CRS("+proj=longlat +datum=WGS84 +no_defs"))
leaflet() %>%
addPolygons(data=subset(shapeData, POSTAREA %in% c('SW')),weight=2)

Plot coordinates on map

I am trying to plot my coordinates using R. I have tried already to follow different post (R: Plot grouped coordinates on world map ; Plotting coordinates of multiple points at google map in R) but I am not having much success with my data.
I am trying to achieve a flat map of the world with my gps coordinate as colored dots (each area a specific color):
area lat long
Agullhas -38,31 40,96
Polar -57,59 76,51
Tasmanian -39,47 108,93
library(RgoogleMaps)
lat <- c(-38.31, -35.50) #define our map's ylim
lon <- c(40.96,37.50) #define our map's xlim
center = c(mean(lat), mean(lon)) #tell what point to center on
zoom <- 2 #zoom: 1 = furthest out (entire globe), larger numbers = closer in
terrmap <- GetMap(center=center, zoom=zoom, maptype= "satallite", destfile = "satallite.png")
problem that now I don't know how to add my points and I will like one color for each region.
Could anyone help me going forward with it?
the other option I have tried is :
library(maps)
library(mapdata)
library(maptools)
map(database= "world", ylim=c(-38.31, -35.5), xlim=c(40.96, 37.5), col="grey80", fill=TRUE, projection="gilbert", orientation= c(90,0,225))
lon <- c(-38.31, -35.5) #fake longitude vector
lat <- c(40.96, 37.5) #fake latitude vector
coord <- mapproject(lon, lat, proj="gilbert", orientation=c(90, 0, 225)) #convert points to projected lat/long
points(coord, pch=20, cex=1.2, col="red") #plot converted points
but the coordinates ends in a wrong position and I am not sure why
Hope someone can help
As an alternative to RgoogleMaps, you can also use the combination ggplot2 with ggmap.
With this code:
# loading the required packages
library(ggplot2)
library(ggmap)
# creating a sample data.frame with your lat/lon points
lon <- c(-38.31,-35.5)
lat <- c(40.96, 37.5)
df <- as.data.frame(cbind(lon,lat))
# getting the map
mapgilbert <- get_map(location = c(lon = mean(df$lon), lat = mean(df$lat)), zoom = 4,
maptype = "satellite", scale = 2)
# plotting the map with some points on it
ggmap(mapgilbert) +
geom_point(data = df, aes(x = lon, y = lat, fill = "red", alpha = 0.8), size = 5, shape = 21) +
guides(fill=FALSE, alpha=FALSE, size=FALSE)
you get this result:
Another option is using the leaflet package (as suggested here). Unlike Google Maps it does not require any API key.
install.packages(c("leaflet", "sp"))
library(sp)
library(leaflet)
df <- data.frame(longitude = runif(10, -97.365268, -97.356546),
latitude = runif(10, 32.706071, 32.712210))
coordinates(df) <- ~longitude+latitude
leaflet(df) %>% addMarkers() %>% addTiles()
An other alternative, is the plotGoogleMaps package which allows to plot in a navigator, allowing to zoom in and out etc. You can then make a screenshot of your picture to save it (though remember google maps are legally supposed to be used for the internet).
library("plotGoogleMaps")
lat <- c(-38.31, -35.50) #define our map's ylim
lon <- c(40.96,37.50) #define our map's xlim
# make your coordinates a data frame
coords <- as.data.frame(cbind(lon=lon,lat=lat))
# make it a spatial object by defining its coordinates in a reference system
coordinates(coords) <- ~lat+lon
# you also need a reference system, the following should be a fine default
proj4string(coords) <- CRS("+init=epsg:4326")
# Note: it is a short for:
CRS("+init=epsg:4326")
> CRS arguments:
> +init=epsg:4326 +proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0
# then just plot
a <- plotGoogleMaps(coords)
# here `a <-` avoids that you get flooded by the html version of what you plot
And you get :
Here is a solution using only Rgooglemaps, as requested by the user.
# get map (from askers OP, except changed map type = "Satallite" to type = "Satellite")
library(RgoogleMaps)
lat <- c(-38.31, -35.50) #define our map's ylim
lon <- c(40.96,37.50) #define our map's xlim
center = c(mean(lat), mean(lon)) #tell what point to center on
zoom <- 2 #zoom: 1 = furthest out (entire globe), larger numbers = closer in
terrmap <- GetMap(center=center, zoom=zoom, type= "satellite", destfile = "satellite.png")
# plot points and save image
lat <- c(-38.31, -57.59, -39.47)
lon <- c(40.96, 76.51, 108.93)
png('map.png')
PlotOnStaticMap(terrmap, lat = lat, lon = lon, pch = 20, col = c('red', 'blue', 'green'))
dev.off()

Resources