From 6772257a9df024346ef85951e24ef754e9d77dc8 Mon Sep 17 00:00:00 2001 From: dzmitryfomchyn Date: Fri, 18 Apr 2025 19:23:33 +0200 Subject: [PATCH] Support for StepIntersection.formOfWays and StepIntersection.geometries --- CHANGELOG.md | 3 + .../directions/v5/models/RouteOptions.java | 101 ++++++++++++++++++ .../v5/models/StepIntersection.java | 77 +++++++++++++ .../v5/models/DirectionsRouteTest.java | 51 +++++++++ .../v5/models/RouteOptionsTest.java | 30 +++++- .../v5/models/StepIntersectionTest.java | 77 +++++++++++++ ...rections_v5_with_toll_costs_and_lanes.json | 23 +++- .../src/test/resources/route_options_v5.json | 6 +- 8 files changed, 363 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e35c5bcfd..4939d1306 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ Mapbox welcomes participation and contributions from everyone. ### main +- Added `StepIntersection#formOfWays` property which provides a list representing the "form of way" values for all roads at the step intersection. [#1611](https://github.com/mapbox/mapbox-java/pull/1611) +- Added `StepIntersection#geometries` property which provides a list representing geometry for all roads at the step intersection. [#1611](https://github.com/mapbox/mapbox-java/pull/1611) + ### v7.4.0 - April 11, 2025 - Added `IntersectionLanes#access` property which provides lane access attributes, such as allowed vehicle types for designated lanes. [#1608](https://github.com/mapbox/mapbox-java/pull/1608) diff --git a/services-directions-models/src/main/java/com/mapbox/api/directions/v5/models/RouteOptions.java b/services-directions-models/src/main/java/com/mapbox/api/directions/v5/models/RouteOptions.java index a16746293..7d6e77310 100644 --- a/services-directions-models/src/main/java/com/mapbox/api/directions/v5/models/RouteOptions.java +++ b/services-directions-models/src/main/java/com/mapbox/api/directions/v5/models/RouteOptions.java @@ -946,6 +946,40 @@ public List paymentMethodsList() { @Nullable public abstract Boolean suppressVoiceInstructionLocalNames(); + /** + * Defines whether to return "form of way" values for roads at each {@link StepIntersection}. + * See {@link StepIntersection#formOfWays()} for details. + * + * @return boolean representing the `intersectionLinkFormOfWay` value + */ + @SerializedName("intersection_link_form_of_way") + @Nullable + public abstract Boolean intersectionLinkFormOfWay(); + + /** + * A comma-separated list of road types for which intersection geometry data should be included + * at each {@link StepIntersection}. See {@link StepIntersection#geometries()} for details. + * Possible values include: + * - "motorway" + * - "trunk" + * - "primary" + * - "secondary" + * - "tertiary" + * - "unclassified" + * - "residential" + * - "service_other" + * Geometry data will be provided for each intersection along the requested route. + * Be aware that enabling this option can significantly increase the response size + * and the memory required to store the response object. Use this option selectively, for example, + * if geometry is only needed for motorways, specify only "motorway". + * + * @return a comma-separated list of road types for which intersection geometry data + * should be included + */ + @SerializedName("intersection_link_geometry") + @Nullable + public abstract String intersectionLinkGeometry(); + /** * Gson type adapter for parsing Gson to this class. * @@ -1088,6 +1122,8 @@ public URL toUrl(@NonNull String accessToken) { "suppress_voice_instruction_local_names", suppressVoiceInstructionLocalNames() ); + appendQueryParameter(sb, "intersection_link_form_of_way", intersectionLinkFormOfWay()); + appendQueryParameter(sb, "intersection_link_geometry", intersectionLinkGeometry()); Map unrecognized = unrecognized(); if (unrecognized != null) { @@ -2127,6 +2163,71 @@ public abstract Builder suppressVoiceInstructionLocalNames( @Nullable Boolean suppressVoiceInstructionLocalNames ); + /** + * Defines whether to return "form of way" values for roads at each {@link StepIntersection}. + * See {@link StepIntersection#formOfWays()} for details. + * + * @param intersectionLinkFormOfWay whether to return "form of way" values + * @return this builder + */ + @NonNull + public abstract Builder intersectionLinkFormOfWay( + @Nullable Boolean intersectionLinkFormOfWay + ); + + /** + * A comma-separated list of road types for which intersection geometry data should be included + * at each {@link StepIntersection}. See {@link StepIntersection#geometries()} for details. + * Possible values include: + * - "motorway" + * - "trunk" + * - "primary" + * - "secondary" + * - "tertiary" + * - "unclassified" + * - "residential" + * - "service_other" + * Geometry data will be provided for each intersection along the requested route. + * Be aware that enabling this option can significantly increase the response size + * and the memory required to store the response object. Use this option selectively, + * for example, if geometry is only needed for motorways, specify only "motorway". + * + * @param intersectionLinkGeometry a comma-separated list of road types for which intersection + * geometry data should be included + * @return this builder + */ + @NonNull + public abstract Builder intersectionLinkGeometry( + @Nullable String intersectionLinkGeometry + ); + + /** + * A list of road types for which intersection geometry data should be included + * at each {@link StepIntersection}. See {@link StepIntersection#geometries()} for details. + * Possible values include: + * - "motorway" + * - "trunk" + * - "primary" + * - "secondary" + * - "tertiary" + * - "unclassified" + * - "residential" + * - "service_other" + * Geometry data will be provided for each intersection along the requested route. + * Be aware that enabling this option can significantly increase the response size + * and the memory required to store the response object. Use this option selectively, + * for example, if geometry is only needed for motorways, specify only "motorway". + * + * @param intersectionLinkGeometry a list of road types for which intersection geometry data + * should be included + * @return this builder + */ + @NonNull + public Builder intersectionLinkGeometry(@Nullable List intersectionLinkGeometry) { + String result = FormatUtils.join(",", intersectionLinkGeometry); + return intersectionLinkGeometry(result); + } + /** * Use this method to add request parameters, * which are not present in the model yet but are supported on the Directions API, diff --git a/services-directions-models/src/main/java/com/mapbox/api/directions/v5/models/StepIntersection.java b/services-directions-models/src/main/java/com/mapbox/api/directions/v5/models/StepIntersection.java index e67f396ce..78a326562 100644 --- a/services-directions-models/src/main/java/com/mapbox/api/directions/v5/models/StepIntersection.java +++ b/services-directions-models/src/main/java/com/mapbox/api/directions/v5/models/StepIntersection.java @@ -92,6 +92,44 @@ public Point location() { @Nullable public abstract List entry(); + /** + * A list representing the "form of way" values for all roads at the step intersection. + * This list has a 1:1 correspondence with the {@link #bearings()}, {@link #entry()}, + * and {@link #geometries()} lists. + * Each element is either: + * - A list of strings representing the form(s) of way for a specific road, or + * - `null` if the form of way is unknown. + * Possible values for the form of way include: + * - "ramp" + * - "elevated" + * - "roundabout" + * - "service_road" + * Example: + * "form_of_way": [ + * ["ramp", "elevated"], + * ["ramp"], + * null + * ] + * + * @return A list of form of way values for all roads at the step intersection + */ + @Nullable + @SerializedName("form_of_way") + public abstract List> formOfWays(); + + /** + * A list representing geometry for all roads at the step intersection. + * This list has a 1:1 correspondence with the {@link #bearings()}, {@link #entry()}, + * and {@link #formOfWays()} lists. + * Each element is either: + * - A strings representing geometry of a specific road, or + * - `null` if the geometry is unknown. + * @return A list of geometry for all roads at the step intersection + */ + @Nullable + @SerializedName("geometries") + public abstract List geometries(); + /** * Index into bearings/entry array. Used to calculate the bearing before the turn. Namely, the * clockwise angle from true north to the direction of travel before the maneuver/passing the @@ -360,6 +398,45 @@ public abstract static class Builder extends DirectionsJsonObject.Builder entry); + /** + * A list representing the "form of way" values for all roads at the step intersection. + * This list has a 1:1 correspondence with the {@link #bearings()}, {@link #entry()}, + * and {@link #geometries()} lists. + * Each element is either: + * - A list of strings representing the form(s) of way for a specific road, or + * - `null` if the form of way is unknown. + * Possible values for the form of way include: + * - "ramp" + * - "elevated" + * - "roundabout" + * - "service_road" + * Example: + * "form_of_way": [ + * ["ramp", "elevated"], + * ["ramp"], + * null + * ] + * + * @param formOfWays a list of form of way values for all roads at the step intersection + * @return this builder for chaining options together + */ + @NonNull + public abstract Builder formOfWays(List> formOfWays); + + /** + * A list representing geometry for all roads at the step intersection. + * This list has a 1:1 correspondence with the {@link #bearings()}, {@link #entry()}, + * and {@link #formOfWays()} lists. + * Each element is either: + * - A strings representing geometry of a specific road, or + * - `null` if the geometry is unknown. + * + * @param geometries a list of geometry for all roads at the step intersection + * @return this builder for chaining options together + */ + @NonNull + public abstract Builder geometries(List geometries); + /** * Index into bearings/entry array. Used to calculate the bearing before the turn. Namely, the * clockwise angle from true north to the direction of travel before the maneuver/passing the diff --git a/services-directions-models/src/test/java/com/mapbox/api/directions/v5/models/DirectionsRouteTest.java b/services-directions-models/src/test/java/com/mapbox/api/directions/v5/models/DirectionsRouteTest.java index 6ad866760..b0b7c903b 100644 --- a/services-directions-models/src/test/java/com/mapbox/api/directions/v5/models/DirectionsRouteTest.java +++ b/services-directions-models/src/test/java/com/mapbox/api/directions/v5/models/DirectionsRouteTest.java @@ -10,6 +10,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import org.hamcrest.junit.ExpectedException; @@ -261,4 +262,54 @@ public void directionsRoute_hasLaneAttributes() throws IOException { .build(); assertEquals(access, lane.access()); } + + @Test + public void directionsRoute_hasFormOfWays() throws IOException { + String json = loadJsonFixture("directions_v5_with_toll_costs_and_lanes.json"); + RouteOptions options = RouteOptions.builder() + .profile(DirectionsCriteria.PROFILE_DRIVING_TRAFFIC) + .coordinatesList(new ArrayList() {{ + add(Point.fromLngLat(1.0, 1.0)); + add(Point.fromLngLat(2.0, 2.0)); + }}) + .build(); + String uuid = "123"; + DirectionsRoute route = DirectionsRoute.fromJson(json, options, uuid); + + final List> formOfWays = route.legs().get(0).steps().get(0).intersections().get(0) + .formOfWays(); + + final List> expectedFormOfWays = Arrays.asList( + Collections.singletonList("ramp"), + Arrays.asList("ramp", "elevated"), + null + ); + + assertEquals(expectedFormOfWays, formOfWays); + } + + @Test + public void directionsRoute_hasGeometries() throws IOException { + String json = loadJsonFixture("directions_v5_with_toll_costs_and_lanes.json"); + RouteOptions options = RouteOptions.builder() + .profile(DirectionsCriteria.PROFILE_DRIVING_TRAFFIC) + .coordinatesList(new ArrayList() {{ + add(Point.fromLngLat(1.0, 1.0)); + add(Point.fromLngLat(2.0, 2.0)); + }}) + .build(); + String uuid = "123"; + DirectionsRoute route = DirectionsRoute.fromJson(json, options, uuid); + + final List geometries = route.legs().get(0).steps().get(0).intersections().get(0) + .geometries(); + + final List expectedGeometries = Arrays.asList( + "k}fiyAcxhgOjCRrD?rDS~CSrDSbQ{@rIg@nFSfES~HSjMSrNRjMz@jHz@jMjCnK~CzJrD~MvG", + null, + null + ); + + assertEquals(expectedGeometries, geometries); + } } diff --git a/services-directions-models/src/test/java/com/mapbox/api/directions/v5/models/RouteOptionsTest.java b/services-directions-models/src/test/java/com/mapbox/api/directions/v5/models/RouteOptionsTest.java index 4f2ba022c..7272662b3 100644 --- a/services-directions-models/src/test/java/com/mapbox/api/directions/v5/models/RouteOptionsTest.java +++ b/services-directions-models/src/test/java/com/mapbox/api/directions/v5/models/RouteOptionsTest.java @@ -38,7 +38,7 @@ public class RouteOptionsTest extends TestUtils { */ private static final String ROUTE_OPTIONS_JSON = "route_options_v5.json"; private static final String ROUTE_OPTIONS_URL = - "https://api.mapbox.com/directions/v5/mapbox/driving/-122.4003312,37.7736941;-122.4187529,37.7689715;-122.4255172,37.7775835?access_token=pk.token&geometries=polyline6&alternatives=false&overview=full&radiuses=%3Bunlimited%3B5.1&steps=true&avoid_maneuver_radius=200.0&bearings=0%2C90%3B90%2C0%3B&layers=-42%3B%3B0&continue_straight=false&annotations=congestion%2Cdistance%2Cduration&language=ru&roundabout_exits=false&voice_instructions=true&banner_instructions=true&voice_units=metric&exclude=toll%2Cferry%2Cpoint%2811.0+-22.0%29&include=hot%2Chov2&approaches=%3Bcurb%3B&waypoints=0%3B1%3B2&waypoint_names=%3BSerangoon+Garden+Market+%26+Food+Centre%3BFunky+%26nAmE*&waypoint_targets=%3B12.2%2C21.2%3B&enable_refresh=true&walking_speed=5.11&walkway_bias=-0.2&alley_bias=0.75&snapping_include_closures=%3Bfalse%3Btrue&snapping_include_static_closures=true%3B%3Bfalse&arrive_by=2021-01-01%27T%2701%3A01&depart_at=2021-02-02%27T%2702%3A02&max_height=1.5&max_width=1.4&max_weight=2.9&compute_toll_cost=true&waypoints_per_route=true&metadata=true&payment_methods=general&suppress_voice_instruction_local_names=true"; + "https://api.mapbox.com/directions/v5/mapbox/driving/-122.4003312,37.7736941;-122.4187529,37.7689715;-122.4255172,37.7775835?access_token=pk.token&geometries=polyline6&alternatives=false&overview=full&radiuses=%3Bunlimited%3B5.1&steps=true&avoid_maneuver_radius=200.0&bearings=0%2C90%3B90%2C0%3B&layers=-42%3B%3B0&continue_straight=false&annotations=congestion%2Cdistance%2Cduration&language=ru&roundabout_exits=false&voice_instructions=true&banner_instructions=true&voice_units=metric&exclude=toll%2Cferry%2Cpoint%2811.0+-22.0%29&include=hot%2Chov2&approaches=%3Bcurb%3B&waypoints=0%3B1%3B2&waypoint_names=%3BSerangoon+Garden+Market+%26+Food+Centre%3BFunky+%26nAmE*&waypoint_targets=%3B12.2%2C21.2%3B&enable_refresh=true&walking_speed=5.11&walkway_bias=-0.2&alley_bias=0.75&snapping_include_closures=%3Bfalse%3Btrue&snapping_include_static_closures=true%3B%3Bfalse&arrive_by=2021-01-01%27T%2701%3A01&depart_at=2021-02-02%27T%2702%3A02&max_height=1.5&max_width=1.4&max_weight=2.9&compute_toll_cost=true&waypoints_per_route=true&metadata=true&payment_methods=general&suppress_voice_instruction_local_names=true&intersection_link_form_of_way=true&intersection_link_geometry=motorway%2Ctrunk%2Cprimary"; private static final String ACCESS_TOKEN = "pk.token"; private final String optionsJson = loadJsonFixture(ROUTE_OPTIONS_JSON); @@ -364,6 +364,18 @@ public void waypointsPerRouteAreValid_fromJson() { assertEquals(true, routeOptions.waypointsPerRoute()); } + @Test + public void intersectionLinkGeometryAreValid_fromJson() { + RouteOptions routeOptions = RouteOptions.fromJson(optionsJson); + assertEquals("motorway,trunk,primary", routeOptions.intersectionLinkGeometry()); + } + + @Test + public void intersectionLinkFormOfWayAreValid_fromJson() { + RouteOptions routeOptions = RouteOptions.fromJson(optionsJson); + assertEquals(true, routeOptions.intersectionLinkFormOfWay()); + } + @Test public void defaultTollCost() { RouteOptions options = defaultRouteOptions(); @@ -385,6 +397,18 @@ public void defaultWaypointsPerRoute() { assertNull(options.waypointsPerRoute()); } + @Test + public void defaultIntersectionLinkFormOfWay() { + RouteOptions options = defaultRouteOptions(); + assertNull(options.intersectionLinkFormOfWay()); + } + + @Test + public void defaultIntersectionLinkGeometry() { + RouteOptions options = defaultRouteOptions(); + assertNull(options.intersectionLinkGeometry()); + } + @Test public void routeOptions_toJson() { RouteOptions options = routeOptions(); @@ -1128,6 +1152,8 @@ private RouteOptions routeOptions() { .waypointsPerRoute(true) .paymentMethods(DirectionsCriteria.PAYMENT_METHOD_GENERAL) .suppressVoiceInstructionLocalNames(true) + .intersectionLinkFormOfWay(true) + .intersectionLinkGeometry("motorway,trunk,primary") .build(); } @@ -1237,6 +1263,8 @@ private RouteOptions routeOptionsList() { .waypointsPerRoute(true) .suppressVoiceInstructionLocalNames(true) .paymentMethodsList(Arrays.asList(DirectionsCriteria.PAYMENT_METHOD_GENERAL)) + .intersectionLinkFormOfWay(true) + .intersectionLinkGeometry(Arrays.asList("motorway", "trunk", "primary")) .build(); } } diff --git a/services-directions-models/src/test/java/com/mapbox/api/directions/v5/models/StepIntersectionTest.java b/services-directions-models/src/test/java/com/mapbox/api/directions/v5/models/StepIntersectionTest.java index 4d1504c84..f5f1e3e78 100644 --- a/services-directions-models/src/test/java/com/mapbox/api/directions/v5/models/StepIntersectionTest.java +++ b/services-directions-models/src/test/java/com/mapbox/api/directions/v5/models/StepIntersectionTest.java @@ -1,6 +1,7 @@ package com.mapbox.api.directions.v5.models; import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertNull; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -11,6 +12,8 @@ import org.junit.Test; import java.util.Arrays; +import java.util.Collections; +import java.util.List; public class StepIntersectionTest extends TestUtils { @@ -246,4 +249,78 @@ public void testNullMergingArea() { String jsonStr = stepIntersection.toJson(); compareJson(stepIntersectionJsonString, jsonStr); } + + @Test + public void testFormOfWays() { + String stepIntersectionJsonString = "{" + + "\"location\": [ 13.426579, 52.508068 ]," + + "\"form_of_way\": [\n" + + " [\n" + + " \"ramp\",\"elevated\"\n" + + " ],\n" + + " [\n" + + " \"ramp\"\n" + + " ],\n" + + " null\n" + + "]" + + "}"; + + StepIntersection stepIntersection = StepIntersection.fromJson(stepIntersectionJsonString); + + List> formOfWays = Arrays.asList( + Arrays.asList("ramp", "elevated"), + Collections.singletonList("ramp"), + null + ); + + assertEquals(formOfWays, stepIntersection.formOfWays()); + + String jsonStr = stepIntersection.toJson(); + compareJson(stepIntersectionJsonString, jsonStr); + } + + @Test + public void testNullFormOfWays() { + String stepIntersectionJsonString = "{" + + "\"location\": [ 13.426579, 52.508068 ]" + + "}"; + + StepIntersection stepIntersection = StepIntersection.fromJson(stepIntersectionJsonString); + assertNull(stepIntersection.formOfWays()); + } + + @Test + public void testGeometries() { + String stepIntersectionJsonString = "{" + + "\"location\": [ 13.426579, 52.508068 ]," + + "\"geometries\": [\n" + + " \"srrayA_bmcOjR{ObGcGfE_DfEkCbG_D\",\n" + + " null,\n" + + " null\n" + + "]" + + "}"; + + StepIntersection stepIntersection = StepIntersection.fromJson(stepIntersectionJsonString); + + List geometries = Arrays.asList( + "srrayA_bmcOjR{ObGcGfE_DfEkCbG_D", + null, + null + ); + + assertEquals(geometries, stepIntersection.geometries()); + + String jsonStr = stepIntersection.toJson(); + compareJson(stepIntersectionJsonString, jsonStr); + } + + @Test + public void testNullGeometries() { + String stepIntersectionJsonString = "{" + + "\"location\": [ 13.426579, 52.508068 ]" + + "}"; + + StepIntersection stepIntersection = StepIntersection.fromJson(stepIntersectionJsonString); + assertNull(stepIntersection.geometries()); + } } diff --git a/services-directions-models/src/test/resources/directions_v5_with_toll_costs_and_lanes.json b/services-directions-models/src/test/resources/directions_v5_with_toll_costs_and_lanes.json index 17a98c182..cf07aa141 100644 --- a/services-directions-models/src/test/resources/directions_v5_with_toll_costs_and_lanes.json +++ b/services-directions-models/src/test/resources/directions_v5_with_toll_costs_and_lanes.json @@ -143,10 +143,29 @@ } ], "entry": [ - true + true, + true, + false ], "bearings": [ - 198 + 90, + 180, + 270 + ], + "geometries": [ + "k}fiyAcxhgOjCRrD?rDS~CSrDSbQ{@rIg@nFSfES~HSjMSrNRjMz@jHz@jMjCnK~CzJrD~MvG", + null, + null + ], + "form_of_way": [ + [ + "ramp" + ], + [ + "ramp", + "elevated" + ], + null ], "duration": 3.749, "mapbox_streets_v8": { diff --git a/services-directions-models/src/test/resources/route_options_v5.json b/services-directions-models/src/test/resources/route_options_v5.json index 28998ec51..25b45de77 100644 --- a/services-directions-models/src/test/resources/route_options_v5.json +++ b/services-directions-models/src/test/resources/route_options_v5.json @@ -39,5 +39,7 @@ "waypoints_per_route": true, "compute_toll_cost": true, "payment_methods": "general", - "suppress_voice_instruction_local_names": true -} \ No newline at end of file + "suppress_voice_instruction_local_names": true, + "intersection_link_form_of_way": true, + "intersection_link_geometry": "motorway,trunk,primary" +}