Skip to content
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

Isochrone and isodistance resolution #223

Closed
pasipasi123 opened this issue Mar 7, 2024 · 5 comments
Closed

Isochrone and isodistance resolution #223

pasipasi123 opened this issue Mar 7, 2024 · 5 comments

Comments

@pasipasi123
Copy link

dplyr_1.1.4 sf_1.0-15 mapview_2.11.2 osmdata_0.2.5 dodgr_0.3.0.015

Thanks for the great package. I'm wondering if it's possible to enhance or increase the resolution of dodgr_isochrone() (and also isodistance) results? I'm analyzing walking and cycling in an urban environment, where short distances are relevant. By resolution I mean a similar measure that's available in osrm::osrmIsochrone() (link) that basically increases the the number of polygon vertices to create a more well defined polygon. IIRC osrm creates a regular reachable point matrix within the catchment area with given resolution/density to create the isochrone polygon. I don't have osrm examples right now because I don't have the router server running (which is why I'm thankful for the dodgr package).

Example code:

library(dodgr)
library(osmdata)
library(mapview)
library(sf)
library(dplyr)

dat <- opq("Espoo Leppävaara") %>%
  add_osm_feature(key = "highway") %>%
  osmdata_sc()
graph <- weight_streetnet(dat, wt_profile = "foot")

x <- dodgr_isochrones(graph, from = "10594643693", tlim = c(150, 300, 600, 900, 1200))

x %>% 
  st_as_sf(coords = c("x", "y"), crs = 4326) %>% 
  group_by(tlim) %>% 
  summarise(do_union = FALSE) %>% 
  st_cast("POLYGON") %>% 
  mapview()

This produces polygons that are fairly crude given the density of the street network (dodgr_to_sf(graph) %>% mapview()).

image

Creating a one minute walk isochrone does not seem to be possible, because only a few points are created, and the two minute isochrone shows that the some routes needed for reaching the endpoints are not within the polygon area. I'm guessing only added points along the route and also between the routes could help here.

x2 <- dodgr_isochrones(graph, from = "10594643693", tlim = c(120)) 

x2 %>% 
  st_as_sf(coords = c("x", "y"), crs = 4326) %>% 
  group_by(tlim) %>% 
  summarise(do_union = FALSE) %>% 
  st_cast("POLYGON") %>% 
  mapview()

image

@mpadge
Copy link
Member

mpadge commented Mar 7, 2024

Good question. The current vertices are those in the network taken just before one step further would exceed the limits, so in that sense are the minimal set of absolute limit vertices in the network. Extra points could be included by expanding these exact limit points to include additional approximate limit points. Let me think about how that might be done and get back to you.

@mpadge
Copy link
Member

mpadge commented May 17, 2024

@pasipasi123 Sorry about delayed response here. I've checked out several ways of potentially filling in the polygons in cases like yours, but none of them end up any better than simply calculating a convex hull around the points. sf provides the sf_convex_hull() function to do that "properly", by taking the earth's ellipsoid into account, but the following code shows that simply calculating a mathematical hull directly from the points is sufficiently accurate:

library (dodgr)
packageVersion ("dodgr")
#> [1] '0.3.0.17'

f <- "junk.Rds"
if (!file.exists (f)) {
    dat <- osmdata::opq("Espoo Leppävaara") |>
        osmdata::add_osm_feature(key = "highway") |>
        osmdata::osmdata_sc(quiet = FALSE)
    saveRDS (dat, f)
} else {
    dat <- readRDS (f)
}
graph <- weight_streetnet(dat, wt_profile = "foot")
#> Loading required namespace: geodist
#> Loading required namespace: dplyr

from <- "10594643693"
v <- dodgr_vertices (graph)
v_from <- v [match (from, v$id), ]
x <- dodgr_isochrones(graph, from = from, tlim = 120)
p <- sfheaders::sf_point (x [, c ("x", "y")]) |>
    sf::st_set_crs (4326) |>
    sf::st_combine ()
p_sf <- sf::st_sf (geometry = sf::st_cast (p, "POLYGON"))
hull_sf <- sf::st_sf (geometry = sf::st_convex_hull (p))
h <- grDevices::chull (x [, c ("x", "y")])
hull <- sfheaders::sf_polygon (x [h, c ("x", "y")]) [, "geometry"]
sf::st_crs (hull) <- 4326
dat <- rbind (p_sf, hull, hull_sf)
plot (dat, col = "transparent", lwd = 1, border = c ("red", "blue", "green"))

sf::st_area (hull_sf) - sf::st_area (hull)
#> -1.251829e-08 [m^2]
sf::st_perimeter (hull_sf) - sf::st_perimeter (hull)
#> -6.434675e-11 [m]

Created on 2024-05-17 with reprex v2.1.0

Any other way to extract "well behaved" polygons using the street network is (1) far from trivial, and (2) unavoidably dependent on algorithm design with the unavoidable consequence of results being harder to interpret, and therefore likely harder to use.


My proposal here is to add a convex parameter to the function with a default of FALSE for current behaviour, but if set to TRUE, then the hull is reduced to the convex hull only, like the green line shown above.

@mpadge
Copy link
Member

mpadge commented May 17, 2024

The above commits now enable this:

library (dodgr)
packageVersion ("dodgr")
#> [1] '0.3.0.19'

f <- "junk.Rds"
if (!file.exists (f)) {
    dat <- osmdata::opq("Espoo Leppävaara") |>
        osmdata::add_osm_feature(key = "highway") |>
        osmdata::osmdata_sc(quiet = FALSE)
    saveRDS (dat, f)
} else {
    dat <- readRDS (f)
}
graph <- weight_streetnet(dat, wt_profile = "foot")
#> Loading required namespace: geodist
#> Loading required namespace: dplyr

from <- "10594643693"

v <- dodgr_vertices (graph)
tlim <- 120 * 1:2
x <- dodgr_isochrones(graph, from = from, tlim = tlim)
table (x$tlim)
#> 
#> 120 240 
#>  32  18
xc <- dodgr_isochrones(graph, from = from, tlim = tlim, convex = TRUE)
table (xc$tlim)
#> 
#> 120 240 
#>   8  11

one_sf_poly <- function (x, tlim) {
    res <- sfheaders::sf_point (x [x$tlim == tlim, c ("x", "y")]) |>
        sf::st_set_crs (4326) |>
        sf::st_combine ()
    sf::st_sf (geometry = sf::st_cast (res, "POLYGON"))
}
dat <- rbind (
    one_sf_poly (x, 120), one_sf_poly (x, 240),
    one_sf_poly (xc, 120), one_sf_poly (xc, 240)
)
plot (dat, col = "transparent", lwd = 1, lty = c (2, 2, 1, 1), border = c ("red", "red", "blue", "blue"))

Created on 2024-05-17 with reprex v2.1.0

@mpadge mpadge closed this as completed in 75d1005 May 17, 2024
mpadge added a commit that referenced this issue May 17, 2024
@mpadge
Copy link
Member

mpadge commented May 17, 2024

Re-opening, because the convex parameter alone still doesn't resolve this issue. The current PR #226 produces the following plot of convex hull isodistance contours for 3 dlim values:
image

Each successive value should entirely contain all prior ones, yet that clearly doesn't happen here. The whole iso routines might be better re-written to simply use all points (like current dodgr_isoverts() function), and to calculate hulls around those?

@mpadge mpadge reopened this May 17, 2024
mpadge added a commit that referenced this issue May 17, 2024
mpadge added a commit that referenced this issue May 22, 2024
mpadge added a commit that referenced this issue May 22, 2024
mpadge added a commit that referenced this issue May 22, 2024
mpadge added a commit that referenced this issue May 22, 2024
mpadge added a commit that referenced this issue May 22, 2024
mpadge added a commit that referenced this issue May 22, 2024
mpadge added a commit that referenced this issue May 22, 2024
mpadge added a commit that referenced this issue May 22, 2024
add concaveman src for iso calcs #223
@mpadge
Copy link
Member

mpadge commented May 24, 2024

Those commits finish implementation of new concavity parameter which enables arbitrarily concave polygonal hulls to be fitted to iso results:

library(dodgr)
library(osmdata)
library(mapview)
library(sf)
library(dplyr)

f <- "el.Rds"
if (!file.exists (f)) {
    dat <- opq("Espoo Leppävaara") %>%
        add_osm_feature(key = "highway") %>%
        osmdata_sc()
    saveRDS (dat, f)
} else {
    dat <- readRDS (f)
}
graph <- weight_streetnet(dat, wt_profile = "foot")

# Note the new concavity parameter:
x <- dodgr_isochrones(graph, from = "10594643693", tlim = c(150, 300, 600, 900, 1200), concavity = 0.5)

m <- x %>% 
    st_as_sf(coords = c("x", "y"), crs = 4326) %>% 
    group_by(tlim) %>% 
    summarise(do_union = FALSE) %>% 
    st_cast("POLYGON") %>%
    mapview ()

# Lines to plot mapview result as png:
map2img <- function (m) {
    fout = tempfile(fileext = ".png")
    withr::with_envvar (
        c ("CHROMOTE_CHROME" = "/usr/bin/brave"),
        mapshot2(m, file = fout)
    )
    png::readPNG(fout)
}
grid::grid.raster (map2img (m))

x2 <- dodgr_isochrones(graph, from = "10594643693", tlim = c(120), concavity = 0.5) 
m2 <- x2 %>% 
    st_as_sf(coords = c("x", "y"), crs = 4326) %>% 
    group_by(tlim) %>% 
    summarise(do_union = FALSE) %>% 
    st_cast("POLYGON") %>%
    mapview ()
grid::grid.raster (map2img (m2))

Created on 2024-05-24 with reprex v2.1.0

@mpadge mpadge closed this as completed May 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants