luukvdmeer / sfnetworks

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

More methods for as_sfnetwork #41

Open luukvdmeer opened 4 years ago

luukvdmeer commented 4 years ago

Is your feature request related to a problem? Please describe. Right now, you can already convert a lot of graph objects to an sfnetwork object, if 1) they are a tbl_graph or can be converted to a tbl_graph with as_tbl_graph AND 2) at least the nodes can be converted to an sf object with st_as_sf. The as_tbl_graph function supports a large amount of graph data structures in R.

However, in sfnetwork we also want to support the most important spatial network data structures that already exist.

Describe the solution you'd like More methods for as_sfnetwork, addressing the most important spatial network data structures in R.

An overview (this list will be updated):

@Robinlovelace @agila5 @loreabad6 and others, please feel free to add more classes in the comments!

luukvdmeer commented 4 years ago

There is now a method for sfNetwork from stplanr (in develop):

library(sfnetworks)
library(stplanr)

sln = SpatialLinesNetwork(roxel)
sfn = as_sfnetwork(sln)

It might not be as efficient as possible yet. It simply recreates the network with the @sl slot, and in addition takes the directedness from the @g slot.

Regarding dodgr: the dodgr network object are of class c("data.frame", "dodgr_streetnet"). So providing it to as_sfnetwork will try the data.frame method, which actually runs without errors because tidygraph supports data frames as input, but the result is not correct of course (it treats the first two columns as from and to). Instead, a function dodgr_to_sfnetwork could be used:

dodgr_to_sfnetwork = function(graph) {
  requireNamespace("dodgr")
  as_sfnetwork(
    dodgr::dodgr_to_tidygraph(graph),
    force = TRUE,
    coords = c("x", "y"),
    crs = 4326
  )
}

Then:

library(dodgr)

graph = weight_streetnet(hampi)

graph
#>  geom_num edge_id    from_id from_lon from_lat      to_id   to_lon
#>  1        1       1  339318500 76.47489 15.34169  339318502 76.47612
#>  2        1       2  339318502 76.47612 15.34173  339318500 76.47489
#>  3        1       3  339318502 76.47612 15.34173 2398958028 76.47621
#>  4        1       4 2398958028 76.47621 15.34174  339318502 76.47612
#>  5        1       5 2398958028 76.47621 15.34174 1427116077 76.47628
#>  6        1       6 1427116077 76.47628 15.34179 2398958028 76.47621
#>      to_lat          d d_weighted      highway   way_id component
#>  1 15.34173 132.442169  147.15797 unclassified 28565950         1
#>  2 15.34169 132.442169  147.15797 unclassified 28565950         1
#>  3 15.34174   8.888670    9.87630 unclassified 28565950         1
#>  4 15.34173   8.888670    9.87630 unclassified 28565950         1
#>  5 15.34179   9.326536   10.36282 unclassified 28565950         1
#>  6 15.34174   9.326536   10.36282 unclassified 28565950         1
#>         time time_weighted
#>  1 31.786121     35.317912
#>  2 31.786121     35.317912
#>  3  2.133281      2.370312
#>  4  2.133281      2.370312
#>  5  2.238369      2.487076
#>  6  2.238369      2.487076

dodgr_to_sfnetwork(graph)
#> # An sfnetwork with 2926 nodes and 5973 edges
#> #
#> # CRS:  EPSG:4326 
#> #
#> # A directed simple graph with 3 components and spatially implicit edges
#> #
#> # Node Data:     2,926 x 4 (active)
#> # Geometry type: POINT
#> # Dimension:     XY
#> # Bounding box:  xmin: 76.37263 ymin: 15.30107 xmax: 76.60668 ymax:
#> #   15.40121
#>   name       component     n            geometry
#>   <chr>          <int> <dbl>         <POINT [°]>
#> 1 339318500          1     0 (76.47489 15.34169)
#> 2 339318502          1     1 (76.47612 15.34173)
#> 3 2398958028         1     2 (76.47621 15.34174)
#> 4 1427116077         1     3 (76.47628 15.34179)
#> 5 339318503          1     4  (76.47641 15.3419)
#> 6 2398958034         1     5  (76.4765 15.34199)
#> # … with 2,920 more rows
#> #
#> # Edge Data: 5,973 x 11
#>    from    to weight d_weighted  time time_weighted   xfr   yfr   xto
#>   <int> <int>  <dbl>      <dbl> <dbl>         <dbl> <dbl> <dbl> <dbl>
#> 1     1     2 132.       147.   31.8          35.3   76.5  15.3  76.5
#> 2     2     1 132.       147.   31.8          35.3   76.5  15.3  76.5
#> 3     2     3   8.89       9.88  2.13          2.37  76.5  15.3  76.5
#> # … with 5,970 more rows, and 2 more variables: yto <dbl>,
#> #   component <int>

In dodgr there are more of such conversion functions, like dodgr_to_igraph, dodgr_to_tidygraph etc. I think dodgr_to_sfnetwork would therefore fit better as part of dodgr (when we have a more stable version of the package of course).

Regarding linnet:

At the moment it's possible to convert a linnet object to an sf object and then apply the usual methods.

@agila do you remember which function?

agila5 commented 4 years ago

Hi! This is an example of converting a linnet object into sf format:

# packages
library(sf)
#> Linking to GEOS 3.8.0, GDAL 3.0.4, PROJ 6.3.1
library(spatstat)
#> Loading required package: spatstat.data
#> Loading required package: nlme
#> Loading required package: rpart
#> 
#> spatstat 1.64-1       (nickname: 'Help you I can, yes!') 
#> For an introduction to spatstat, type 'beginner'

# data
class(simplenet)
#> [1] "linnet" "list"
plot(simplenet, main = "Linnet")


# transform into sf format
simplenet_sf <- st_as_sf(as.psp(simplenet))
simplenet_sf
#> Simple feature collection with 11 features and 1 field
#> geometry type:  GEOMETRY
#> dimension:      XY
#> bbox:           xmin: 0 ymin: 0 xmax: 1 ymax: 1
#> CRS:            NA
#> First 10 features:
#>      label                           geom
#> 1   window POLYGON ((0 0, 1 0, 1 1, 0 ...
#> 2  segment LINESTRING (0.181276 0.8345...
#> 3  segment LINESTRING (0.1497036 0.211...
#> 4  segment LINESTRING (0.3254689 0.460...
#> 5  segment LINESTRING (0.4411403 0.160...
#> 6  segment LINESTRING (0.3254689 0.460...
#> 7  segment LINESTRING (0.5358572 0.802...
#> 8  segment LINESTRING (0.5358572 0.802...
#> 9  segment LINESTRING (0.3795058 0.320...
#> 10 segment LINESTRING (0.8418658 0.277...
plot(st_geometry(st_collection_extract(simplenet_sf, "LINESTRING")), main = "sf")

Created on 2020-06-07 by the reprex package (v0.3.0)

There are a few caveats:

  1. The POLYGON geometry is the window of the linnet (like a polygonal bbox).
  2. The appropriate sf methods for psp objects were created in sf 0.8.1.
  3. AFAIK there is no direct way for converting a linnet object into sf format without passing through psp format but I don't think it's a big deal now. We can add that as "future work".
  4. I'm not sure what to do for the CRS component since I think it's not stored in the linnet format.
Robinlovelace commented 4 years ago

Great to see this development work happening, awesome that we can now convert, will make testing/comparing/switching between implementations easier.

luukvdmeer commented 4 years ago

I think we have implemented the most important methods, so I will move this issue to milestone 3 for the remaining ones.

Additionally, we should do a PR to dodgr for a dodgr_to_sfnetwork function.

agila5 commented 4 years ago

Self note: Prof. Baddeley started working on a separate project for linnet class (https://github.com/baddstats/spatstat.linnet) so check if we need to update the dependencies and other methods for conversions.

luukvdmeer commented 3 years ago

Method for sfc objects is now added on develop

agila5 commented 3 years ago

Hi! One question since I don't know what are the "best practices" in these cases. I would like adding a new method to convert sfnetwork objects into linnet objects (since, AFAIK, that's the main R package for statistical analysis of point patterns on networks). Should I add a new PR here or in the spatstat repo?

luukvdmeer commented 3 years ago

I am not sure what the best practice is, maybe @Robinlovelace has an idea? It maybe depends also on how open the spatstat developers are to such pull requests. We can implement it in sfnetworks since as_tbl_graph, st_as_sf and as_tibble fall in the same category and are all implemented in sfnetworks.

Robinlovelace commented 3 years ago

My first impression is that adding the functionality to sfnetworks makes sense, unless there are function in linnet that are needed in which case it would make sense to put in a PR there. No harm in asking a question on their issue tracker before opening a PR - that's probably best practice: ask before doing can save lots of time in my experience.

luukvdmeer commented 3 years ago

Other packages to look into regarding integration with sfnetworks: spdep and spatgraph

agila5 commented 1 year ago

Another package that I want to check sooner or later is MetricGraph. They implement a new class called metric_graph for spatial analysis on linear networks.