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.

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