Skip to content

State-based geofencing zone enforcement for vehicle rental routing#7544

Draft
testower wants to merge 6 commits into
opentripplanner:dev-2.xfrom
entur:feat/vehicle-rental-geofencing
Draft

State-based geofencing zone enforcement for vehicle rental routing#7544
testower wants to merge 6 commits into
opentripplanner:dev-2.xfrom
entur:feat/vehicle-rental-geofencing

Conversation

@testower
Copy link
Copy Markdown
Contributor

@testower testower commented Apr 21, 2026

Summary

Implements state-based geofencing zone tracking with boundary-crossing vertices and an enforcement strategy pattern. Zone membership is tracked in routing State and updated at boundary crossings, enabling correct per-field precedence for overlapping zones as specified by GBFS v2.3+. Zone-type-specific routing decisions (fork, block, force drop-off) are dispatched through GeofencingEnforcement strategies, orchestrated by GeofencingInterceptor.

This PR also fixes a pre-existing bug in the dev-2.x edge-based implementation where station rentals hitting a geofencing zone were force-dropped mid-street, producing illegal itineraries. The enforcement is now station-rental-aware: forward direction blocks at no-traversal zones and business-area boundaries, passes through no-drop-off zones (where the restriction doesn't apply), and leaves arrive-by unchanged (the existing State.empty() checks handle committed states correctly).

Issue

Closes #7578

Unit tests

  • StreetEdgeGeofencingTest: 27 tests covering forward/arriveBy traversal through no-drop-off, no-traversal, and business area zones, including overlapping zones and adjacent zone edge cases
  • RestrictedZoneEnforcementTest: 14 tests for the restricted zone enforcement strategy in isolation using EdgeTraversal lambda (no real graph needed), including station-rental cases
  • BusinessAreaEnforcementTest: 11 tests for business-area enforcement including station-rental cases (exit blocked, boundary exit blocked, enter passes)
  • GeofencingInterceptorTest: 11 tests for the orchestrator dispatch logic, including station-rental pre-guard case
  • StateEditorGeofencingZoneTest: 9 tests for zone state tracking in StateEditor
  • StateGeofencingEnforcementTest: 10 tests for zone-based restriction queries on State
  • ScooterRentalGeofencingTest: 10 integration tests with real graph routing through geofencing zones
  • All existing vehicle rental tests continue to pass

Documentation

  • package.md added to the geofencing package documenting the interceptor/strategy architecture, boundary infrastructure, and zone state tracking
  • Javadoc on all public classes and methods
  • Design rationale for vertex-based boundary tracking (vs per-edge extensions) documented in the linked issue

Bumping the serialization version id

This PR changes graph serialization by adding geofencingBoundaries to Vertex and removing RentalRestrictionExtension fields from edges and vertices. The +Bump Serialization Id label should be added.

@testower testower force-pushed the feat/vehicle-rental-geofencing branch 3 times, most recently from 2fee8ef to 2877da0 Compare April 21, 2026 16:09
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 21, 2026

Codecov Report

❌ Patch coverage is 74.64607% with 197 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.58%. Comparing base (481d2ef) to head (a0d29b5).
⚠️ Report is 35 commits behind head on dev-2.x.

Files with missing lines Patch % Lines
...r/updater/vehicle_rental/VehicleRentalUpdater.java 6.06% 30 Missing and 1 partial ⚠️
...er/service/vehiclerental/model/GeofencingZone.java 64.40% 18 Missing and 3 partials ⚠️
...ental/street/geofencing/GeofencingInterceptor.java 79.77% 7 Missing and 11 partials ⚠️
...al/street/geofencing/NetworkCommitmentHandler.java 82.89% 3 Missing and 10 partials ⚠️
.../opentripplanner/routing/impl/GraphPathFinder.java 42.85% 11 Missing and 1 partial ⚠️
...erental/street/geofencing/DeferredForkHandler.java 84.50% 1 Missing and 10 partials ⚠️
...ental/street/geofencing/GeofencingZoneApplier.java 80.35% 6 Missing and 5 partials ⚠️
...opentripplanner/gbfs/GbfsGeofencingZoneMapper.java 71.42% 5 Missing and 5 partials ⚠️
...tal/street/geofencing/BusinessAreaEnforcement.java 76.19% 5 Missing and 5 partials ⚠️
...l/street/geofencing/RestrictedZoneEnforcement.java 82.45% 3 Missing and 7 partials ⚠️
... and 15 more
Additional details and impacted files
@@              Coverage Diff              @@
##             dev-2.x    #7544      +/-   ##
=============================================
+ Coverage      72.54%   72.58%   +0.04%     
- Complexity     21266    21407     +141     
=============================================
  Files           2380     2381       +1     
  Lines          86400    86862     +462     
  Branches        8486     8620     +134     
=============================================
+ Hits           62675    63051     +376     
- Misses         20728    20768      +40     
- Partials        2997     3043      +46     

☔ 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.

@testower testower force-pushed the feat/vehicle-rental-geofencing branch 5 times, most recently from bdb8a09 to 8e5fe6e Compare April 28, 2026 15:58
@testower testower force-pushed the feat/vehicle-rental-geofencing branch 4 times, most recently from 460f8e5 to a79222b Compare April 30, 2026 10:33
@testower testower marked this pull request as ready for review April 30, 2026 11:52
@testower testower requested a review from a team as a code owner April 30, 2026 11:52
@testower testower added the +Bump Serialization Id Add this label if you want the serialization id automatically bumped after merging the PR label Apr 30, 2026
@testower testower changed the title Reimplement vehicle rental geofencing Replace per-edge geofencing with state-based boundary tracking Apr 30, 2026
@testower testower force-pushed the feat/vehicle-rental-geofencing branch from a79222b to c99e5a8 Compare April 30, 2026 12:12
@testower testower marked this pull request as draft April 30, 2026 12:15
@testower testower force-pushed the feat/vehicle-rental-geofencing branch 11 times, most recently from b951c49 to e59cf6d Compare May 7, 2026 13:48
@testower testower force-pushed the feat/vehicle-rental-geofencing branch from e59cf6d to 2b3f993 Compare May 9, 2026 16:38
@testower testower changed the title Replace per-edge geofencing with state-based boundary tracking Replace per-edge geofencing with enforcement strategy pattern May 9, 2026
@testower testower force-pushed the feat/vehicle-rental-geofencing branch 2 times, most recently from 47ed737 to ca45088 Compare May 9, 2026 18:02
@testower testower changed the title Replace per-edge geofencing with enforcement strategy pattern State-based geofencing zone enforcement for vehicle rental routing May 9, 2026
@testower testower force-pushed the feat/vehicle-rental-geofencing branch 6 times, most recently from 1a11e3c to 2c3431d Compare May 12, 2026 10:07
testower added 5 commits May 12, 2026 13:12
…ofencing

Convert GeofencingZone from a record to a final class with nullable Boolean
restriction fields (dropOffBanned, traversalBanned, rideStartBanned), enabling
per-field precedence resolution across overlapping zones per the GBFS spec.

Add priority (from GBFS feature array position), explicit businessArea flag
(computed at mapping time instead of inferred from absence of restrictions),
vehicleTypeIds, and maximumSpeedKph fields. Custom equals/hashCode on
id+priority only, excluding expensive JTS Geometry comparison.

Add static resolveField() for per-field precedence: for each restriction field
independently, the highest-priority zone that specifies the field wins.

Update GBFS mappers (base, v2, v3) to support per-vehicle-type-scope grouping,
nullable field mapping, and priority assignment. Update GeofencingZoneExtension
and GeofencingZonesPropertyMapper for nullable Boolean API.

Add TestGeofencingZoneBuilder in test-fixtures for readable test zone
construction.
…cking

Add GeofencingBoundaryExtension record (zone, entering) for marking
boundary-crossing vertices. Add GeofencingZoneIndex (STRtree spatial index
with PreparedGeometry.covers()) for efficient containment queries.

Add GeofencingZoneApplier replacing GeofencingVertexUpdater. Detects
boundary-crossing edges via vertex containment with caching, places
paired boundary extensions on both vertices with opposite entering flags.
Retains BusinessAreaBorder placement for business area zones.

Add geofencing zone index storage on Graph with per-datasource registration.
Add initialGeofencingZones on VehicleRentalPlaceVertex, pre-resolved from
spatial index at updater time. Add geofencing boundary fields and methods
on Vertex alongside the existing RentalRestrictionExtension system.

Update BusinessAreaBorder to support multiple networks (Set<String>).
Update VertexLinker to compute geofencing boundaries spatially for split
vertices instead of blind-copying from parent vertices.

Update VehicleRentalUpdater to use GeofencingZoneApplier with zone
change detection via isEquivalentTo() deep comparison. Add factory
methods on StreetVehicleRentalLink and VehicleRentalEdge.
…cing

Add currentGeofencingZones and committedNetworks to StateData, with
boundary transition logic in StateEditor.updateGeofencingZones() using
paired boundary detection and XOR trick for arriveBy direction.

Per-field enforcement methods on State (isDropOffBannedByCurrentZones,
isTraversalBannedByCurrentZones) resolve restrictions via priority-based
precedence. VehicleRentalEdge initializes zones on pickup and checks
committedNetworks for generic state blocking. StreetEdge.doTraverse
calls updateGeofencingZones at boundary edges.

Routing request plumbing precomputes destination zones for arriveBy
searches via GeofencingZoneIndex, propagated through GraphPathFinder.
Delete superseded GeofencingVertexUpdater (replaced by GeofencingZoneApplier).
…specific routing decisions

Replace the 220-line StreetEdge geofencing if-chain with a three-layer
architecture using the Strategy pattern:

- Layer 0: Zone state tracking (StateEditor.updateGeofencingZones, from commit 3)
- Layer 1: Network commitment for generic arriveBy states
- Layer 2: GeofencingEnforcement strategy per zone type

New enforcement implementations:
- RestrictedZoneEnforcement: no-drop-off and no-traversal zones (fork, block, drop)
- BusinessAreaEnforcement: inverted logic (exit = restrict)
- DeferredForkHandler: backState zone exit detection for deferred renting branches

The traversal skeleton iterates paired boundary extensions on fromv,
delegates to the appropriate enforcement via GeofencingEnforcement.forZone(),
and handles network commitment for generic states separately.

Delete old per-edge geofencing system: RentalRestrictionExtension,
GeofencingZoneExtension, NoRestriction, CompositeRentalRestrictionExtension,
BusinessAreaBorder, and all associated Vertex/State/StreetEdge methods.
Wire up the existing applyBusinessAreas flag in GeofencingZoneApplier to
a per-source configuration parameter. When disabled, boundary extensions
are not created for business-area-only zones, suppressing drop-off
enforcement at their boundary while keeping the zones in the index for
state tracking, speed limits, and debug tiles.

Defaults to true for backward compatibility.
@testower testower force-pushed the feat/vehicle-rental-geofencing branch from 2c3431d to b6ada49 Compare May 12, 2026 11:15
…dropping

The geofencing enforcement gated on isRentingVehicle() (covers both station
and floating rentals) but every drop branch called dropFloatingVehicle(),
which would synthesize a HAVE_RENTED state at a non-station vertex. For
station rentals this is illegal — the rider must return the vehicle to a
same-network station.

Add station-rental-aware branches in the forward enforcement and the
bidirectional pre-guard:

- Pre-guard inside a no-traversal zone: block (not force-drop)
- Forward entering a no-traversal zone: block
- Forward entering a no-drop-off zone: pass through (restriction irrelevant
  for station rentals — they never drop mid-street)
- Forward exiting a business area: block (both at-boundary and fallback paths)
- Forward entering a business area: pass through (unchanged)

Arrive-by remains correct via existing State.empty() checks for committed
renting states; phantom RENTING_FLOATING branches created by the deferred
fork for station-only networks self-correct by failing at pickup.
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

State-based geofencing zone tracking with per-field precedence

1 participant