-
Notifications
You must be signed in to change notification settings - Fork 299
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Finding neighboring polygons #234
Comments
|
Disclaimer: If Stackoverflow is the preferred venue for this sort of question please let me know and I'll post there. Below I have a reprex showing the type of polygon neighbor calculation I do regularly. The blue county has several intersecting neighbors which are identified using Could someone demonstrate a way to do this using library(sp)
library(spdep)
library(tidyverse)
library(sf)
demo(nc, ask = FALSE, verbose = FALSE)
par(mfrow = c(3, 1), mar = c(rep(0.75, 4)))
# Target county
plot(nc[1], col = "red", main = "Target county")
plot(nc[50, "AREA"], add = TRUE)
# Using sf verbs
spare_mtx <- st_intersects(nc, nc)
#> although coordinates are longitude/latitude, it is assumed that they are planar
plot(nc[1], col = "red", main = "Neighbors: st_intersection")
plot(nc[spare_mtx[[50]], "NAME"], add = TRUE)
plot(nc[50, "AREA"], add = TRUE)
# Using the 'rook' method from spdep::poly2nb
spare_mtx_rook <- poly2nb(as(nc, "Spatial"), queen = FALSE)
plot(nc[1], col = "red", main = "Neighbors: poly2nb")
plot(nc[spare_mtx_rook[[50]], "NAME"], add = TRUE)
plot(nc[50, "AREA"], add = TRUE) |
Look into (It does give you a dense matrix, though; in case memory is a problem you could do |
Got it.
Using the example above, I loop through the result of spare_mtx <- st_intersects(nc, nc)
spare_mtx[[50]]
#> [1] 39 40 42 50 69 70 71
spare_mtx[[50]] %>%
map(~nc[.x, ]) %>%
map(~st_relate(x = nc[50, ], y = .x))
#> [[1]]
#> [,1]
#> [1,] "FF2F11212"
#>
#> [[2]]
#> [,1]
#> [1,] "FF2F11212"
#>
#> [[3]]
#> [,1]
#> [1,] "FF2F11212"
#>
#> [[4]]
#> [,1]
#> [1,] "2FFF1FFF2"
#>
#> [[5]]
#> [,1]
#> [1,] "FF2F11212"
#>
#> [[6]]
#> [,1]
#> [1,] "FF2F01212" <- Note: this is the diagonal neighbor county from the example above
#>
#> [[7]]
#> [,1]
#> [1,] "FF2F11212" I think a convenience function similar to |
On a related note, I'm a bit puzzled by the following error: library(tidyverse)
library(sf)
library(purrr)
demo(nc, ask = FALSE, verbose = FALSE)
# Loop over nc$geom with st_intersects() and save the results in a new list-col: nc$INTERSECT
nc %>% mutate(INTERSECT = map(.x = geom, .f = st_intersects, y = st_geometry(nc)))
#> Error in mutate_impl(.data, dots): st_crs(x) == st_crs(y) is not TRUE In this example, it appears that the classes of the two objects are different: #> Browse[2]> map(list(x,y),class)
#> [[1]]
#> [1] "XY" "MULTIPOLYGON" "sfg"
#>
#> [[2]]
#> [1] "sfc_MULTIPOLYGON" "sfc" ... and therefore #> Browse[2]> map(list(x,y),st_crs)
#> [[1]]
#> $epsg
#> [1] NA
#>
#> $proj4string
#> [1] NA
#>
#> attr(,"class")
#> [1] "crs"
#>
#> [[2]]
#> $epsg
#> [1] 4267
#>
#> $proj4string
#> [1] "+proj=longlat +datum=NAD27 +no_defs"
#>
#> attr(,"class")
#> [1] "crs" I find it confusing (and inconvenient) that a It seems like a vignette demonstrating the recommended way of manipulating Edit: library(tidyverse)
library(stringr)
library(sf)
library(purrr)
demo(nc, ask = FALSE, verbose = FALSE)
nc %>%
select(NAME) %>%
mutate(NB = st_intersects(geom, geom)) %>%
mutate(NB_GEOMS = map(NB, ~st_geometry(nc[.x, ]))) %>%
mutate(GEOM_LST = map(geom, ~st_geometry(.x) %>% st_set_crs(st_crs(nc)))) %>%
mutate(RELATE = map2(.x = NB_GEOMS, .y = GEOM_LST, .f = ~st_relate(.y, .x))) %>%
mutate(RELATE_LGL = map(RELATE, str_detect, pattern = "^[[:alnum:]]{4}1")) %>%
mutate(NB_ROOK = map2(.x = NB, .y = RELATE_LGL, ~.x[.y])) %>%
select(NAME, NB, NB_ROOK, dplyr::everything()) %>%
slice(50) %>% # Rowan County is the one highlighted in the example above
as_tibble()
#> # A tibble: 1 × 8
#> NAME NB NB_ROOK NB_GEOMS GEOM_LST
#> <fctr> <list> <list> <list> <list>
#> 1 Rowan <int [7]> <int [6]> <simple_feature> <simple_feature>
#> # ... with 3 more variables: RELATE <list>, RELATE_LGL <list>,
#> # geom <simple_feature> |
if we call a geometry predicate like st_intersects, do not require that x and y have identical crs when one of them (or both) is an sfg, since sfg's don't have a crs. Adresses #234
Your example above > nc %>% mutate(INTERSECT = map(.x = geom, .f = st_intersects, y = st_geometry(nc))) %>% print(n=2)
Simple feature collection with 100 features and 15 fields
geometry type: MULTIPOLYGON
dimension: XY
bbox: xmin: -84.32385 ymin: 33.88199 xmax: -75.45698 ymax: 36.58965
epsg (SRID): 4267
proj4string: +proj=longlat +datum=NAD27 +no_defs
First 2 features:
AREA PERIMETER CNTY_ CNTY_ID NAME FIPS FIPSNO CRESS_ID BIR74 SID74
1 0.114 1.442 1825 1825 Ashe 37009 37009 5 1091 1
2 0.061 1.231 1827 1827 Alleghany 37005 37005 3 487 0
NWBIR74 BIR79 SID79 NWBIR79 INTERSECT geom
1 10 1364 0 19 1, 2, 18, 19 MULTIPOLYGON(((-81.47275543...
2 10 542 3 12 1, 2, 3, 18 MULTIPOLYGON(((-81.23989105... now works, but the simpler way to do this is nc %>% mutate(INTERSECT = st_intersects(.)) I am not so sure I can follow your misunderstanding of |
Thanks for adding d9d0af3 🎉 Regarding your question: I believe that my mistake was that I should have used |
I'd like to know your thoughts (as the package developer) on the approach I demonstrated in the edit - is this the way you intend for users to step through the creation of a spatial analysis variable (such as identifying nearest neighbors)? Or do you think it's more suitable for users to compose functions that do this sort of task? |
With admittedly limited knowledge of |
You certainly could use the indices - it's probably more computationally efficient to do it that way. But that is not the focus of my question. My question is a bit more general: how should an Using the existing I think that relatively new R-users like myself who are starting to tackle spatial problems (or spatial datasets) would benefit from a vignette demonstrating how to go about a multi-step spatial operation using It looks like you've started a vignette to-do list - perhaps you might consider adding this request to your list. Thanks! |
I now understand it - sorry, took a while. What I think we need is to add a |
Pls test; you should now get rook neighbors with st_rook = function(a, b = a) st_relate(a, b, pattern = "F***1****")
nc %>% mutate(NB_ROOK = st_rook(.)) |
This is fantastic! I've tested and it works. By extension, to get queen's case neighbors as well you can do: st_queen <- function(a, b = a) st_relate(a, b, pattern = "F***T****")
nc %>% mutate(NB_QUEEN = st_queen(.)) which constitutes an improvement on using For anyone coming across this, the Wikipedia page for DE-9IM strings is quite helpful: https://en.wikipedia.org/wiki/DE-9IM. |
Thanks @walkerke , I've added this to the documentation of |
Hi, I am creating a function, st2nb, to compute adjacencies from sf-objects using the the code in poly2nb() using all st_rook() and st_queen(), as explained in earlier posts in this thread. However, poly2nb() takes an additional argument called snap: snap: boundary points less than ‘snap’ distance apart are Is there any way to include this in the call to st_relate? Thanks! Virgilio |
I can think of two solutions:
|
Hi Edzer,
I can think of two solutions:
• you can use st_distance to compute distances, and compare these with snap; this however gives you a dense matrix,
• set the precision, which should round away (most?) differences smaller than snap.
Thanks for your comments. Setting the precision seems to have funny side and st_distance seems to only work point geometries. It would be doable with this but it really involves more work than using st_queen or st_rook… So I think that I will keep a warning for now. :)
Best,
Virgilio
|
Somewhere I should have an object that needed non-default snap that might help. I'll try to find it next week - may we talk about this at useR!? |
Hi Roger,
Somewhere I should have an object that needed non-default snap that might help. I'll try to find it next week - may we talk about this at useR!?
Thanks! That would be very useful. I think that there is always the possibility of rewriting the existing code to deal with sf-objects instead of using st_relate, but this will require more work, I think.
Best,
Virgilio
|
> p = st_polygon(list(rbind(c(0,0),c(1,1),c(0,1),c(0,0))))
> st_distance(p, p + c(2,2))
[,1]
[1,] 1.414214 |
Hi
El 22 jun 2017, a las 14:30, Edzer Pebesma ***@***.***> escribió:
> p = st_polygon(list(rbind(c(0,0),c(1,1),c(0,1),c(0,0))))
> st_distance(p, p + c(2,2))
[,1]
Thanks for the example. In poly2nb(), the bounding boxes of two possible neighbors are checked first to see if they overlap. If they do, then the boundaries are actually checked to see if they are neighbors or not. So, there is no need to compute all pairs of distances. But I thought that perhaps with simple features there were other (simpler) options using some of the st_XXX functions.
Another option would be to add a buffer of snap/2 around each polygon and then use st_relate, right? Just thinking…
Best,
Virgilio
|
If they do not overlap, they can be still snap distance apart... The st_buffer approach sounds good, if you insist on supporting snap. |
I found this approach worked in a use case from sp, as documented in #210. The question is a) does this generalise to other instances where the dimension of the relation is important and b) how to make the code more user-friendly and concise? If there's no quickfire answer I can open a new issue but just wanted to check I'm not missing something obvious and easy to teach first (from an R for spatial data course where people are asking about this!): library(sp)
g = SpatialGrid(GridTopology(c(0, 0), c(1, 1), c(3, 3)))
p = as(g, "SpatialPolygons")
library(sf)
#> Linking to GEOS 3.5.1, GDAL 2.2.1, proj.4 4.9.2, lwgeom 2.3.3 r15473
library(tidyverse)
#> Loading tidyverse: ggplot2
#> Loading tidyverse: tibble
#> Loading tidyverse: tidyr
#> Loading tidyverse: readr
#> Loading tidyverse: purrr
#> Loading tidyverse: dplyr
#> Conflicts with tidy packages ----------------------------------------------
#> filter(): dplyr, stats
#> lag(): dplyr, stats
p_sf = st_as_sf(p)
st_rook = function(a, b = a) st_relate(a, b, pattern = "F***1****")
st_queen <- function(a, b = a) st_relate(a, b, pattern = "F***T****")
p_queen = p_sf %>% mutate(NB_ROOK = st_queen(., b = p_sf[5, ])) %>% filter(NB_ROOK ==
1)
nrow(p_queen)
#> [1] 8
p_rook = p_sf %>% mutate(NB_ROOK = st_rook(., b = p_sf[5, ])) %>% filter(NB_ROOK ==
1)
nrow(p_rook)
#> [1] 4
p_minDim2 = p_sf %>% filter(st_within(., y = p_sf[5, ], sparse = FALSE))
nrow(p_minDim2)
#> [1] 1 |
Notes so far on spdep's R-forge SVN site. This is very much work in progress, as Edzer has added a class ( Because spdep (on R-forge) now suggests sf (and may import from), it has stopped checking and building on R-forge (but rgdal and rgeos build and check OK??). The report:
But you can download the Rmd file, or checkout SVN. |
Does r-forge have GDAL >= 2.0 ? |
It is possible to transfer the repository's history and match svn user names to GitHub names. I can help out if you would like to preserve these.
Should the repo become part of r-spatial?
|
Thanks for the offer of help! Is this a reasonable representation of transfer? I see that gstat is not in r-spatial, so I think I would do the same, keeping r-spatial for shared infrastructure, does that make sense? |
r-spatial welcomes modern and actively maintained spatial R packages. |
@edzer spdep isn't modern, but is actively maintained. It also invites modularisation and rationalisation, but it isn't clear that modularisation in-place (in r-spatial) is the only choice. For example, I do not use roxygen and am not convinced that it is helpful; I feel further that mechanical code coverage is not better than help page examples showing the relationship between implementation and the published books/articles with their examples. So if modern means roxygen and testthat, spdep isn't modern. If modern is without such constraints, maybe r-spatial is viable. |
With modern I was more thinking of modern, state-of-the-art methods, not of coding styles. |
Aha, then yes, spdep and code/packages depending on spdep are modern ... like the recent Wagner & Zeileis spatial and tree regression two-step package, and others ... |
@Nowosad : may I upload a newly imported NY8 GPKG to spData? This will let me continue with the neighbour object vignette, and start a transition to importing data from spData into spdep, then spdep to github. |
@rsbivand Yes, of course. Do you prefer to make a pull request or should I give you a push access to the repository? |
Thanks, when I get to it, I'll make a PR. |
The vignette at: and attached now runs, see the summary section. You'll also need spData from https://github.com/rsbivand/spData; I haven't made a PR yet for this, so it isn't in Jakub Nowosad's repo. Maybe you can just try out the suggestions in the vignette summary without needing the data. I've been using the most recent sf development version. Please let me know if this is clear, and if it helps. |
When installing spdep from r-forge, after installing your spData fork, I'm seeing:
any ideas? |
Checks cleanly here (two different Fedora machines). This looks like about line 269: try(NY8_sf_old_1_sgbp <- st_queen(NY8_sf_old)), the cited line numbers don't seem relevant. This has to be on the MULTIPOLYGON object, it can't be on the POINT object. Can you run it with spdep from CRAN free-standing? I'll try ... success. The error message is shown as:
in chunk 31. I've added ", silent = TRUE" to the try lines to remove the verbatim error message from the output, but R CMD build worked for me anyway when it was there. |
The error came from RANN not being installed; it now runs. |
Will now run without RANN too. |
spdep now on r-spatial on github, 0.7-2 submitted to CRAN |
I created a grid using
I am not sure what exactly is the issue here. Am I inputting the wrong spatial object here? Or is it because the number of cells in X direction is not the same as that in Y direction? I also tried looking at the code of I wonder if you all have some obvious solution to my issue. If not, I can share a reprex for the same. Edit: sharing reprex
|
Always minimal reprex. Note that this is a very old issue, and that spdep fully supports most sf objects. |
Thanks for the very quick response. I've updated my post with a reprex. Like the OP, I would like to find a way to achieve this using only |
Please avoid any tidyverse and pipes in all reprex, as they prevent access to intermediate objects, in which misunderstandings typically occur. Do not use the st_rook() etc. functions, use functions in spdep. |
Do not use +towgs84 ever. Do not expect spherical geometries as planar. Consider reading https://rsbivand.github.io/csds_jan23/csds_crs_workshop_230119.html if this seems hard to grasp. s2 does not behave like GEOS. See what happens in your (corrected) reprex with sf_use_s2(FALSE). |
@rikudoukarthik there is no problem at all in the example:
spdep does a lot more checking (in particular for no-neighbour entities) and prepares the output object for use in spatial analysis. It does not use sp objects. |
Thanks @rsbivand for the detailed responses. I now understand the issue with using +towgs84, and have changed it as you have suggested. However, I am still facing an issue in the example. Would you be able to shed light on what is happening here? I figure it might have to do with the fact that |
Only Simply stop using |
I use
spdep::poly2nb
to identify neighboring polygons, but this necessitates a conversion back tosp
- is there a way to do this without that conversion?The text was updated successfully, but these errors were encountered: