diff --git a/services-directions/src/main/java/com/mapbox/api/directions/v5/DirectionsService.java b/services-directions/src/main/java/com/mapbox/api/directions/v5/DirectionsService.java index 1acdf636f..53d0e8d22 100644 --- a/services-directions/src/main/java/com/mapbox/api/directions/v5/DirectionsService.java +++ b/services-directions/src/main/java/com/mapbox/api/directions/v5/DirectionsService.java @@ -46,6 +46,7 @@ public interface DirectionsService { * @param voiceUnits voice units * @param exclude exclude tolls, motorways or more along your route * @param approaches which side of the road to approach a waypoint + * @param waypoints which input coordinates should be treated as waypoints * @param waypointNames custom names for waypoints used for the arrival instruction * @param waypointTargets list of coordinate pairs for drop-off locations * @return the {@link DirectionsResponse} in a Call wrapper @@ -73,6 +74,7 @@ Call getCall( @Query("voice_units") String voiceUnits, @Query("exclude") String exclude, @Query("approaches") String approaches, + @Query("waypoints") String waypoints, @Query("waypoint_names") String waypointNames, @Query("waypoint_targets") String waypointTargets ); diff --git a/services-directions/src/main/java/com/mapbox/api/directions/v5/MapboxDirections.java b/services-directions/src/main/java/com/mapbox/api/directions/v5/MapboxDirections.java index 56cede36b..a2e014663 100644 --- a/services-directions/src/main/java/com/mapbox/api/directions/v5/MapboxDirections.java +++ b/services-directions/src/main/java/com/mapbox/api/directions/v5/MapboxDirections.java @@ -1,6 +1,7 @@ package com.mapbox.api.directions.v5; import android.support.annotation.FloatRange; +import android.support.annotation.IntRange; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -91,6 +92,7 @@ protected Call initializeCall() { voiceUnits(), exclude(), approaches(), + waypoints(), waypointNames(), waypointTargets()); } @@ -359,6 +361,9 @@ private static String formatWaypointTargets(Point[] waypointTargets) { @Nullable abstract String approaches(); + @Nullable + abstract String waypoints(); + @Nullable abstract String waypointNames(); @@ -419,6 +424,7 @@ public abstract static class Builder { private Point destination; private Point origin; private String[] approaches; + private Integer[] waypoints; private String[] waypointNames; private Point[] waypointTargets; @@ -787,6 +793,26 @@ public Builder addApproaches(String... approaches) { abstract Builder approaches(@Nullable String approaches); + /** + * Optionally, set which input coordinates should be treated as waypoints. + *

+ * Most useful in combination with steps=true and requests based on traces + * with high sample rates. Can be an index corresponding to any of the input coordinates, + * but must contain the first ( 0 ) and last coordinates' index separated by ; . + * {@link #steps()} + *

+ * + * @param waypoints integer array of coordinate indices to be used as waypoints + * @return this builder for chaining options together + * @since 4.4.0 + */ + public Builder addWaypoints(@Nullable @IntRange(from = 0) Integer... waypoints) { + this.waypoints = waypoints; + return this; + } + + abstract Builder waypoints(@Nullable String waypoints); + /** * Custom names for waypoints used for the arrival instruction, * each separated by ; . Values can be any string and total number of all characters cannot @@ -845,6 +871,24 @@ public MapboxDirections build() { + " directions API request."); } + if (waypoints != null) { + if (waypoints.length < 2) { + throw new ServicesException( + "Waypoints must be a list of at least two indexes separated by ';'"); + } + if (waypoints[0] != 0 || waypoints[waypoints.length - 1] != coordinates.size() - 1) { + throw new ServicesException( + "Waypoints must contain indices of the first and last coordinates" + ); + } + for (int i = 1; i < waypoints.length - 1; i++) { + if (waypoints[i] < 0 || waypoints[i] >= coordinates.size()) { + throw new ServicesException( + "Waypoints index too large (no corresponding coordinate)"); + } + } + } + if (waypointNames != null) { if (waypointNames.length != coordinates.size()) { throw new ServicesException("Number of waypoint names must match " @@ -879,6 +923,7 @@ public MapboxDirections build() { bearing(TextUtils.formatBearing(bearings)); annotation(TextUtils.join(",", annotations)); radius(TextUtils.formatRadiuses(radiuses)); + waypoints(TextUtils.join(";", waypoints)); MapboxDirections directions = autoBuild(); diff --git a/services-directions/src/main/java/com/mapbox/api/directions/v5/models/RouteOptions.java b/services-directions/src/main/java/com/mapbox/api/directions/v5/models/RouteOptions.java index 05c7492a8..35cf5a0c9 100644 --- a/services-directions/src/main/java/com/mapbox/api/directions/v5/models/RouteOptions.java +++ b/services-directions/src/main/java/com/mapbox/api/directions/v5/models/RouteOptions.java @@ -267,6 +267,22 @@ public static Builder builder() { @Nullable public abstract String approaches(); + /** + * Indicates which input coordinates should be treated as waypoints. + *

+ * Most useful in combination with steps=true and requests based on traces + * with high sample rates. Can be an index corresponding to any of the input coordinates, + * but must contain the first ( 0 ) and last coordinates' index separated by ; . + * {@link #steps()} + *

+ * + * @return a string representing indices to be used as waypoints + * @since 4.4.0 + */ + + @Nullable + public abstract String waypoints(); + /** * Custom names for waypoints used for the arrival instruction in banners and voice instructions, * each separated by ; . Values can be any string and total number of all characters cannot @@ -541,6 +557,17 @@ public abstract Builder overview( @Nullable public abstract Builder approaches(String approaches); + /** + * The same waypoints the user originally made when the request was made. + * + * @param indices to be used as waypoints + * @return this builder for chaining options together + * @since 4.4.0 + */ + + @Nullable + public abstract Builder waypoints(@Nullable String indices); + /** * The same waypoint names the user originally made when the request was made. * diff --git a/services-directions/src/test/java/com/mapbox/api/directions/v5/MapboxDirectionsTest.java b/services-directions/src/test/java/com/mapbox/api/directions/v5/MapboxDirectionsTest.java index e7eba9e39..710a986ae 100644 --- a/services-directions/src/test/java/com/mapbox/api/directions/v5/MapboxDirectionsTest.java +++ b/services-directions/src/test/java/com/mapbox/api/directions/v5/MapboxDirectionsTest.java @@ -230,6 +230,19 @@ public void addWaypoint_doesGetFormattedInUrlCorrectly() throws Exception { .contains("1.234,2.345;90.01,50.23;13.493,9.958")); } + @Test + public void waypoints_doesGetFormattedInUrlCorrectly() throws Exception { + MapboxDirections directions = MapboxDirections.builder() + .destination(Point.fromLngLat(13.4930, 9.958)) + .addWaypoint(Point.fromLngLat(4.56, 7.89)) + .origin(Point.fromLngLat(1.234, 2.345)) + .addWaypoints(0,2) + .accessToken(ACCESS_TOKEN) + .build(); + String semicolon = "%3B"; + assertTrue(directions.cloneCall().request().url().toString().contains("waypoints=0" + semicolon + "2")); + } + @Test public void alternatives_doesGetFormattedInUrlCorrectly() throws Exception { MapboxDirections directions = MapboxDirections.builder() @@ -655,6 +668,53 @@ public void testRouteOptionsApproaches() throws Exception { assertEquals("unrestricted;curb", approaches); } + @Test(expected = ServicesException.class) + public void build_exceptionThrownWhenLessThanTwoWaypointsProvided() { + MapboxDirections.builder() + .origin(Point.fromLngLat(2.0, 2.0)) + .destination(Point.fromLngLat(4.0, 4.0)) + .addWaypoints(0) + .baseUrl("https://foobar.com") + .accessToken(ACCESS_TOKEN) + .build(); + } + + @Test(expected = ServicesException.class) + public void build_exceptionThrownWhenWaypointsDoNotStartWith0() { + MapboxDirections.builder() + .origin(Point.fromLngLat(2.0, 2.0)) + .addWaypoint(Point.fromLngLat(3.0, 3.0)) + .destination(Point.fromLngLat(4.0, 4.0)) + .addWaypoints(1, 2) + .baseUrl("https://foobar.com") + .accessToken(ACCESS_TOKEN) + .build(); + } + + @Test(expected = ServicesException.class) + public void build_exceptionThrownWhenWaypointDoNotEndWithLast() { + MapboxDirections.builder() + .origin(Point.fromLngLat(2.0, 2.0)) + .addWaypoint(Point.fromLngLat(3.0, 3.0)) + .destination(Point.fromLngLat(4.0, 4.0)) + .addWaypoints(0, 1) + .baseUrl("https://foobar.com") + .accessToken(ACCESS_TOKEN) + .build(); + } + + @Test(expected = ServicesException.class) + public void build_exceptionThrownWhenMiddleWaypointsAreWrong() { + MapboxDirections.builder() + .origin(Point.fromLngLat(2.0, 2.0)) + .addWaypoint(Point.fromLngLat(3.0, 3.0)) + .destination(Point.fromLngLat(4.0, 4.0)) + .addWaypoints(0, 3, 2) + .baseUrl("https://foobar.com") + .accessToken(ACCESS_TOKEN) + .build(); + } + @Test public void sanityWaypointNamesInstructions() throws Exception { MapboxDirections mapboxDirections = MapboxDirections.builder()