Reputation: 4090
I have a set of forest stands (SpatialPolygonDataFrame) distributed randomly over the landscape, i.e. dispersed and aggregated. For each polygon, I want to decide if it has an open edge or not. Polygon has open edge if:
I wander how to add attribute open_edge = TRUE/FALSE
to individual polygons? In raster
package, there is a promising approach using moving window
. However, my original data are feature classes, and unfortunately are not so raster like as in the working example.
I though to (pseudocode):
for
loop)buffer
with the standsopen_edge = TRUE
But, this approach does not consider what the stand has let's say neighbors only at 3 sides, i.e. as rook neighborhood. The poly2nb
tool seems promising, but how to add attributes to individual stands?
Here is my dummy approach but I wonder if you have a more efficient solution?
Create dummy data:
library(ggplot2) # for choropleth map plot
library(broom) # to convert spatial data to dataframe
library(mapproj)
library(spdep) # neighbours
library(rgdal)
library(rgeos)
library(sf)
library(raster)
library(dplyr)
library(spData)
library(sf)
r <- raster(nrow=6, ncol=6, crs = "+init=epsg:2957")
values(r) <- matrix(data = c(9, NA, NA, NA, NA, NA,
NA, NA, NA, NA, NA, NA,
NA, NA, NA, NA, NA, NA,
NA, NA, NA, 1, 1, 1,
NA, NA, NA, 1, 9, 1,
NA, NA, NA, 1, 1, 1),
nrow = 6,
ncol = 6,
byrow = TRUE)
# Convert raster to polygon
polys <- rasterToPolygons(r)
Identify if stand has open edge, on example of one stand:
# Subset first row in SpatialPolygonDataFrame
i = 10
one = polys[i, ]
# Keep the remaining polygons
left = polys[-i,]
# Create buffer within distance
buff = buffer(one, width = 100)
# subset set of neighbours by spatial overlap
nbrs <- left[which(gContains(sp::geometry(buff),
sp::geometry(left), byid = T)),
# Compare if the values are different
height.one = rep(one$layer[1], nrow(nbrs))
height.nbrs = nbrs$layer
# Get the differences between the neighbouring stands
difference = height.one - height.nbrs
# If the difference in at least one stand is
# in more than 5, set open_edge = TRUE
# or if no neighbours find
one$open_edge <- any(difference > 5)
Upvotes: 4
Views: 740
Reputation: 2588
It seems like ther is a more simple solution. You can use focal
a sliding window function from raster
package.
Here is an example:
library(raster)
r <- raster(nrow=6, ncol=6, crs = "+init=epsg:2957")
values(r) <- matrix(data = c(9, NA, NA, NA, NA, NA,
NA, NA, NA, NA, NA, NA,
NA, NA, 2, 1, 3, 1,
NA, NA, 1, 1, 1, 1,
NA, NA, 1, 2, 2, 1,
NA, NA, 1, 1, 1, 1),
nrow = 6,
ncol = 6,
byrow = TRUE)
# Prepare function for sliding window
focal_func <- function(x) {
# Assuming 3x3 moving window
# central cell has index 5
# Check if the cell is not NA
if (is.na(x[5])){
return(NA)
}
# Check if any surrounding is NA
if (any(is.na(x[-5]))) {
return(TRUE)
}
# Check difference
if (any((x[5] - x[-5]) > 5)) {
return(TRUE)
}
# Else, return FALSE
return(FALSE)
}
# Apply focal_function with sliding window
res <- focal(r, w = matrix(rep(1, 9), 3), fun = focal_func, pad = TRUE)
# Check if the same as desired output
res_mat <- as.matrix(res)
res[!is.na(res)] == 1
It gives:
[1] TRUE TRUE TRUE TRUE TRUE TRUE FALSE FALSE TRUE TRUE FALSE FALSE
[13] TRUE TRUE TRUE TRUE TRUE
I.e. the same as desired output.
Upvotes: 1
Reputation: 4090
Based on @RobertHijmans answer and how to get list of neighbors I have created a step by step approach to check my neighbors and evaluate height difference.
Step by step:
open_edge
as TRUE
, otherwise FALSE
open_edge
is set to TRUE
.Here is slightly more complicated situation, allowing more stands to have a neighbors:
Create dummy data:
r <- raster(nrow=6, ncol=6, crs = "+init=epsg:2957")
values(r) <- matrix(data = c(9, NA, NA, NA, NA, NA,
NA, NA, NA, NA, NA, NA,
NA, NA, 2, 1, 3, 1,
NA, NA, 1, 1, 1, 1,
NA, NA, 1, 2, 2, 1,
NA, NA, 1, 1, 1, 1),
nrow = 6,
ncol = 6,
byrow = TRUE)
# Convert raster to polygon
polys <- rasterToPolygons(r)
Calculate neighbors based on continuity, taking into account possible gaps or slivers between stands:
# Calculate the count and position of neighbors; this allows to create one by one list
nb <- poly2nb(polys,
snap = 10) # snap corrects for the gaps/slivers
# store the number of neighbors for each central cell
polys$nb_count<- card(nb)
# Has the stand an open edge? Is surrounded by neighbors, pre-value is FALSE
polys$open_edge = ifelse(card(nb) <8, "TRUE", "FALSE")
If a cell has complete neighbors, compare the differences in height between them:
# Get the position of the cell surrounded by neighbors
center.index <- which(polys$nb_count == 8)
# Get the stand height of a stand
# as a vector to compare element wise
center.height = polys[center.index,]$layer
# Loop through the cells with neighbors:
# - keep height of the central stand
# - get height of neighbors
# compare the height between them
# if difference is more than 5: => open_edge = TRUE
for (i in seq_along(center.index)) {
# Get central stand height
center.height = polys[center.index[i],]$layer
# Identify neighbors of the stands
# by the index value
nb.index = unlist(nb[center.index[i]])
# Get heights of the stands
nb.height = polys[nb.index,]$layer
# Adjust Center.height length as a vector to allow element wise comparison
center.height.v = rep(center.height, length(nb.index))
# Compare the heights
h.diff = center.height.v - nb.height
# if any difference is more than 5 => change open_edge = TRUE
if (any(h.diff > 5)) {
polys@data[center.index[i],]$open_edge <- "TRUE"
}
}
Look at final data output:
> polys@data
layer nb_count open_edge
1 9 0 TRUE
2 2 3 TRUE
3 1 5 TRUE
4 3 5 TRUE
5 1 3 TRUE
6 1 5 TRUE
7 1 8 FALSE
8 1 8 FALSE
9 1 5 TRUE
10 1 5 TRUE
11 2 8 FALSE
12 2 8 FALSE
13 1 5 TRUE
14 1 3 TRUE
15 1 5 TRUE
16 1 5 TRUE
17 1 3 TRUE
Upvotes: 0
Reputation: 47181
To get you started with spdep::poly2nb
library(raster)
r <- raster(nrow=6, ncol=6, crs = "+init=epsg:2957")
values(r) <- matrix(data = c(9, NA, NA, NA, NA, NA,
NA, NA, NA, NA, NA, NA,
NA, NA, NA, NA, NA, NA,
NA, NA, NA, 1, 1, 1,
NA, NA, NA, 1, 9, 1,
NA, NA, NA, 1, 1, 1),
nrow = 6,
ncol = 6,
byrow = TRUE)
# Convert raster to polygon
polys <- rasterToPolygons(r)
library(spdep)
nb <- poly2nb(polys)
plot(polys)
text(polys, 1:length(polys))
str(nb)
#List of 10
# $ : int 0
# $ : int [1:3] 3 5 6
# $ : int [1:5] 2 4 5 6 7
# $ : int [1:3] 3 6 7
# ...
So poly 1 has no neighbors, poly 2 has neighbors 3, 5, 6, etc.
Now you can use sapply
to compute things. For example the number of neighbors
nbcnt <- sapply(nb, function(i) length(i[i>0]))
nbcnt
#[1] 0 3 5 3 5 8 5 3 5 3
To add this back to the polygons
polys$nbcnt <- nbcnt
Upvotes: 1