Determine transects perpendicular to a (coast)line in R - r

I'd like to automatically derive transects, perpendicular to the coastline. I need to be able to control their length and spacing and their oriƫntation needs to be on the "correct" side of the line. I came up with a way to do that, but especially selecting the "correct" (it needs to point to the ocean) can be done better. General approach:
For each line segment in a SpatialLineDataFrame define transect
locations
define transect: in both directions perpendicular to coastline: create points that determine the transect
Create a polygon based on the coastline, add extra points to grow the polygon in a direction that is known and use that to clip the points that are inside (considered as land, and therefore not of interest)
Create transect based on remaining point
Especially part 3 is of interest. I'd like a more robust method to determine the correct orientation of the transect. This is what i'm using now:
library(rgdal)
library(raster)
library(sf)
library(ggplot2)
library(rgeos) # create lines and spatial objects
# create testing lines
l1 <- cbind(c(1, 2, 3), c(3, 2, 2))
l2 <- cbind(c(1, 2, 3), c(1, 1.5, 1))
Sl1 <- Line(l1)
Sl2 <- Line(l2)
S1 <- Lines(list(Sl1), ID = "a")
S2 <- Lines(list(Sl2), ID = "b")
line <- SpatialLines(list(S1, S2))
plot(line)
# for testing:
sep <- 0.1
start <- 0
AllTransects <- vector('list', 100000) # DB that should contain all transects
for (i in 1: length(line)){
# i <- 2
###### Define transect locations
# Define geometry subset
subset_geometry <- data.frame(geom(line[i,]))[, c('x', 'y')]
# plot(SpatialPoints(data.frame(x = subset_geometry[,'x'], y = subset_geometry[,'y'])), axes = T, add = T)
dx <- c(0, diff(subset_geometry[,'x'])) # Calculate difference at each cell comapred to next cell
dy <- c(0, diff(subset_geometry[,'y']))
dseg <- sqrt(dx^2+dy^2) # get rid of negatives and transfer to uniform distance per segment (pythagoras)
dtotal <- cumsum(dseg) # cumulative sum total distance of segments
linelength = sum(dseg) # total linelength
pos = seq(start,linelength, by=sep) # Array with postions numbers in meters
whichseg = unlist(lapply(pos, function(x){sum(dtotal<=x)})) # Segments corresponding to distance
pos=data.frame(pos=pos, # keep only
whichseg=whichseg, # Position in meters on line
x0=subset_geometry[whichseg,1], # x-coordinate on line
y0=subset_geometry[whichseg,2], # y-coordinate on line
dseg = dseg[whichseg+1], # segment length selected (sum of all dseg in that segment)
dtotal = dtotal[whichseg], # Accumulated length
x1=subset_geometry[whichseg+1,1], # Get X coordinate on line for next point
y1=subset_geometry[whichseg+1,2] # Get Y coordinate on line for next point
)
pos$further = pos$pos - pos$dtotal # which is the next position (in meters)
pos$f = pos$further/pos$dseg # fraction next segment of its distance
pos$x = pos$x0 + pos$f * (pos$x1-pos$x0) # X Position of point on line which is x meters away from x0
pos$y = pos$y0 + pos$f * (pos$y1-pos$y0) # Y Position of point on line which is x meters away from y0
pos$theta = atan2(pos$y0-pos$y1,pos$x0-pos$x1) # Angle between points on the line in radians
pos$object = i
###### Define transects
tlen <- 0.5
pos$thetaT = pos$theta+pi/2 # Get the angle
dx_poi <- tlen*cos(pos$thetaT) # coordinates of point of interest as defined by position length (sep)
dy_poi <- tlen*sin(pos$thetaT)
# tabel met alleen de POI informatie
# transect is defined by x0,y0 and x1,y1 with x,y the coordinate on the line
output <- data.frame(pos = pos$pos,
x0 = pos$x + dx_poi, # X coordinate away from line
y0 = pos$y + dy_poi, # Y coordinate away from line
x1 = pos$x - dx_poi, # X coordinate away from line
y1 = pos$y - dy_poi, # X coordinate away from line
theta = pos$thetaT, # angle
x = pos$x, # Line coordinate X
y = pos$y, # Line coordinate Y
object = pos$object,
nextx = pos$x1,
nexty = pos$y1)
# create polygon from object to select correct segment of the transect (coastal side only)
points_for_polygon <- rbind(output[,c('x', 'y','nextx', 'nexty')])# select points
pol_for_intersect <- SpatialPolygons( list( Polygons(list(Polygon(points_for_polygon[,1:2])),1)))
# plot(pol_for_intersect, axes = T, add = T)
# Find a way to increase the polygon - should depend on the shape&direction of the polygon
# for the purpose of cropping the transects
firstForPlot <- data.frame(x = points_for_polygon$x[1], y = points_for_polygon$y[1])
lastForPlot <- data.frame(x = points_for_polygon$x[length(points_for_polygon$x)],
y = points_for_polygon$y[length(points_for_polygon$y)])
plot_first <- SpatialPoints(firstForPlot)
plot_last <- SpatialPoints(lastForPlot)
# plot(plot_first, add = T, col = 'red')
# plot(plot_last, add = T, col = 'blue')
## Corners of shape dependent bounding box
## absolute values should be depended on the shape beginning and end point relative to each other??
LX <- min(subset_geometry$x)
UX <- max(subset_geometry$x)
LY <- min(subset_geometry$y)
UY <- max(subset_geometry$y)
# polygon(x = c(LX, UX, UX, LX), y = c(LY, LY, UY, UY), lty = 2)
# polygon(x = c(LX, UX, LX), y = c(LY, LY, UY), lty = 2)
# if corners are changed to much the plot$near becomes a problem: the new points are to far away
# Different points are selected
LL_corner <- data.frame(x = LX-0.5, y = LY - 1)
LR_corner <- data.frame(x = UX + 0.5 , y = LY - 1)
UR_corner <- data.frame(x = LX, y = UY)
corners <- rbind(LL_corner, LR_corner)
bbox_add <- SpatialPoints(rbind(LL_corner, LR_corner))
# plot(bbox_add ,col = 'green', axes = T, add = T)
# Select nearest point for drawing order to avoid weird shapes
firstForPlot$near <-apply(gDistance(bbox_add,plot_last, byid = T), 1, which.min)
lastForPlot$near <- apply(gDistance(bbox_add,plot_first, byid = T), 1, which.min)
# increase polygon with corresponding points
points_for_polygon_incr <- rbind(points_for_polygon[1:2], corners[firstForPlot$near,], corners[lastForPlot$near,])
pol_for_intersect_incr <- SpatialPolygons( list( Polygons(list(Polygon(points_for_polygon_incr)),1)))
plot(pol_for_intersect_incr, col = 'blue', axes = T)
# Coordinates of points first side
coordsx1y1 <- data.frame(x = output$x1, y = output$y1)
plotx1y1 <- SpatialPoints(coordsx1y1)
plot(plotx1y1, add = T)
coordsx0y0 <- data.frame(x = output$x0, y = output$y0)
plotx0y0 <- SpatialPoints(coordsx0y0)
plot(plotx0y0, add = T, col = 'red')
# Intersect
output[, "x1y1"] <- over(plotx1y1, pol_for_intersect_incr)
output[, "x0y0"] <- over(plotx0y0, pol_for_intersect_incr)
x1y1NA <- sum(is.na(output$x1y1)) # Count Na
x0y0NA <- sum(is.na(output$x1y1)) # Count NA
# inefficient way of selecting the correct end point
# e.g. either left or right, depending on intersect
indexx0y0 <- with(output, !is.na(output$x0y0))
output[indexx0y0, 'endx'] <- output[indexx0y0, 'x1']
output[indexx0y0, 'endy'] <- output[indexx0y0, 'y1']
index <- with(output, is.na(output$x0y0))
output[index, 'endx'] <- output[index, 'x0']
output[index, 'endy'] <- output[index, 'y0']
AllTransects = rbind(AllTransects, output)
}
# Create the transects
lines <- vector('list', nrow(AllTransects))
for(n in 1: nrow(AllTransects)){
# n = 30
begin_coords <- data.frame(lon = AllTransects$x, lat = AllTransects$y) # Coordinates on the original line
end_coords <- data.frame(lon = AllTransects$endx, lat = AllTransects$endy) # coordinates as determined by the over: remove implement in row below by selecting correct column from output
col_names <- list('lon', 'lat')
row_names <- list('begin', 'end')
# dimnames < list(row_names, col_names)
x <- as.matrix(rbind(begin_coords[n,], end_coords[n,]))
dimnames(x) <- list(row_names, col_names)
lines[[n]] <- Lines(list(Line(x)), ID = as.character(n))
}
lines_sf <- SpatialLines(lines)
# plot(lines_sf)
df <- SpatialLinesDataFrame(lines_sf, data.frame(AllTransects))
plot(df, axes = T)
As long as i'm able to correctly define the bounding box and grow the polygon correctly this works. But I'd like to try this on multiple coastlines and parts of coastlines, each with its own orientation. In the example below the growing of the polygon is made for the bottom coastline segment, as a result the top one has transects in the wrong direction.
Anybody has an idea in what directio to look? I was considering to perhaps use external data but when possible i'd like to avoid that.

I used your code for my question (measure line inside a polygon) but maybe this works for you:
Took a spatial polygon or line
Extract the coordinates of the element
Make a combination of coordinates to create straight lines, from with you can derivate perpendicular lines (e.g. ((x1,x3)(y1, y3)) or ((x2,x4)(y2, y4)) )
Iterate along with all the pairs of coordinates
Apply the code you did, especially the result of the 'output' table.
I did this for a polygon, so I could generate perpendicular lines based on the straight line I create taking an arbitrary (1, 3) set of coordinates.
#Define a polygon
pol <- rip[1, 1] # I took the first polygon from my Shapefile
polcoords <- pol#polygons[[1]]#Polygons[[1]]#coords
# define how to create your coords pairing. My case: 1st with 3rd, 2nd with 4th, ...
pairs <- data.frame(a = 1:( nrow(polcoords) - 1),
b = c(2:(nrow(polcoords)-1)+1, 1) )
# Empty list to store the lines
lnDfls <- list()
for (j in 1:nrow(pairs)){ # j = 1
# Select the pairs
pp <- polcoords[c(pairs$a[j], pairs$b[j]), ]
#Extract mean coord, from where the perp. line will start
midpt <- apply(pp, 2, mean)
# points(pp, col = 3, pch = 20 )
# points(midpt[1], midpt[2], col = 4, pch = 20)
x <- midpt[1]
y <- midpt[2]
theta = atan2(y = pp[2, 2] - pp[1, 2], pp[2, 1] - pp[1, 1]) # Angle between points on the line in radians
# pos$theta = atan2(y = pos$y0-pos$y1 , pos$x0-pos$x1) # Angle between points on the line in radians
###### Define transects
tlen <- 1000 # distance in m
thetaT = theta+pi/2 # Get the angle
dx_poi <- tlen*cos(thetaT) # coordinates of point of interest as defined by position length (sep)
dy_poi <- tlen*sin(thetaT)
# tabel met alleen de POI informatie
# transect is defined by x0,y0 and x1,y1 with x,y the coordinate on the line
output2 <- data.frame(#pos = pos,
x0 = x + dx_poi, # X coordinate away from line
y0 = y + dy_poi, # Y coordinate away from line
x1 = x - dx_poi, # X coordinate away from line
y1 = y - dy_poi # X coordinate away from line
#theta = thetaT, # angle
#x = x, # Line coordinate X
#y = y # Line coordinate Y
)
# points(output2$x1, output2$y1, col = 2)
#segments(x, y, output2$x1[1], output2$y1[1], col = 2)
mat <- as.matrix(cbind( c( x, output2$x1[1] ) , c( y, output2$y1[1] ) ))
LL <- Lines(list(Line( mat )), ID = as.character(j))
# plot(SpatialLinesDataFrame(LL, data.frame (a = 1)), add = TRUE, col = 2)
# plot(SpatialLines(list(LL)), add = TRUE, col = 2)
#lnList[[j]] <- LL
lnDfls[[j]] <- SpatialLinesDataFrame( SpatialLines(LinesList = list(LL)) ,
match.ID = FALSE,
data.frame(id = as.character(j ) ) )
# line = st_sfc(st_linestring(mat))
# st_length(line)
# ln <- (SpatialLines(LinesList = list(LL)))
# lndf <- SpatialLinesDataFrame( lndf , data.frame(id = j ))
# sf::st_length(ln)
# # plot(lines_sf)
}
compDf <- do.call(what = sp::rbind.SpatialLines, args = lnDfls)
plot(pol)
plot(compDf, add = TRUE, col = 2)
plot(inDfLn, add = TRUE, col = 3)

Related

Voronoi approach to buffering polygons while preserving topological integrity

As I understand it R lacks a methods to buffer polygons in a spatially exclusive way that preserves the topology of adjacent polygons. So I'm experimenting with an approach that generates voronoi polygons of the original polygon vertices. Results seem quite promising except for apparent errors in the voronoi generation.
Fairly old school R, so it's possible a tidier alternative may work better. This reproducible example uses US/Canada, but note the problem is one of mathematical geometry so marine boundaries are not relevant:
require(rworldmap)
require(rgeos)
require(dismo)
require(purrr)
require(dplyr)
par(mai = rep(0,4))
p = rworldmap::countriesCoarse[,'ADMIN']
p = p[p$ADMIN %in% c('United States of America', 'Canada'),]
p$ADMIN = as.character(p$ADMIN)
p = rgeos::gBuffer(p, byid=T, width = 0) # precaution to ensure no badly-formed polygon nonsense
# Not critical to the problem, but consider we have points we want to assign to enclosing or nearest polygon
set.seed(42)
pts = data.frame(x = runif(1000, min = p#bbox[1,1], max = p#bbox[1,2]),
y = runif(1000, min = p#bbox[2,1], max = p#bbox[2,2]))
coordinates(pts) = pts
pts#proj4string = p#proj4string
# point in polygon classification.
pts$admin = sp::over(pts, p)$ADMIN
pts$admin = replace(pts$admin, is.na(pts$admin), 'unclass')
plot(p)
plot(pts, pch=16, cex=.4, col = c('red','grey','blue')[factor(pts$admin)], add=T)
Let's say we want to bin the grey points to nearest polygon. I think the most elegant approach would be to create a new expanded set of polygons. This avoids lots of n-squared nearest neighbour calculations. Next we try a voronoi tesselation of the original polygon vertices:
vertices1 = map_df(p#polygons, ~ map2_df(.x#Polygons, rep(.x#ID, length(.x#Polygons)),
~ as.data.frame(..1#coords) %>% `names<-`(c('x','y')) %>% mutate(id = ..2)))
print(head(vertices1))
#> x y id
#> 1 -56.13404 50.68701 Canada
#> 2 -56.79588 49.81231 Canada
#> 3 -56.14311 50.15012 Canada
#> 4 -55.47149 49.93582 Canada
#> 5 -55.82240 49.58713 Canada
#> 6 -54.93514 49.31301 Canada
coordinates(vertices1) = vertices1[,1:2]
# voronois
vor1 = dismo::voronoi(vertices1)
# visualise
plot(p)
plot(vertices1, add=T, pch=16, cex=.5, col = c('red','blue')[factor(vertices1$id)])
plot(vor1, add=T, border='#00000010', col = c('#FF000040','#0000FF40')[factor(vor1$id)])
Lots of errors in here. Maybe due to different polygons sharing some vertices. Let's try small negative buffer to help the algorithm:
p_buff2 = rgeos::gBuffer(p, byid=T, width = -.00002) # order of 1 metre
vertices2 = map_df(p_buff2#polygons, ~ map2_df(.x#Polygons, rep(.x#ID, length(.x#Polygons)),
~ as.data.frame(..1#coords) %>% `names<-`(c('x','y')) %>% mutate(id = ..2)))
coordinates(vertices2) = vertices2[,1:2]
vor2 = dismo::voronoi(vertices2)
plot(p_buff2)
plot(vertices2, add=T, pch=16, cex=.4, col = c('red','blue')[factor(vertices2$id)])
plot(vor2, add=T, border='#00000010', col = c('#FF000040','#0000FF40')[factor(vor2$id)])
Some improvements - almost validating the approach I think. But again we still have some errors, e.g. blue chunk of British Colombia and a thin pink strip of easter border area in Alaska. Lastly I plot with a bigger buffer to help show what is happening with individual vertices (click for bigger resolution):
p_buff3 = rgeos::gBuffer(p, byid=T, width = -.5, ) # order of 30kms I think
vertices3 = map_df(p_buff3#polygons, ~ map2_df(.x#Polygons, rep(.x#ID, length(.x#Polygons)),
~ as.data.frame(..1#coords) %>% `names<-`(c('x','y')) %>% mutate(id = ..2)))
coordinates(vertices3) = vertices3[,1:2]
vor3 = dismo::voronoi(vertices3)
plot(p_buff3)
plot(vertices3, add=T, pch=16, cex=.4, col = c('red','blue')[factor(vertices3$id)])
plot(vor3, add=T, border='#00000010', col = c('#FF000040','#0000FF40')[factor(vor3$id)])
Is anyone able to shed light on the problem, or possible suggest an alternative voronoi method that works? I've tried ggvoronoi but struggled to get that working. Any assistance appreciated.
That is an interesting, and important, problem; and I think it is a good idea to use voronoi. The apparent errors arise from the distribution of the vertices. For example, the border between Canada and the USA hardly has vertices in the west. This leads to undesired results, but they are not wrong. A step in the right direction might be to add vertices, using geosphere::makePoly
library(dismo)
library(geosphere)
library(rworldmap)
library(rgeos)
w <- rworldmap::countriesCoarse[,'ADMIN']
w <- w[w$ADMIN %in% c('United States of America', 'Canada'),]
p <- geosphere::makePoly(w, 25000)
p$ADMIN = as.character(p$ADMIN)
p <- buffer(p, width = 0, dissolve=FALSE)
p_buff <- buffer(p, width = -.00002, dissolve=FALSE) # order of 1 metre
g <- geom(p_buff)
g <- unique(g)
vor <- dismo::voronoi(g[,c("x", "y")])
plot(p_buff)
points(g[,c("x", "y")], pch=16, cex=.4, col= c('red','blue')[g[,"object"]])
plot(vor, add=T, border='#00000010', col = c('#FF000040','#0000FF40')[g[,"object"]])
Dissolve the polygons by country and remove holes
v <- aggregate(vor, list(g[,"object"]), FUN=length)
gg <- data.frame(geom(v))
v <- as(gg[gg$hole==0, ], "SpatialPolygons")
lines(v, col="yellow", lwd=4)
Now use this to cut the buffer by country
pp <- buffer(p, width = 10)
buf <- v * (pp - p) # intersect(v, erase(pp, p))
buf <- SpatialPolygonsDataFrame(buf, data=data.frame(p), match.ID = FALSE)
x <- bind(p, buf)
z <- aggregate(x, "ADMIN")
lines(z, lwd=2, col="dark green")
And now for something more focused. The below does essentially the same as the above, but focuses just on the regions that matter (coastal borders) making it computationally less intensive --- although not so much for this example with a rather large buffer.
library(dismo)
library(rworldmap)
library(rgeos)
w <- rworldmap::countriesCoarse[,'ADMIN']
w <- w[w$ADMIN %in% c('United States of America', 'Canada', 'Mexico'),]
p <- geosphere::makePoly(w, 25000)
p$ADMIN = as.character(p$ADMIN)
p <- buffer(p, width = 0, dissolve=FALSE)
#p <- buffer(p, width = -.00002, dissolve=FALSE) # order of 1 metre
bsz <- 10
mbuf <- buffer(p, width = bsz, dissolve=FALSE)
# e <- mbuf[1,] * mbuf[2,]
# -----------
# general solution for e?
poly_combs = expand.grid(p1 = seq_along(mbuf), p2 = seq_along(mbuf))
poly_combs = poly_combs[poly_combs$p1 < poly_combs$p2,]
# pairwise overlaps
e_pw = plyr::compact(lapply(1:nrow(poly_combs), FUN = function(i){
pair = poly_combs[i,]
pairing = suppressWarnings(mbuf[pair$p1,] * mbuf[pair$p2,])
return(pairing)
}))
e = e_pw[[1]]
for(i in 2:length(e_pw)) e = e + e_pw[[i]]
# -----------
f <- e - p
b <- buffer(f, bsz)
# bp is the area that matters
bp <- b * p
g <- data.frame(geom(bp))
# getting rid of duplicated and shared vertices
g <- aggregate(g[,1,drop=FALSE], g[,5:6], min)
v <- dismo::voronoi(g[,c("x", "y")], extent(p)+ 2 * bsz)
v <- aggregate(v, list(g[,"object"]), FUN=length)
v <- v- p
buf1 <- buffer(p, width = bsz, dissolve=TRUE)
v <- v * buf1
v#data <- p#data
plot(v, col=c("red", "blue", "green"))
Slight adaptation from Robert's, for discussion.
library(dismo)
library(rworldmap)
library(rgeos)
w <- rworldmap::countriesCoarse[,'ADMIN']
# w <- w[w$ADMIN %in% c('United States of America', 'Canada'),]
w <- w[w$ADMIN %in% c('Guyana', 'Suriname','French Guiana'),]
p <- geosphere::makePoly(w, 25000)
p$ADMIN = as.character(p$ADMIN)
p <- buffer(p, width = 0, dissolve=FALSE)
#p <- buffer(p, width = -.00002, dissolve=FALSE) # order of 1 metre
bsz <- .5
# outward buffer
mbuf = buffer(p, width = bsz, dissolve=F)
# overlay between two country buffers
# e <- mbuf[1,] * mbuf[2,]
poly_combs = expand.grid(p1 = seq_along(mbuf), p2 = seq_along(mbuf))
poly_combs = poly_combs[poly_combs$p1 < poly_combs$p2,]
# pairwise overlaps
e_pw = plyr::compact(lapply(1:nrow(poly_combs), FUN = function(i){
pair = poly_combs[i,]
pairing = suppressWarnings(mbuf[pair$p1,] * mbuf[pair$p2,])
return(pairing)
}))
e = e_pw[[1]]
for(i in 2:length(e_pw)) e = e + e_pw[[i]]
# contested buffer zones - overlap minus original polys
f <- e - p
f#data = data.frame(id = seq_along(f))
# buffer the contested zones
b <- buffer(f, bsz)
# bp is the area that matters
bp <- b * p
# vertices
bp = buffer(bp, width = -0.00002, dissolve=F)
g0 <- data.frame(data.frame(geom(bp)))
# getting rid of duplicated and shared vertices
# g <- aggregate(g0[,'object', drop=FALSE], g0[,c('x','y')], min)
g = unique(g0)
v0 <- dismo::voronoi(g[,c("x", "y")], extend(extent(p), 2 * bsz))
v0$id = g$object
v <- raster::aggregate(v0, list(g[,"object"]), FUN=length)
v#proj4string = p#proj4string
v = v * f
v#data = data.frame(ADMIN = p$ADMIN[v$Group.1])
# full buffer
fb = raster::bind(mbuf - p - f, v, p)
fb = raster::aggregate(fb, list(fb$ADMIN), FUN = function(x)x[1])[,'ADMIN']
fb#proj4string = p#proj4string
#----------------------------------
par(mai=c(0,0,0,0))
plot(p, border='grey')
plot(mbuf, add=T, border='pink')
plot(e, add=T, col='#00000010', border=NA)
plot(f, add=T, border='purple', lwd=1.5)
plot(b, add=T, border='red')
plot(bp, add=T, col='#ffff0040', border=NA)
# plot(v, add=T, col=c("#ff770020", "#0077ff20"), border=c("#ff7700", "#0077ff"))
plot(fb, add=T, col=c("#ff000020", "#00ff0020", "#0000ff20"), border=NA)

Dissolving hexmap polygon shape files

I am trying to produce an outline for a hexagonal cartogram by dissolving the inner polygons via the unionSpatialPolygons or aggregate functions. I am getting stray hexs that do not dissolve... a dummy example to show the problem:
# grab a dummy example shape file
library(raster)
g <- getData(name = "GADM", country = "GBR", level = 2)
# par(mar = rep(0,4))
# plot(g)
# create a hexagonal cartogram
# library(devtools)
# install_github("sassalley/hexmapr")
library(hexmapr)
h <- calculate_cell_size(shape = g, seed = 1,
shape_details = get_shape_details(g),
learning_rate = 0.03, grid_type = 'hexagonal')
i <- assign_polygons(shape = g, new_polygons = h)
par(mar = rep(0,4))
plot(i)
# dissolve the polygons to get coastline
library(maptools)
j <- unionSpatialPolygons(SpP = i, IDs = rep(1, length(i)))
par(mar = rep(0,4))
plot(j)
# same result with aggregate in the raster package
k <- aggregate(x = i)
par(mar = rep(0,4))
plot(k)
With the shapefile I am actually using (not for the UK) I get even more stray hexagons - some complete - some not.
Suggested solution from Roger Bivand (via an email exchange):
g1 <- spTransform(x = g, CRSobj = CRS("+init=epsg:27700"))
# cellsize from calculate_cell_size() above
h1 <- spsample(x = g1, type="hexagonal", cellsize=38309)
i2 <- HexPoints2SpatialPolygons(hex = h1)
j2 <- unionSpatialPolygons(SpP = i2, IDs = rep(1, length(i2)))
plot(j2)
i.e. avoid assign_polygons() in hexmapr and utilize 1) spsample to generate shape positions and 2) HexPoints2SpatialPolygons for the hexagonal grid (both in sp package).

How to plot points aligned at different angles and connect these by a line?

I am trying to simulate the shape of the rings from a trunk section in R, but each time that I want to approach the real shape it get more difficult. I started doing it with four radii measurements, and I got a nice solution (see here).
However, now I want to plot more than four radii but at different angles, and connect these points with a line simulating the rings like this sketch that I did:
My first approach was to rotate the matrix of data, but I could not make that all radii started in the same position (0,0). I also tried to rate the axes without success.
That is why I would like to ask for some direction to do it, and finally calculate the area of each ring.
Any help will be welcome
I am using the spline.poly function from here.
spline.poly
spline.poly <- function(xy, vertices, k=3, ...) {
# Assert: xy is an n by 2 matrix with n >= k.
# Wrap k vertices around each end.
n <- dim(xy)[1]
if (k >= 1) {
data <- rbind(xy[(n-k+1):n,], xy, xy[1:k, ])
} else {
data <- xy
}
# Spline the x and y coordinates.
data.spline <- spline(1:(n+2*k), data[,1], n=vertices, ...)
x <- data.spline$x
x1 <- data.spline$y
x2 <- spline(1:(n+2*k), data[,2], n=vertices, ...)$y
# Retain only the middle part.
cbind(x1, x2)[k < x & x <= n+k, ]
}
DATA
df = data.frame(A = c(1, 4, 5, 8, 10),
B = c(1, 3, 7, 9, 10),
C = c(2, 6, 8, 9, 10),
D = c(1, 3, 4, 7, 9),
E = c(1, 2, 3, 4, 5))
DRAW
#Calculate angles based on number of columns
angles = 0:(NCOL(df) - 1) * 2*pi/NCOL(df)
#Calculate x and y corresponding to each radial distance
toplot = lapply(1:NCOL(df), function(i){
data.frame(x = df[,i]*cos(angles[i]),
y = df[,i]*sin(angles[i]))
})
#Split toplot and merge back together the same rows
toplot2 = lapply(toplot, function(x) data.frame(x, ID = sequence(NROW(x))))
toplot2 = do.call(rbind, toplot2)
toplot2 = split(toplot2, toplot2$ID)
#Create empty plot
graphics.off()
plot(do.call(rbind, toplot), type = "n", axes = FALSE, ann = FALSE, asp = 1)
#Allow drawing outside the plot region just in case
par(xpd = TRUE)
#Draw polygons
lapply(toplot2, function(a){
polygon(spline.poly(xy = cbind(a$x, a$y), vertices = 100, k = 3))
})
#Draw points
lapply(toplot, function(a){
points(a)
})
#Draw radial lines
lapply(toplot, function(a){
lines(a)
})
AREA
area_data = lapply(toplot2, function(a){
spline.poly(xy = cbind(a$x, a$y), vertices = 100, k = 3)
})
library(geometry)
lapply(area_data, function(P) polyarea(P[,1], P[,2]))
#$`1`
#[1] 4.35568
#$`2`
#[1] 38.46985
#$`3`
#[1] 96.41331
#$`4`
#[1] 174.1584
#$`5`
#[1] 240.5837

Generating a sequence of equidistant points on polygon boundary

I am looking for a procedure that allows me to generate a sequence of equidistant points (coordinates) along the sides of an arbitrary polygon.
Imaging a polygon defined by the coordinates of its vertexes:
poly.mat <- matrix(c(0,0,
0,1,
0.5,1.5,
0.5,0,
0,0 # last row included to close the polygon
), byrow = T, ncol = 2)
colnames(poly.mat) <- c("x", "y")
plot(poly.mat, type = "l")
If the length of the sequence I want to generate is n (adjustable), how I can produce a sequence, starting at (0,0), of equidistant coordinates.
I got as far as calculating the perimeter of the shape with the geosphere package (which I believe I need)
library(geosphere)
n <- 50 # sequence of length set to be 50
perim <- perimeter(poly.mat)
perim/n # looks like every section needs to be 8210.768 something in length
You will have to write the code yourself. Sorry, there isn't a library function for every last detail of every last assignment. Assuming that each pair of points defines a line segment, you could just generate N points along each segment, as in
begin = [xbegin, ybegin ];
end = [xend, yend ];
xdist = ( xend - xbegin ) / nintervals;
ydist = ( yend - ybegin ) / nintervals;
then your points are given by [ xbegin + i * xdist, ybegin + i * ydist ]
Here is the solution I came up with.
pointDistance <- function(p1, p2){
sqrt((p2[,1]-p1[,1])^2) + sqrt((p2[,2]-p1[,2])^2)
}
getPos <- function(shp.mat, ll){
greaterLL <- shp.mat$cumdis > ll
if(all(greaterLL == FALSE)) return(poly.mat[nrow(poly.mat), c("x", "y")])
smallRow <- min(which(greaterLL)) # the smallest coordinate that has greater length
p.start <- shp.mat[smallRow-1, c("x","y")]
p.end <- shp.mat[smallRow, c("x","y")]
cumVal <- shp.mat$cumdis[smallRow]
prop <- (ll-shp.mat$cumdis[smallRow-1])/(shp.mat$cumdis[smallRow]-shp.mat$cumdis[smallRow-1])
p.start + (prop)* (p.end-p.start)
}
# shp1
poly.mat <- matrix(c(0,0,
0,1,
0.5,1.5,
0.5,0,
0,0
),byrow = T, ncol = 2)
colnames(poly.mat) <- c("x", "y")
poly.mat <- as.data.frame(poly.mat)
# Main fun
pointsOnPath <- function(shp.mat, n){
dist <- vector(mode = "numeric", length = nrow(shp.mat)-1)
for(i in 2:nrow(shp.mat)){
dist[i] <- pointDistance(p1 = shp.mat[i,], p2 = shp.mat[i-1,])
}
shp.mat$dist <- dist
shp.mat$cumdis <- cumsum(shp.mat$dist)
dis <- matrix(seq(from = 0, to = max(shp.mat$cumdis), length.out = n+1), ncol = 1)
out <- lapply(dis, function(x) getPos(shp.mat = shp.mat, ll = x))
out <- do.call("rbind", out)
out$dis <- dis
out[-nrow(out),]
}
df <- pointsOnPath(shp.mat = poly.mat, 5)
# Plot
plot(poly.mat$x, poly.mat$y, type = "l", xlim = c(0,1.5), ylim = c(0,1.5))
points(df$x, df$y, col = "red", lwd = 2)
There is room for improving the code, but it should return the correct result

Drawing Land data from 'Natural Earth' shape file in spherical coordinates by RGL

I downloaded land polygon shape file from Natural Earth [1] and I am able to plot/draw coastlines on a spherical coordinates system. Questions is how I can fill the land regions! I've been searching on this for couple of days and the resources that I came up are below. Also I added a function how to plot/draw the coastlines. Any idea?
require(rgl)
require(maptools)
shapefile_lines <- function() {
save <- par3d(skipRedraw=TRUE)
on.exit(par3d(save))
r <- 6378100 # earth radius in meters
shp <- readShapeSpatial(fn = "ne_10m_coastline/ne_10m_coastline.shp")
# Combine lines in a single matrix
mat <- do.call(rbind, sapply(1:length(shp#lines), function(i) rbind(shp#lines[[i]]#Lines[[1]]#coords,c(NA,NA))))
# Convert spherical to cartesian
xyz <- rgl.sph2car(mat[,2], mat[,1], radius=r + 200)
bg3d("black")
# Draw sphere
rgl.sphere(ng=100, radius=r, col="gray50",specular = "black", add=T)
plot3d(xyz, col = "white", add = T, type = "l")
}
rgl.sphere <- function (x=0, y=NULL, z=NULL, ng=50, radius = 1, color="white", add=F, set_normals=T, ...) {
if(length(ng)==1) ng <- c(ng, ng)
nlon <- ng[1]; nlat <- ng[2]
lat <- matrix(seq(90, -90, len = nlat)*pi/180, nlon, nlat, byrow = TRUE)
long <- matrix(seq(-180, 180, len = nlon)*pi/180, nlon, nlat)
vertex <- rgl:::rgl.vertex(x, y, z)
nvertex <- rgl:::rgl.nvertex(vertex)
radius <- rbind(vertex, rgl:::rgl.attr(radius, nvertex))[4,]
color <- rbind(vertex, rgl:::rgl.attr(color, nvertex))[4,]
for(i in 1:nvertex) {
add2 <- if(!add) i>1 else T
x <- vertex[1,i] + radius[i]*cos(lat)*cos(long)
y <- vertex[2,i] + radius[i]*cos(lat)*sin(long)
z <- vertex[3,i] + radius[i]*sin(lat)
if(set_normals)
persp3d(x, y, z, add=add2, color=color[i], normal_x=x, normal_y=y, normal_z=z, ...)
else
persp3d(x, y, z, add=add2, color=color[i], ...)
}
}
rgl.sph2car <- function(lat=0, lon=0, radius=1, deg=T, precise=T) {
if(deg) {
if(precise){
lat <- lat/180
lon <- lon/180
x <- radius*cospi(lat)*cospi(lon)
y <- radius*cospi(lat)*sinpi(lon)
z <- radius*sinpi(lat)
}else{
lat <- lat*pi/180
lon <- lon*pi/180
x <- radius*cos(lat)*cos(lon)
y <- radius*cos(lat)*sin(lon)
z <- radius*sin(lat)
}
}
return(matrix(c(x,y,z),nrow=length(x), ncol=3, dimnames=list(NULL, c("x","y","z"))))
}
Other Possible Related Resources:
1- 3d surface plot with xyz coordinates
2- https://gis.stackexchange.com/questions/90635/what-programs-would-allow-for-the-mapping-of-a-geoid-in-3d
3- Mesh generation from points with x, y and z coordinates
4- Z - Values for polygon (shapefile) in R

Resources