Skip to content

Optimize routing time vertex linking with areas #6495

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

Merged
merged 50 commits into from
Mar 17, 2025

Conversation

vesameskanen
Copy link
Contributor

@vesameskanen vesameskanen commented Feb 26, 2025

Summary

Several optimizations:

  • An expanded (more complex) version of the area polygon is no longer used
  • Vertex is not snapped to the expanded polygon edge, but original location is used
  • Too high visibility vertex count is reduced during the graph building by taking vertices with most connections. There is usually no need to apply skip logic while routing. N^2 type slowdown by area complexity is compensated by applying less visibility connections to closest vertices when complexity grows over a defined limit.
  • Some unnecessary intersection tests and geometry conversions removed from vertex linker
  • Linker checks if area linking is already done before the time consuming point in polygon test
  • Linker reuses the created street vertices inside areas, which reduces edge count a lot
  • Linker uses two strategies to connect a vertex to an area: the visibility edges and connection to closest edge pair
  • Very bad linking fallback by ignoring all boundaries and connecting to all visibility vertices removed. Only one connection to the closest vertex is used in the highly unprobable case where both strategies fail.
  • Hard coded visibility vertex limit is now defined in a single place, StreetConstants.java. It used both in graph building and routing time linking. Default visibility vertex count increased from 150 to 200.

The notorious areas of Lund, Sweden now route fast. Some detours along area edges may occur when a visibility connection is not available, but such paths are mostly reasonable.

Unit tests

Existing tests updated to reflect new linking. Three new tests added.

Documentation

Related javadoc and comments updated.

Vertexlinker should not create an expanded area polygon whenever itinerary search
starts/ends in an area. Buffer operation for a complex geometry is obviously very slow.
Precompute the expansion.
Keep the visibility points which have the best connectivity.
A test breaks if the limit is taken literally
This eliminates the need for several point inside polygon tests.
Copy link

codecov bot commented Feb 26, 2025

Codecov Report

Attention: Patch coverage is 83.33333% with 27 lines in your changes missing coverage. Please review.

Project coverage is 70.25%. Comparing base (532da22) to head (9ee7de6).
Report is 139 commits behind head on dev-2.x.

✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
.../opentripplanner/routing/linking/VertexLinker.java 81.88% 18 Missing and 7 partials ⚠️
...n/java/org/opentripplanner/standalone/OTPMain.java 0.00% 1 Missing ⚠️
...anner/street/model/StreetLimitationParameters.java 75.00% 1 Missing ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##             dev-2.x    #6495      +/-   ##
=============================================
+ Coverage      70.18%   70.25%   +0.07%     
- Complexity     18320    18386      +66     
=============================================
  Files           2084     2087       +3     
  Lines          77278    77429     +151     
  Branches        7834     7843       +9     
=============================================
+ Hits           54235    54396     +161     
+ Misses         20272    20262      -10     
  Partials        2771     2771              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

There is no apparent reason for using it. Expanded geomerty typically
has 4 times more points which slows down computations a lot.
Very complex areas have many visibility points. Computation of
visibility edge intersections with are edges therefore eventually becomes
too slow. Now the linker creates less visibility edges when the area gets
more complex. Closest visibility points are used for connections. Probablility
that a random visibility points far away could be connected are low, so this
is a fair heuristics to speed up linking.
VertexLinker used a hard coded value to limit routing time area linking.
Configured maxAreaNodes expresses better the current hardware and
service resource limitations.
New optimizations speed up area linking. Increase the default value accordingly.
…exing

The value will be used in street routing.
Area linking connected vertex to all visibility vertices, ignoring
area boundaries such as buildings, when all attempted connections
were blocked. Undesired fallback behavior removed.
- Divide linking code into smaller reusable functions
- Separate edge snapping logic from area linking
- Optimize parameters and geometry conversions
- Add more documentation, remove outdated comments
Visibility vertices are optimized for passing through areas,
They do not always provide an optimal path from a random area point.
It is not wise to overload same name with different functionality
- Remember created area interior vertices and reuse them to simplify the graph
- Snap added area interior vertices to all closest edge candidates
- Fallback when both visibility edges and connections to closest edges fail
@vesameskanen vesameskanen marked this pull request as ready for review March 6, 2025 12:15
* but visibility edges which would cross the area boundary are not added
*/
@Test
void testLinkStopToConcaveArea() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😍 Great!

// If more than one area intersects, we pick one by random for the name & properties
// we already know that the edge is inside the areaGroup and hit != null, but test anyway
if (hit == null) {
LOG.warn("No intersecting area found. This indicates a bug.");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm seeing this log message when linking transit stops. I guess that means that something is wrong? If you want I can extract a dataset to reproduce the issue?

[...]
11:20:13.702 INFO [main]  (Graph.java:288) Index street model complete.
11:20:21.391 INFO [main]  (OsmBoardingLocationsModule.java:116) Found 0 OSM references which match a stop's id or code
11:20:21.392 INFO [main]  (TimetableRepository.java:159) Index timetable repository...
11:20:21.392 INFO [main]  (TimetableRepositoryIndex.java:63) Timetable repository index init...
11:20:22.150 INFO [main]  (TimetableRepositoryIndex.java:117) Timetable repository index init complete.
11:20:22.150 INFO [main]  (TimetableRepository.java:161) Index timetable repository complete.
11:20:22.150 INFO [main]  (Graph.java:286) Index street model...
11:20:22.587 INFO [main]  (StreetIndex.java:426) Index street vertex progress tracking started.
11:20:27.617 INFO [main]  (StreetIndex.java:437) Index street vertex progress: 1,078,000 of 2,413,678 (44%)
11:20:32.620 INFO [main]  (StreetIndex.java:437) Index street vertex progress: 2,270,000 of 2,413,678 (94%)
11:20:33.348 INFO [main]  (StreetIndex.java:443) Index street vertex progress tracking complete. 2,413,678 done in 10s (224,278 per second).
11:20:33.349 INFO [main]  (Graph.java:288) Index street model complete.
11:20:33.578 INFO [main]  (StreetLinkerModule.java:95) Linking transit stops to graph progress tracking started.
11:20:34.326 WARN [main]  (VertexLinker.java:729) No intersecting area found. This indicates a bug.
11:20:34.334 WARN [main]  (VertexLinker.java:729) No intersecting area found. This indicates a bug.
11:20:35.558 WARN [main]  (VertexLinker.java:729) No intersecting area found. This indicates a bug.
11:20:35.569 WARN [main]  (VertexLinker.java:729) No intersecting area found. This indicates a bug.
11:20:35.753 WARN [main]  (VertexLinker.java:729) No intersecting area found. This indicates a bug.
11:20:35.754 WARN [main]  (VertexLinker.java:729) No intersecting area found. This indicates a bug.
11:20:35.795 WARN [main]  (VertexLinker.java:729) No intersecting area found. This indicates a bug.
[...]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was such a good idea to add the warning. This is probably an accuracy issue which appears because I removed the polygon buffering step.

Thanks for a good catch, I will fix the error!

Define thresholds as degrees in the class data.
Linker tried to connect two overlapping split vertices at the area
boundary. This is not necessary.
Leaving e.g. a transit stop unlinked due to boundary accuracy
issues is a bad bug.
@vesameskanen
Copy link
Contributor Author

vesameskanen commented Mar 14, 2025

It turned out that linking failed when a stop was very close to an area boundary. Code created very short (or zero length) connecting edges, which could be seen in the graph build log.

Now the collapsed case it catched. This actually makes the graph simpler. Also the case when one link connection is forced for any unexpected accuracy reason is now handled cracefully instead of rejecting the linking. HSL data set never hits this fallback, though.

Latest changes also optimize some unnecessary conversions of threshold constanst from meters to degrees during routing.

One new unit tests added to ensure that collapsed edges are not created.

@vesameskanen
Copy link
Contributor Author

Walk routing between two complex areas in lund (using default visibility node counts) was before over 600 ms, and is now about 100 ms. Speedup is more than 5 times.

public static List<Itinerary> route(OtpServerRequestContext serverContext, RouteRequest request) {
if (request.journey().direct().mode() == StreetMode.NOT_SET) {
return Collections.emptyList();
}
OTPRequestTimeoutException.checkForTimeout();

long searchBeginTime = System.currentTimeMillis();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work. This works fine for all cases I've tested and the speedup is big.

@leonardehrenfried leonardehrenfried added the bump serialization id Add this label if you want the serialization id automatically bumped after merging the PR label Mar 17, 2025
@vesameskanen vesameskanen merged commit 34f4bf6 into opentripplanner:dev-2.x Mar 17, 2025
6 checks passed
@vesameskanen vesameskanen deleted the optimize-area-linking branch March 17, 2025 15:06
t2gran pushed a commit that referenced this pull request Mar 17, 2025
t2gran pushed a commit that referenced this pull request Mar 17, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bump serialization id Add this label if you want the serialization id automatically bumped after merging the PR Improvement A functional improvement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants