luukvdmeer / sfnetworks

Tidy Geospatial Networks in R
https://luukvdmeer.github.io/sfnetworks/
Other
333 stars 20 forks source link

Error when blending points into network #252

Open xiaochi-liu opened 11 months ago

xiaochi-liu commented 11 months ago

Describe the bug Hi there. Thanks for this handy package!

I just got an minor error that couldn't blend certain points into the road network: image

This is strange because the st_network_blend() method works perfectly fine for my other data points. Only the points shown above couldn't be blended. The test data can be accessed here.

Reproducible example

library(tidyverse)
library(sf)
#> Linking to GEOS 3.11.0, GDAL 3.5.3, PROJ 9.1.0; sf_use_s2() is TRUE
library(sfnetworks)

# import data
sf_road_test = read_rds("sf_road_test.rds")
sf_point_test = read_rds("sf_point_test.rds")

# turn road `sf` into `sfnetwork`
sf_road_test %>% 
  as_sfnetwork() -> sfnetwork_road_test

# blend points into network
sfnetwork_road_test %>% 
  st_network_blend(sf_point_test)
#> Warning: st_network_blend assumes attributes are constant over geometries
#> Error in `structure()`:
#> ! Assigned data `value` must be compatible with existing data.
#> ✖ Existing data has 0 rows.
#> ✖ Assigned data has 2 rows.
#> ℹ Only vectors of size 1 are recycled.
#> Caused by error in `vectbl_recycle_rhs_rows()`:
#> ! Can't recycle input of size 2 to size 0.
#> Backtrace:
#>      ▆
#>   1. ├─sfnetwork_road_test %>% st_network_blend(sf_point_test)
#>   2. ├─sfnetworks::st_network_blend(., sf_point_test)
#>   3. ├─sfnetworks:::st_network_blend.sfnetwork(., sf_point_test)
#>   4. │ └─sfnetworks:::blend_(x, y, tolerance)
#>   5. │   ├─base::`$<-`(`*tmp*`, ".sfnetwork_index", value = `<int>`)
#>   6. │   └─sf:::`$<-.sf`(`*tmp*`, ".sfnetwork_index", value = `<int>`)
#>   7. │     ├─base::`[[<-`(`*tmp*`, i, value = `<int>`)
#>   8. │     ├─sf:::`[[<-.sf`(`*tmp*`, i, value = `<int>`)
#>   9. │     │ └─base::structure(...)
#>  10. │     ├─base::NextMethod()
#>  11. │     └─tibble:::`[[<-.tbl_df`(`*tmp*`, i, value = `<int>`)
#>  12. │       └─tibble:::tbl_subassign(...)
#>  13. │         └─tibble:::vectbl_recycle_rhs_rows(value, fast_nrow(xo), i_arg = NULL, value_arg, call)
#>  14. │           ├─base::withCallingHandlers(...)
#>  15. │           └─vctrs::vec_recycle(value[[j]], nrow)
#>  16. └─vctrs:::stop_recycle_incompatible_size(...)
#>  17.   └─vctrs:::stop_vctrs(...)
#>  18.     └─rlang::abort(message, class = c(class, "vctrs_error"), ..., call = call)

Expected behavior The sf_point_test can be blended into sfnetwork_road_test.

R Session Info

sessionInfo()
#> R version 4.3.0 (2023-04-21)
#> Platform: aarch64-apple-darwin20 (64-bit)
#> Running under: macOS Ventura 13.4.1
#> 
#> Matrix products: default
#> BLAS:   /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/lib/libRblas.0.dylib 
#> LAPACK: /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.11.0
#> 
#> locale:
#> [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
#> 
#> time zone: Australia/Melbourne
#> tzcode source: internal
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> loaded via a namespace (and not attached):
#>  [1] digest_0.6.33   fastmap_1.1.1   xfun_0.39       glue_1.6.2     
#>  [5] knitr_1.43      htmltools_0.5.5 rmarkdown_2.23  lifecycle_1.0.3
#>  [9] cli_3.6.1       reprex_2.0.2    withr_2.5.0     compiler_4.3.0 
#> [13] rstudioapi_0.14 tools_4.3.0     evaluate_0.21   yaml_2.3.7     
#> [17] rlang_1.1.1     fs_1.6.3

Thank you very much for your kind guidance!

Robinlovelace commented 11 months ago

Maybe because multiple points are associated with a single point on the network?

agila5 commented 11 months ago

I think this is an error that occurs when blending sf objects with attributes into the network and every external point is matched with an existing node. Simpler reprex:

#packages
library(sf)
#> Linking to GEOS 3.11.2, GDAL 3.6.2, PROJ 9.2.0; sf_use_s2() is TRUE
library(sfnetworks)

# toy-data
l <- st_linestring(rbind(c(0, 0), c(1, 1)))
sfc <- as_sfnetwork(st_sfc(l))
external_point <- st_sfc(st_point(c(-1, -1)))

# plot
# plot(sfc, axes = TRUE, xlim = c(-1.5, 2), ylim = c(-1.5, 2), cex = 2, lwd = 2)
# plot(external_point, col = "red", add = TRUE, pch = 20, cex = 2)

# blend sfc object
sfc2 <- st_network_blend(sfc, external_point)

# the output is identical to original net
igraph::identical_graphs(sfc, sfc2)
#> [1] TRUE

# since the closest point to "external_point" is the boundary point of l.
# Nevertheless, if I add an attribute to "external_point"
external_sf <- st_sf(
  data.frame(x = "A"), 
  geometry = external_point
)

st_network_blend(sfc, external_sf)
#> Error in `[[<-.data.frame`(`*tmp*`, i, value = 3:2): replacement has 2 rows, data has 0

Created on 2023-08-11 with reprex v2.0.2

I think the error is here

https://github.com/luukvdmeer/sfnetworks/blob/23a4125030178f5c42ebb48d2547052a31a3cdea/R/blend.R#L456-L458

since new_feat_idxs is created as follows

https://github.com/luukvdmeer/sfnetworks/blob/23a4125030178f5c42ebb48d2547052a31a3cdea/R/blend.R#L444-L445

and is_new might be a vector of just FALSE values (implying that new_feat_idxs is a vector with 0 elements).

xiaochi-liu commented 10 months ago

Thank you very much @agila5 and @Robinlovelace!

I removed all attributes for the external points, which can then be blended into the network!

May I ask, is it possible to fix it? What I want to do after blending is to extract the network nodes that are generated by the points blended:

library(sf)
library(sfnetworks)

# toy-data
l = st_linestring(rbind(c(0, 0), c(1, 1)))
sfc = as_sfnetwork(st_sfc(l))
external_sf = st_sf(
  data.frame(x = "A"), 
  geometry = external_point
)

# blend external points into the network (if successful)
sfc2 = st_network_blend(sfc, external_sf)

# extract the nodes that are generated by blending points
sfc2 %>% 
  activate("nodes") %>% 
  st_as_sf() %>% 
  filter(!is.na(A)) -> sf_point_blended

Thus, it would be great if the external points had some attributes.