Skip to content

Conversation

@mattwigway
Copy link
Contributor

@mattwigway mattwigway commented Sep 14, 2025

This is a first implementation of the isochrone generation process discussed in #495. In a nutshell, the current isochrone generation code works by taking a random sample of points from the network (by default 80%), computing travel times to those points, and then taking a concave hull of the reachable subset of those points. This has several drawbacks:

  1. It only uses information about what points are reachable to build the isochrone, and does not use information about what points are not (i.e. it cannot differentiate an unreachable location from a location with no roads). The concave hull algorithm is a heuristic that tries to figure out the shape of the reachable area, but it won't handle reachable areas that have e.g. holes in them, and if there are "tentacles" (e.g. bus lines reaching out from a central hub), the algorithm may fill in the areas between them if they are close to one another.
  2. Because it uses a random sample of the points, results may vary, and the local resolution of the isochrone is also very affected by the network density in an area.
  3. It is slow (I suspect due to the concave hull algorithm but I haven't benchmarked it).

In this pull request, I switch to computing travel times to a regular grid of points across the network (specifically a grid of Web Mercator pixels). Then I use the marching squares algorithm (as provided by the {isoband} package) to get the isochrones. This is the same approach taken by Conveyal.

Marching squares will ensure the isochrone line is always between a cell that is reachable and one that is not, so holes, tentacles, etc. will all be properly handled. For instance, here's a comparison of the isochrone generated by the current implementation and the new implementation, starting from the central bus station in Raleigh, NC (new/marching squares in blue, current/concave hull in yellow):

image

The current isochrone fills in a lot of unreachable areas between bus lines, whereas the new isochrone correctly gathers that these areas are inaccessible. It's a lot faster too; the current isochrone takes 41 seconds, whereas the new one takes 6.3 seconds for the first isochrone computed and 0.3s/isochrone after that (R5 caches the regular grid definition and linkage to the network after the first isochrone).

The new algorithm has one parameter, zoom, that controls the level of detail of the isochrones at the expense of computation. The zoom parameter can be set from 9 to 12 and controls the size of the regular grid; details are here. I have set the default to zoom 10. In my test network, R5 refuses to use zoom 12 because the area is too large. Currently the algorithm assumes the isochrone may reach to the full extent of the network; we could allow the user to specify a smaller maximum extent and thus use a finer grid over a smaller area. Isochrones from three of the zoom levels are below:

image

I'm creating this as a draft pull request so folks can start to give feedback on whether this is a desirable change and how to go about it. It needs a lot more testing as this ended up touching a lot of r5r code. Since the travel time grid process needs to return a matrix rather than an RDataFrame, I made R5Process a generic type so subclasses can return any value, and then have an extending R5DataFrameProcess that contains dataframe-specific features - which means that the codepath for every single analysis function has changed somewhat. I've also completely replaced the polygon isochrone generation code (though retained the line isochrone code)—I think it's desirable to do a complete replacement but a parallel implementation could also be considered.

@rafapereirabr
Copy link
Member

Hi @mattwigway , thanks for this PR. On a quick look, this sounds really promising! I'm currently on a vacation trip, so I will only have time to have a closer look at this when I return in October.

ps. did you see this error message from CMD check ?

Error in rJava::.jcall():
! method travelTimeSurfaces with signature (Ljava/lang/String;DDLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IIIII)[Lorg/ipea/r5r/RegularGridResult; not found

@mattwigway
Copy link
Contributor Author

That's interesting as it works locally, I'll take a look. Enjoy your vacation!

@mattwigway
Copy link
Contributor Author

I think the R CMD check error is due to the JAR not being rebuilt in the repo. I thought that was supposed to happen automatically, but it seems like it didn't in this case.

@mattwigway
Copy link
Contributor Author

I rebased and fixed the tests for this branch. @rafapereirabr have you had a chance to review?

@rafapereirabr
Copy link
Member

rafapereirabr commented Nov 21, 2025

Hi @mattwigway , thanks for making so much progress with the new isochrone function. I was reviewing your code and eveything looks fine. However, you worked on an old fork that was using R5 v6.4 with JDK 11, and we are currently using R5 v7.4 with JDK 21. Have you tried running it with this config to make sure it runs without any errors ? I guess the automatic CMD-checks below tests for this but I just wanted to double check

@rafapereirabr
Copy link
Member

Ok, I've managed to run it locally and it is sooo much better. Thanks so much, @mattwigway ! I'll merge the PR now and adjust the vignette asap

@rafapereirabr rafapereirabr marked this pull request as ready for review November 21, 2025 01:28
@rafapereirabr rafapereirabr merged commit 499d78c into ipeaGIT:master Nov 21, 2025
9 checks passed
@mattwigway
Copy link
Contributor Author

@rafapereirabr I think the vignette should already be updated unless I missed something!

I haven't tested this with fare structures or scenarios fwiw.

@rafapereirabr
Copy link
Member

ah, no worries. At the moment, we do not expose any fare structures or scenario options in the isochrone function.

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

Successfully merging this pull request may close these issues.

2 participants