diff --git a/app/src/main/java/com/mapbox/services/android/navigation/testapp/activity/MockNavigationActivity.java b/app/src/main/java/com/mapbox/services/android/navigation/testapp/activity/MockNavigationActivity.java index ec12200acaa..6618e0c0f11 100644 --- a/app/src/main/java/com/mapbox/services/android/navigation/testapp/activity/MockNavigationActivity.java +++ b/app/src/main/java/com/mapbox/services/android/navigation/testapp/activity/MockNavigationActivity.java @@ -32,6 +32,7 @@ import com.mapbox.mapboxsdk.maps.OnMapReadyCallback; import com.mapbox.mapboxsdk.maps.Style; import com.mapbox.services.android.navigation.testapp.utils.Utils; +import com.mapbox.services.android.navigation.v5.navigation.NavigationRoute; import com.mapbox.services.android.navigation.v5.navigation.metrics.MapboxMetricsReporter; import com.mapbox.services.android.navigation.v5.navigation.metrics.MetricEvent; import com.mapbox.services.android.navigation.v5.navigation.metrics.MetricsObserver; @@ -48,7 +49,6 @@ import com.mapbox.services.android.navigation.v5.navigation.MapboxNavigation; import com.mapbox.services.android.navigation.v5.navigation.MapboxNavigationOptions; import com.mapbox.services.android.navigation.v5.navigation.NavigationEventListener; -import com.mapbox.services.android.navigation.v5.navigation.NavigationRoute; import com.mapbox.services.android.navigation.v5.navigation.RefreshCallback; import com.mapbox.services.android.navigation.v5.navigation.RefreshError; import com.mapbox.services.android.navigation.v5.navigation.RouteRefresh; diff --git a/examples/build.gradle b/examples/build.gradle index f2c2f986245..d02e95ac6d8 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -84,9 +84,11 @@ android { dependencies { implementation project(':libdirections-offboard') + implementation project(':libdirections-onboard') implementation project(':libtrip-notification') implementation project(':libnavigation-util') implementation project(':liblogger') + implementation project(':libnavigator') //ktlint ktlint dependenciesList.ktlint diff --git a/examples/src/main/AndroidManifest.xml b/examples/src/main/AndroidManifest.xml index 45bdf80fe04..7dd37100ad2 100644 --- a/examples/src/main/AndroidManifest.xml +++ b/examples/src/main/AndroidManifest.xml @@ -48,6 +48,22 @@ android:value=".MainActivity"/> + + + + + + + + diff --git a/examples/src/main/java/com/mapbox/navigation/examples/MainActivity.java b/examples/src/main/java/com/mapbox/navigation/examples/MainActivity.java index 076cde6750f..e09dae8e0da 100644 --- a/examples/src/main/java/com/mapbox/navigation/examples/MainActivity.java +++ b/examples/src/main/java/com/mapbox/navigation/examples/MainActivity.java @@ -22,6 +22,8 @@ import com.mapbox.navigation.examples.activity.MockNavigationActivity; import com.mapbox.navigation.examples.activity.OffboardRouterActivityJava; import com.mapbox.navigation.examples.activity.OffboardRouterActivityKt; +import com.mapbox.navigation.examples.activity.OnboardRouterActivityJava; +import com.mapbox.navigation.examples.activity.OnboardRouterActivityKt; import java.util.ArrayList; import java.util.Arrays; @@ -67,6 +69,16 @@ protected void onCreate(Bundle savedInstanceState) { getString(R.string.title_offboard_router_java), getString(R.string.description_offboard_router_java), OffboardRouterActivityJava.class + ), + new SampleItem( + getString(R.string.title_onboard_router_kotlin), + getString(R.string.description_onboard_router_kotlin), + OnboardRouterActivityKt.class + ), + new SampleItem( + getString(R.string.title_onboard_router_java), + getString(R.string.description_onboard_router_java), + OnboardRouterActivityJava.class ) )); diff --git a/examples/src/main/java/com/mapbox/navigation/examples/activity/OnboardRouterActivityJava.java b/examples/src/main/java/com/mapbox/navigation/examples/activity/OnboardRouterActivityJava.java new file mode 100644 index 00000000000..e5b32702a1e --- /dev/null +++ b/examples/src/main/java/com/mapbox/navigation/examples/activity/OnboardRouterActivityJava.java @@ -0,0 +1,214 @@ +package com.mapbox.navigation.examples.activity; + +import android.os.Bundle; +import android.os.Environment; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +import com.google.android.material.snackbar.Snackbar; +import com.mapbox.api.directions.v5.models.DirectionsRoute; +import com.mapbox.geojson.Point; +import com.mapbox.mapboxsdk.annotations.MarkerOptions; +import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; +import com.mapbox.mapboxsdk.geometry.LatLng; +import com.mapbox.mapboxsdk.maps.MapView; +import com.mapbox.mapboxsdk.maps.MapboxMap; +import com.mapbox.mapboxsdk.maps.OnMapReadyCallback; +import com.mapbox.mapboxsdk.maps.Style; +import com.mapbox.navigation.base.route.Router; +import com.mapbox.navigation.base.route.model.Route; +import com.mapbox.navigation.base.route.model.RouteOptionsNavigation; +import com.mapbox.navigation.examples.R; +import com.mapbox.navigation.examples.utils.Utils; +import com.mapbox.navigation.examples.utils.extensions.Mappers; +import com.mapbox.navigation.route.onboard.MapboxOnboardRouter; +import com.mapbox.navigation.route.onboard.model.Config; +import com.mapbox.services.android.navigation.ui.v5.route.NavigationMapRoute; + +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.util.List; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import timber.log.Timber; + +public class OnboardRouterActivityJava extends AppCompatActivity implements OnMapReadyCallback, + MapboxMap.OnMapClickListener { + + private Router onboardRouter; + private MapboxMap mapboxMap; + + private DirectionsRoute route; + private NavigationMapRoute navigationMapRoute; + private Point origin; + private Point destination; + private Point waypoint; + + @BindView(R.id.mapView) + MapView mapView; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_mock_navigation); + ButterKnife.bind(this); + + setupRouter(); + + mapView.onCreate(savedInstanceState); + mapView.getMapAsync(this); + } + + private void setupRouter() { + File file = new File( + Environment.getExternalStoragePublicDirectory("Offline").getAbsolutePath(), + "2019_04_13-00_00_11"); + File fileTiles = new File(file, "tiles"); + Config config = new Config( + fileTiles.getAbsolutePath(), + null, + null, + null, + null // working with pre-fetched tiles only + ); + + onboardRouter = new MapboxOnboardRouter(config, null); + } + + + @OnClick(R.id.newLocationFab) + public void onNewLocationClick() { + newOrigin(); + } + + private void newOrigin() { + if (mapboxMap != null) { + clearMap(); + LatLng latLng = new LatLng(47.05991, 9.49183); + origin = Point.fromLngLat(latLng.getLongitude(), latLng.getLatitude()); + mapboxMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 12)); + } + } + + @Override + public void onMapReady(@NonNull MapboxMap mapboxMap) { + this.mapboxMap = mapboxMap; + this.mapboxMap.addOnMapClickListener(this); + mapboxMap.setStyle(Style.MAPBOX_STREETS, style -> { + navigationMapRoute = new NavigationMapRoute(mapView, mapboxMap); + Snackbar.make(findViewById(R.id.container), "Tap map to place waypoint", Snackbar.LENGTH_LONG).show(); + newOrigin(); + }); + } + + private void clearMap() { + if (mapboxMap != null) { + mapboxMap.clear(); + route = null; + destination = null; + waypoint = null; + navigationMapRoute.updateRouteVisibilityTo(false); + navigationMapRoute.updateRouteArrowVisibilityTo(false); + } + } + + private void findRoute() { + RouteOptionsNavigation.Builder optionsBuilder = new RouteOptionsNavigation.Builder() + .accessToken(Utils.getMapboxAccessToken(this)) + .origin(origin) + .destination(destination); + if (waypoint != null) { + optionsBuilder.addWaypoint(waypoint); + } + onboardRouter.getRoute(optionsBuilder.build(), new Router.Callback() { + @Override + public void onResponse(@NotNull List routes) { + if (!routes.isEmpty()) { + route = Mappers.mapToDirectionsRoute(routes.get(0)); + navigationMapRoute.addRoute(route); + } + } + + @Override + public void onFailure(@NotNull Throwable throwable) { + Timber.e(throwable, "onRoutesRequestFailure: navigation.getRoute()"); + } + }); + } + + @Override + public boolean onMapClick(@NonNull LatLng point) { + if (destination == null) { + destination = Point.fromLngLat(point.getLongitude(), point.getLatitude()); + mapboxMap.addMarker(new MarkerOptions().position(point)); + findRoute(); + } else if (waypoint == null) { + waypoint = Point.fromLngLat(point.getLongitude(), point.getLatitude()); + mapboxMap.addMarker(new MarkerOptions().position(point)); + findRoute(); + } else { + Toast.makeText(this, "Only 2 waypoints supported for this example", Toast.LENGTH_LONG).show(); + clearMap(); + } + return false; + } + + /* + * Activity lifecycle methods + */ + + @Override + public void onResume() { + super.onResume(); + mapView.onResume(); + } + + @Override + public void onPause() { + super.onPause(); + mapView.onPause(); + } + + @Override + protected void onStart() { + super.onStart(); + mapView.onStart(); + } + + @Override + protected void onStop() { + super.onStop(); + mapView.onStop(); + } + + @Override + public void onLowMemory() { + super.onLowMemory(); + mapView.onLowMemory(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (onboardRouter != null) { + onboardRouter.cancel(); + onboardRouter = null; + } + if (mapboxMap != null) { + mapboxMap.removeOnMapClickListener(this); + mapView.onDestroy(); + } + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + mapView.onSaveInstanceState(outState); + } +} diff --git a/examples/src/main/java/com/mapbox/navigation/examples/activity/OnboardRouterActivityKt.kt b/examples/src/main/java/com/mapbox/navigation/examples/activity/OnboardRouterActivityKt.kt new file mode 100644 index 00000000000..4867c2dd68c --- /dev/null +++ b/examples/src/main/java/com/mapbox/navigation/examples/activity/OnboardRouterActivityKt.kt @@ -0,0 +1,192 @@ +package com.mapbox.navigation.examples.activity + +import android.os.Bundle +import android.os.Environment +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import com.google.android.material.snackbar.Snackbar +import com.mapbox.api.directions.v5.models.DirectionsRoute +import com.mapbox.geojson.Point +import com.mapbox.mapboxsdk.annotations.MarkerOptions +import com.mapbox.mapboxsdk.camera.CameraUpdateFactory +import com.mapbox.mapboxsdk.geometry.LatLng +import com.mapbox.mapboxsdk.maps.MapboxMap +import com.mapbox.mapboxsdk.maps.OnMapReadyCallback +import com.mapbox.mapboxsdk.maps.Style +import com.mapbox.navigation.base.route.Router +import com.mapbox.navigation.base.route.model.Route +import com.mapbox.navigation.base.route.model.RouteOptionsNavigation +import com.mapbox.navigation.examples.R +import com.mapbox.navigation.examples.utils.Utils +import com.mapbox.navigation.examples.utils.extensions.mapToDirectionsRoute +import com.mapbox.navigation.route.onboard.MapboxOnboardRouter +import com.mapbox.navigation.route.onboard.model.Config +import com.mapbox.navigation.utils.extensions.ifNonNull +import com.mapbox.services.android.navigation.ui.v5.route.NavigationMapRoute +import com.mapbox.turf.TurfConstants +import com.mapbox.turf.TurfMeasurement +import java.io.File +import kotlinx.android.synthetic.main.activity_mock_navigation.* +import timber.log.Timber + +class OnboardRouterActivityKt : AppCompatActivity(), OnMapReadyCallback, + MapboxMap.OnMapClickListener { + + private lateinit var onboardRouter: Router + private lateinit var mapboxMap: MapboxMap + + private var route: DirectionsRoute? = null + private lateinit var navigationMapRoute: NavigationMapRoute + private var origin: Point? = null + private var destination: Point? = null + private var waypoint: Point? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_mock_navigation) + setupRouter() + mapView.onCreate(savedInstanceState) + mapView.getMapAsync(this) + newLocationFab?.setOnClickListener { newOrigin() } + } + + private fun setupRouter() { + val file = File( + Environment.getExternalStoragePublicDirectory("Offline").absolutePath, + "2019_04_13-00_00_11" + ) + val fileTiles = File(file, "tiles") + val config = Config( + fileTiles.absolutePath, + null, + null, + null, + null // working with pre-fetched tiles only + ) + onboardRouter = MapboxOnboardRouter(config, null) + } + + private fun newOrigin() { + clearMap() + val latLng = LatLng(47.05991, 9.49183) + origin = Point.fromLngLat(latLng.longitude, latLng.latitude) + mapboxMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 12.0)) + } + + override fun onMapReady(mapboxMap: MapboxMap) { + this.mapboxMap = mapboxMap + this.mapboxMap.addOnMapClickListener(this) + mapboxMap.setStyle( + Style.MAPBOX_STREETS + ) { + navigationMapRoute = NavigationMapRoute(mapView, mapboxMap) + Snackbar.make( + findViewById(R.id.container), + "Tap map to place waypoint", + Snackbar.LENGTH_LONG + ).show() + newOrigin() + } + } + + private fun clearMap() { + mapboxMap.clear() + route = null + destination = null + waypoint = null + navigationMapRoute.updateRouteVisibilityTo(false) + navigationMapRoute.updateRouteArrowVisibilityTo(false) + } + + private fun findRoute() { + ifNonNull(origin, destination) { origin, destination -> + if (TurfMeasurement.distance(origin, destination, TurfConstants.UNIT_METERS) > 50) { + + val optionsBuilder = + RouteOptionsNavigation.Builder() + .accessToken(Utils.getMapboxAccessToken(this)) + .origin(origin) + .destination(destination) + waypoint?.let { optionsBuilder.addWaypoint(it) } + + onboardRouter.getRoute(optionsBuilder.build(), object : Router.Callback { + override fun onResponse(routes: List) { + if (routes.isNotEmpty()) { + route = routes[0].mapToDirectionsRoute() + navigationMapRoute.addRoute(route) + } + } + + override fun onFailure(throwable: Throwable) { + Timber.e(throwable, "onRoutesRequestFailure: navigation.getRoute()") + } + }) + } + } + } + + override fun onMapClick(point: LatLng): Boolean { + when { + destination == null -> { + destination = Point.fromLngLat(point.longitude, point.latitude) + mapboxMap.addMarker(MarkerOptions().position(point)) + findRoute() + } + waypoint == null -> { + waypoint = Point.fromLngLat(point.longitude, point.latitude) + mapboxMap.addMarker(MarkerOptions().position(point)) + findRoute() + } + else -> { + Toast.makeText( + this, + "Only 2 waypoints supported for this example", + Toast.LENGTH_LONG + ) + .show() + clearMap() + } + } + return false + } + + /* + * Activity lifecycle methods + */ + override fun onResume() { + super.onResume() + mapView.onResume() + } + + override fun onPause() { + super.onPause() + mapView.onPause() + } + + override fun onStart() { + super.onStart() + mapView.onStart() + } + + override fun onStop() { + super.onStop() + mapView.onStop() + } + + override fun onLowMemory() { + super.onLowMemory() + mapView.onLowMemory() + } + + override fun onDestroy() { + super.onDestroy() + onboardRouter.cancel() + mapboxMap.removeOnMapClickListener(this) + mapView.onDestroy() + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + mapView.onSaveInstanceState(outState) + } +} diff --git a/examples/src/main/res/values/strings.xml b/examples/src/main/res/values/strings.xml index 24ecb3826f2..3f714330c48 100644 --- a/examples/src/main/res/values/strings.xml +++ b/examples/src/main/res/values/strings.xml @@ -10,6 +10,12 @@ Offboard Router Java Shows routes using offboard routing api in Java code. + Onboard Router Kotlin + Shows routes using onboard routing api in Kotlin code. + + Onboard Router Java + Shows routes using onboard routing api in Java code. + Trip Service Kotlin Shows how to use the Service to push notifications in Kotlin code. diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 9e53a48683a..029919224a3 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -49,7 +49,9 @@ ext { crashlytics : '2.9.9', multidex : '2.0.0', json : '20180813', - coroutinesAndroid : '1.3.3' + coroutinesAndroid : '1.3.3', + okhttp : '3.12.0', + okio : '2.4.3' ] dependenciesList = [ // mapbox @@ -77,6 +79,13 @@ ext { // code style ktlint : "com.pinterest:ktlint:${version.ktlint}", + // network + okhttp : "com.squareup.okhttp3:okhttp:${version.okhttp}", + okhttpInterceptor : "com.squareup.okhttp3:logging-interceptor:${version.okhttp}", + + // I/O + okio : "com.squareup.okio:okio:${version.okio}", + // AutoValue autoValue : "com.google.auto.value:auto-value:${version.autoValue}", autoValuesParcel : "com.ryanharter.auto.value:auto-value-parcel:${version.autoValueParcel}", @@ -141,7 +150,7 @@ ext { errorprone : '0.0.13', coveralls : '2.8.1', spotbugs : '1.3', - gradle : '3.5.1', + gradle : '3.5.3', dependencyGraph : '0.3.0', dependencyUpdates: '0.20.0', kotlin : '1.3.61', diff --git a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/internal/navigation/DepartEventHandler.kt b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/internal/navigation/DepartEventHandler.kt index 0c12082fc86..5856b5100c6 100644 --- a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/internal/navigation/DepartEventHandler.kt +++ b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/internal/navigation/DepartEventHandler.kt @@ -2,11 +2,11 @@ package com.mapbox.services.android.navigation.v5.internal.navigation import android.content.Context import com.mapbox.services.android.navigation.v5.internal.location.MetricsLocation -import com.mapbox.services.android.navigation.v5.internal.navigation.metrics.MetricsReporter import com.mapbox.services.android.navigation.v5.internal.navigation.metrics.NavigationEventFactory import com.mapbox.services.android.navigation.v5.internal.navigation.metrics.PhoneState import com.mapbox.services.android.navigation.v5.internal.navigation.metrics.SessionState import com.mapbox.services.android.navigation.v5.internal.navigation.routeprogress.MetricsRouteProgress +import com.mapbox.services.android.navigation.v5.navigation.metrics.MetricsReporter internal class DepartEventHandler( private val applicationContext: Context, diff --git a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/internal/navigation/HttpClient.kt b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/internal/navigation/HttpClient.kt index 31d872de64a..76b7cc8f217 100644 --- a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/internal/navigation/HttpClient.kt +++ b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/internal/navigation/HttpClient.kt @@ -28,8 +28,10 @@ internal class HttpClient( private val client: OkHttpClient by lazy { if (BuildConfig.DEBUG) { - val interceptor = HttpLoggingInterceptor(HttpLoggingInterceptor.Logger { message -> - Timber.d(message) + val interceptor = HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger { + override fun log(message: String) { + Timber.d(message) + } }).setLevel(HttpLoggingInterceptor.Level.BASIC) clientBuilder.addInterceptor(interceptor) diff --git a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/internal/navigation/InitialGpsEventFactory.kt b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/internal/navigation/InitialGpsEventFactory.kt index 53df2b73ba3..bdd559d5c8e 100644 --- a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/internal/navigation/InitialGpsEventFactory.kt +++ b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/internal/navigation/InitialGpsEventFactory.kt @@ -1,6 +1,6 @@ package com.mapbox.services.android.navigation.v5.internal.navigation -import com.mapbox.services.android.navigation.v5.internal.navigation.metrics.MetricsReporter +import com.mapbox.services.android.navigation.v5.navigation.metrics.MetricsReporter import com.mapbox.services.android.navigation.v5.utils.time.ElapsedTime internal class InitialGpsEventFactory @JvmOverloads constructor( diff --git a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/internal/navigation/InitialGpsEventHandler.kt b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/internal/navigation/InitialGpsEventHandler.kt index ac13a44d9d2..902946c6608 100644 --- a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/internal/navigation/InitialGpsEventHandler.kt +++ b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/internal/navigation/InitialGpsEventHandler.kt @@ -1,6 +1,6 @@ package com.mapbox.services.android.navigation.v5.internal.navigation -import com.mapbox.services.android.navigation.v5.internal.navigation.metrics.MetricsReporter +import com.mapbox.services.android.navigation.v5.navigation.metrics.MetricsReporter internal class InitialGpsEventHandler( private val metricsReporter: MetricsReporter diff --git a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/internal/navigation/NavigationTelemetry.kt b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/internal/navigation/NavigationTelemetry.kt index cd8cae8a3be..4c24df213fa 100644 --- a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/internal/navigation/NavigationTelemetry.kt +++ b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/internal/navigation/NavigationTelemetry.kt @@ -13,7 +13,6 @@ import com.mapbox.geojson.utils.PolylineUtils import com.mapbox.services.android.navigation.BuildConfig import com.mapbox.services.android.navigation.v5.internal.location.MetricsLocation import com.mapbox.services.android.navigation.v5.internal.navigation.metrics.FeedbackEvent -import com.mapbox.services.android.navigation.v5.internal.navigation.metrics.MetricsReporter import com.mapbox.services.android.navigation.v5.internal.navigation.metrics.NavigationAppUserTurnstileEvent import com.mapbox.services.android.navigation.v5.internal.navigation.metrics.NavigationEventFactory import com.mapbox.services.android.navigation.v5.internal.navigation.metrics.NavigationMetricListener @@ -25,6 +24,7 @@ import com.mapbox.services.android.navigation.v5.internal.utils.RingBuffer import com.mapbox.services.android.navigation.v5.navigation.MapboxNavigation import com.mapbox.services.android.navigation.v5.navigation.MapboxNavigationOptions import com.mapbox.services.android.navigation.v5.navigation.metrics.MapboxMetricsReporter +import com.mapbox.services.android.navigation.v5.navigation.metrics.MetricsReporter import com.mapbox.services.android.navigation.v5.routeprogress.RouteProgress import com.mapbox.services.android.navigation.v5.utils.exceptions.NavigationException import com.mapbox.services.android.navigation.v5.utils.extensions.ifNonNull diff --git a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationRoute.kt b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationRoute.kt index 00722b16a73..8ad014a4bb1 100644 --- a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationRoute.kt +++ b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationRoute.kt @@ -13,7 +13,6 @@ import com.mapbox.geojson.Point import com.mapbox.services.android.navigation.v5.internal.accounts.SkuInterceptor import com.mapbox.services.android.navigation.v5.utils.extensions.getUnitTypeForLocale import com.mapbox.services.android.navigation.v5.utils.extensions.inferDeviceLocale -import com.mapbox.services.android.navigation.v5.utils.extensions.mapToWalkingOptions import java.util.Locale import okhttp3.EventListener import okhttp3.Interceptor @@ -626,8 +625,8 @@ internal constructor( * @param navigationWalkingOptions object holding walking options * @return this builder for chaining options together */ - fun walkingOptions(navigationWalkingOptions: WalkingOptionsNavigation): Builder { - directionsBuilder.walkingOptions(navigationWalkingOptions.mapToWalkingOptions()) + fun walkingOptions(navigationWalkingOptions: NavigationWalkingOptions): Builder { + directionsBuilder.walkingOptions(navigationWalkingOptions.walkingOptions) return this } diff --git a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationRouteWaypoint.kt b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationRouteWaypoint.kt index 6d5f5620cae..c648dae2384 100644 --- a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationRouteWaypoint.kt +++ b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationRouteWaypoint.kt @@ -2,7 +2,7 @@ package com.mapbox.services.android.navigation.v5.navigation import com.mapbox.geojson.Point -internal data class NavigationRouteWaypoint( +data class NavigationRouteWaypoint( val point: Point, val bearingAngle: Double?, val tolerance: Double? diff --git a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationWalkingOptions.kt b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationWalkingOptions.kt new file mode 100644 index 00000000000..ce63379be8a --- /dev/null +++ b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationWalkingOptions.kt @@ -0,0 +1,73 @@ +package com.mapbox.services.android.navigation.v5.navigation + +import com.mapbox.api.directions.v5.WalkingOptions + +/** + * Class for specifying options for use with the walking profile. + */ +class NavigationWalkingOptions internal constructor(val walkingOptions: WalkingOptions) { + + companion object { + /** + * Build a new [WalkingOptions] object with no defaults. + * + * @return a [Builder] object for creating a [NavigationWalkingOptions] object + */ + @JvmStatic + fun builder(): Builder { + return Builder(WalkingOptions.builder()) + } + } + + /** + * This builder is used to create a new object with specifications relating to walking directions. + */ + class Builder internal constructor(private val builder: WalkingOptions.Builder) { + + /** + * Builds a [NavigationWalkingOptions] object with the specified configurations. + * + * @return a NavigationWalkingOptions object + */ + fun build(): NavigationWalkingOptions = NavigationWalkingOptions(builder.build()) + + /** + * Walking speed in meters per second. Must be between 0.14 and 6.94 meters per second. + * Defaults to 1.42 meters per second + * + * @param walkingSpeed in meters per second + * @return this builder + */ + fun walkingSpeed(walkingSpeed: Double?): Builder { + builder.walkingSpeed(walkingSpeed) + return this + } + + /** + * A bias which determines whether the route should prefer or avoid the use of roads or paths + * that are set aside for pedestrian-only use (walkways). The allowed range of values is from + * -1 to 1, where -1 indicates preference to avoid walkways, 1 indicates preference to favor + * walkways, and 0 indicates no preference (the default). + * + * @param walkwayBias bias to prefer or avoid walkways + * @return this builder + */ + fun walkwayBias(walkwayBias: Double?): Builder { + builder.walkwayBias(walkwayBias) + return this + } + + /** + * A bias which determines whether the route should prefer or avoid the use of alleys. The + * allowed range of values is from -1 to 1, where -1 indicates preference to avoid alleys, 1 + * indicates preference to favor alleys, and 0 indicates no preference (the default). + * + * @param alleyBias bias to prefer or avoid alleys + * @return this builder + */ + fun alleyBias(alleyBias: Double?): Builder { + builder.alleyBias(alleyBias) + return this + } + } +} diff --git a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/metrics/MapboxMetricsReporter.kt b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/metrics/MapboxMetricsReporter.kt index 1ae4e2b115f..a0aea60c434 100644 --- a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/metrics/MapboxMetricsReporter.kt +++ b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/metrics/MapboxMetricsReporter.kt @@ -3,7 +3,6 @@ package com.mapbox.services.android.navigation.v5.navigation.metrics import android.content.Context import com.google.gson.Gson import com.mapbox.android.telemetry.MapboxTelemetry -import com.mapbox.services.android.navigation.v5.internal.navigation.metrics.MetricsReporter import com.mapbox.services.android.navigation.v5.internal.utils.extensions.toTelemetryEvent import com.mapbox.services.android.navigation.v5.utils.thread.WorkThreadHandler diff --git a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/metrics/MetricsReporter.kt b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/metrics/MetricsReporter.kt new file mode 100644 index 00000000000..a6855f4f5eb --- /dev/null +++ b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/metrics/MetricsReporter.kt @@ -0,0 +1,32 @@ +package com.mapbox.services.android.navigation.v5.navigation.metrics + +/** + * Interface for handling metric events. + * + * @since 0.43.0 + */ +interface MetricsReporter { + + /** + * Add event to metrics reporter when this event occurs. + * + * @param metricEvent event that should be handled + * @since 0.43.0 + */ + fun addEvent(metricEvent: MetricEvent) + + /** + * Add observer that triggered when metric event handled + * + * @param metricsObserver metric event handle observer + * @since 0.43.0 + */ + fun setMetricsObserver(metricsObserver: MetricsObserver) + + /** + * Remove metrics observer + * + * @since 0.43.0 + */ + fun removeObserver() +} diff --git a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/utils/extensions/Mappers.kt b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/utils/extensions/Mappers.kt deleted file mode 100644 index b414d7c0dc1..00000000000 --- a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/utils/extensions/Mappers.kt +++ /dev/null @@ -1,13 +0,0 @@ -@file:JvmName("Mappers") - -package com.mapbox.services.android.navigation.v5.utils.extensions - -import com.mapbox.api.directions.v5.WalkingOptions -import com.mapbox.services.android.navigation.v5.navigation.WalkingOptionsNavigation - -fun WalkingOptionsNavigation.mapToWalkingOptions(): WalkingOptions = WalkingOptions - .builder() - .walkingSpeed(walkingSpeed) - .walkwayBias(walkwayBias) - .alleyBias(alleyBias) - .build() diff --git a/libandroid-navigation/src/test/java/com/mapbox/services/android/navigation/v5/internal/navigation/InitialGpsEventFactoryTest.java b/libandroid-navigation/src/test/java/com/mapbox/services/android/navigation/v5/internal/navigation/InitialGpsEventFactoryTest.java index 66e3332d391..2af41a53e22 100644 --- a/libandroid-navigation/src/test/java/com/mapbox/services/android/navigation/v5/internal/navigation/InitialGpsEventFactoryTest.java +++ b/libandroid-navigation/src/test/java/com/mapbox/services/android/navigation/v5/internal/navigation/InitialGpsEventFactoryTest.java @@ -1,6 +1,6 @@ package com.mapbox.services.android.navigation.v5.internal.navigation; -import com.mapbox.services.android.navigation.v5.internal.navigation.metrics.MetricsReporter; +import com.mapbox.services.android.navigation.v5.navigation.metrics.MetricsReporter; import com.mapbox.services.android.navigation.v5.utils.time.ElapsedTime; import org.junit.Test; diff --git a/libdirections-offboard/src/main/java/com/mapbox/navigation/route/offboard/MapboxOffboardRouter.kt b/libdirections-offboard/src/main/java/com/mapbox/navigation/route/offboard/MapboxOffboardRouter.kt index 1ff18714492..a943c060dae 100644 --- a/libdirections-offboard/src/main/java/com/mapbox/navigation/route/offboard/MapboxOffboardRouter.kt +++ b/libdirections-offboard/src/main/java/com/mapbox/navigation/route/offboard/MapboxOffboardRouter.kt @@ -26,7 +26,9 @@ class MapboxOffboardRouter(private val accessToken: String, private val context: routeOptions: RouteOptionsNavigation, callback: Router.Callback ) { - navigationRoute = RouteBuilderProvider.getBuilder(accessToken, context).routeOptions(routeOptions).build() + navigationRoute = RouteBuilderProvider.getBuilder(accessToken, context) + .routeOptions(routeOptions) + .build() navigationRoute?.getRoute(object : Callback { override fun onResponse( diff --git a/libdirections-offboard/src/main/java/com/mapbox/navigation/route/offboard/extension/Mappers.kt b/libdirections-offboard/src/main/java/com/mapbox/navigation/route/offboard/extension/Mappers.kt index c78acde76fe..b9f1a97b0f1 100644 --- a/libdirections-offboard/src/main/java/com/mapbox/navigation/route/offboard/extension/Mappers.kt +++ b/libdirections-offboard/src/main/java/com/mapbox/navigation/route/offboard/extension/Mappers.kt @@ -4,16 +4,32 @@ package com.mapbox.navigation.route.offboard.extension import com.mapbox.api.directions.v5.DirectionsCriteria import com.mapbox.api.directions.v5.WalkingOptions +import com.mapbox.api.directions.v5.models.BannerComponents +import com.mapbox.api.directions.v5.models.BannerInstructions +import com.mapbox.api.directions.v5.models.BannerText import com.mapbox.api.directions.v5.models.DirectionsRoute +import com.mapbox.api.directions.v5.models.IntersectionLanes +import com.mapbox.api.directions.v5.models.LegAnnotation import com.mapbox.api.directions.v5.models.LegStep +import com.mapbox.api.directions.v5.models.MaxSpeed import com.mapbox.api.directions.v5.models.RouteLeg import com.mapbox.api.directions.v5.models.RouteOptions +import com.mapbox.api.directions.v5.models.StepIntersection import com.mapbox.api.directions.v5.models.StepManeuver +import com.mapbox.api.directions.v5.models.VoiceInstructions +import com.mapbox.navigation.base.route.model.BannerComponentsNavigation +import com.mapbox.navigation.base.route.model.BannerInstructionsNavigation +import com.mapbox.navigation.base.route.model.BannerTextNavigation +import com.mapbox.navigation.base.route.model.IntersectionLanesNavigation +import com.mapbox.navigation.base.route.model.LegAnnotationNavigation import com.mapbox.navigation.base.route.model.LegStepNavigation +import com.mapbox.navigation.base.route.model.MaxSpeedNavigation import com.mapbox.navigation.base.route.model.Route import com.mapbox.navigation.base.route.model.RouteLegNavigation import com.mapbox.navigation.base.route.model.RouteOptionsNavigation +import com.mapbox.navigation.base.route.model.StepIntersectionNavigation import com.mapbox.navigation.base.route.model.StepManeuverNavigation +import com.mapbox.navigation.base.route.model.VoiceInstructionsNavigation import com.mapbox.navigation.base.route.model.WalkingOptionsNavigation import java.util.Locale @@ -43,8 +59,8 @@ fun RouteLeg.mapToRouteLeg() = RouteLegNavigation.Builder() fun DirectionsRoute.mapToRoute() = Route( routeIndex = routeIndex(), - distance = distance()!!, - duration = duration()?.toLong()!!, + distance = distance() ?: .0, + duration = duration()?.toLong() ?: 0, geometry = geometry(), weight = weight(), weightName = weightName(), @@ -57,6 +73,21 @@ fun DirectionsRoute.mapToRoute() = Route( voiceLanguage = voiceLanguage() ) +fun RouteLeg.mapToRouteLegNavigation() = RouteLegNavigation.Builder() + .distance(distance()) + .duration(duration()) + .steps(steps()?.map { it.mapToLegStep() }) + .summary(summary()) + .build() + +fun LegStep.mapToLegStepNavigation() = LegStepNavigation.Builder() + .stepManeuver(maneuver().mapToStepManeuverNavigation()) + .distance(distance()) + .drivingSide(drivingSide()) + .duration(duration()) + .geometry(geometry()) + .build() + fun RouteOptions.mapToRouteOptionsNavigation(): RouteOptionsNavigation { val routeOptionsNavigationBuilder = RouteOptionsNavigation .builder() @@ -99,6 +130,73 @@ fun RouteOptions.mapToRouteOptionsNavigation(): RouteOptionsNavigation { .build() } +fun VoiceInstructions.mapToVoiceInstructionsNavigation() = VoiceInstructionsNavigation( + distanceAlongGeometry = distanceAlongGeometry(), + announcement = announcement(), + ssmlAnnouncement = ssmlAnnouncement() +) + +fun BannerInstructions.mapToBannerInstructionsNavigation() = BannerInstructionsNavigation( + distanceAlongGeometry = distanceAlongGeometry(), + primary = primary().mapToBannerTextNavigation(), + secondary = secondary()?.mapToBannerTextNavigation(), + sub = sub()?.mapToBannerTextNavigation() +) + +fun BannerText.mapToBannerTextNavigation() = BannerTextNavigation( + text = text(), + components = components()?.map { it.mapToBannerComponentsNavigation() }, + type = type(), + modifier = modifier(), + degrees = degrees(), + drivingSide = drivingSide() +) + +fun BannerComponents.mapToBannerComponentsNavigation() = BannerComponentsNavigation( + text = text(), + type = type(), + abbreviation = abbreviation(), + abbreviationPriority = abbreviationPriority(), + imageBaseUrl = imageBaseUrl(), + directions = directions(), + active = active() +) + +fun StepManeuver.mapToStepManeuverNavigation() = StepManeuverNavigation.Builder() + .modifier(modifier()) + .type(type()) + .build() + +fun StepIntersection.mapToStepIntersectionNavigation() = StepIntersectionNavigation( + location = location(), + bearings = bearings(), + classes = classes(), + entry = entry(), + into = `in`(), + out = out(), + lanes = lanes()?.map { it.mapToIntersectionLanesNavigation() } +) + +fun IntersectionLanes.mapToIntersectionLanesNavigation() = IntersectionLanesNavigation( + valid = valid(), + indications = indications() +) + +fun LegAnnotation.mapToLegAnnotationNavigation() = LegAnnotationNavigation( + distance = distance(), + duration = duration(), + speed = speed(), + maxspeed = maxspeed()?.map(MaxSpeed::mapToMaxSpeedNavigation), + congestion = congestion() +) + +fun MaxSpeed.mapToMaxSpeedNavigation() = MaxSpeedNavigation( + speed = speed(), + unit = unit(), + unknown = unknown(), + none = none() +) + fun WalkingOptionsNavigation.mapToWalkingOptions(): WalkingOptions = WalkingOptions .builder() .walkingSpeed(walkingSpeed) diff --git a/libdirections-offboard/src/main/java/com/mapbox/navigation/route/offboard/router/NavigationOffboardRoute.kt b/libdirections-offboard/src/main/java/com/mapbox/navigation/route/offboard/router/NavigationOffboardRoute.kt index c4afd4b77c3..b04583422fc 100644 --- a/libdirections-offboard/src/main/java/com/mapbox/navigation/route/offboard/router/NavigationOffboardRoute.kt +++ b/libdirections-offboard/src/main/java/com/mapbox/navigation/route/offboard/router/NavigationOffboardRoute.kt @@ -18,7 +18,7 @@ import retrofit2.Call import retrofit2.Callback /** - * The NavigationRoute class wraps the [MapboxDirections] class with parameters which + * The NavigationOffboardRoute class wraps the [MapboxDirections] class with parameters which * must be set in order for a navigation session to successfully begin. While it is possible * to pass in any [com.mapbox.api.directions.v5.models.DirectionsRoute] into * [MapboxNavigation.startNavigation], using this class will ensure your @@ -26,9 +26,8 @@ import retrofit2.Callback * * Developer Note: MapboxDirections cannot be directly extended since it is an AutoValue class. * - * 1.0 + * 1.0.0 */ - internal class NavigationOffboardRoute constructor( private val mapboxDirections: MapboxDirections @@ -53,7 +52,7 @@ constructor( * [Callback] must be passed into the method to handle both the response and failure. * * @param callback a RetroFit callback which contains an onResponse and onFailure - * @since 0.5.0 + * @since 1.0.0 */ fun getRoute(callback: Callback) { mapboxDirections.enqueueCall(callback) @@ -82,33 +81,33 @@ constructor( * reflect your users use-case. * * - * @since 0.5.0 + * @since 1.0.0 */ class Builder internal constructor(private val directionsBuilder: MapboxDirections.Builder) { - private val eventListener: NavigationRouteEventListener + + companion object { + private const val SEMICOLON = ";" + private const val COMMA = "," + } + + private val eventListener: NavigationRouteEventListener = EVENT_LISTENER private var origin: RoutePointNavigation? = null private var destination: RoutePointNavigation? = null private val waypoints = ArrayList() - private val SEMICOLON = ";" - private val COMMA = "," /** * Private constructor for initializing the raw MapboxDirections.Builder */ - constructor() : this(MapboxDirections.builder()) {} - - init { - this.eventListener = EVENT_LISTENER - } + constructor() : this(MapboxDirections.builder()) /** * This selects which mode of transportation the user will be using while navigating from the * origin to the final destination. The options include driving, driving considering traffic, * walking, and cycling. Using each of these profiles will result in different routing biases. * - * @param profile required to be one of the String values found in the [ProfileCriteria] + * @param profile required to be one of the String values found in the [DirectionsCriteria.ProfileCriteria] * @return this builder for chaining options together - * @since 0.5.0 + * @since 1.0.0 */ internal fun profile(@DirectionsCriteria.ProfileCriteria profile: String): Builder { directionsBuilder.profile(profile) @@ -180,7 +179,7 @@ constructor( * * @param clientAppName base package name or other simple string identifier * @return this builder for chaining options together - * @since 0.5.0 + * @since 1.0.0 */ fun clientAppName(clientAppName: String): Builder { directionsBuilder.clientAppName(clientAppName) @@ -242,31 +241,23 @@ constructor( * * @param options containing all variables for request * @return this builder for chaining options together - * @since 0.9.0 + * @since 1.0.0 */ - internal fun routeOptions(options: RouteOptionsNavigation): Builder { - options.baseUrl?.let { - directionsBuilder.baseUrl(it) - } + fun routeOptions(options: RouteOptionsNavigation): Builder { + directionsBuilder.baseUrl(options.baseUrl) - options.user?.let { - directionsBuilder.user(it) - } + directionsBuilder.user(options.user) - options.profile?.let { - directionsBuilder.profile(it) - } + directionsBuilder.profile(options.profile) - origin = options.coordinates.first() + origin = options.origin waypoints.clear() - waypoints.addAll(options.coordinates.drop(1).dropLast(1)) + waypoints.addAll(options.waypoints) - destination = options.coordinates.last() + destination = options.destination - options.alternatives?.let { - directionsBuilder.alternatives(it) - } + directionsBuilder.alternatives(options.alternatives) options.language?.let { directionsBuilder.language(Locale(it)) @@ -289,45 +280,33 @@ constructor( } } - options.continueStraight?.let { - directionsBuilder.continueStraight(it) - } + directionsBuilder.continueStraight(options.continueStraight) - options.roundaboutExits?.let { - directionsBuilder.roundaboutExits(it) - } + directionsBuilder.roundaboutExits(options.roundaboutExits) options.geometries?.let { - directionsBuilder.geometries(it) + directionsBuilder.geometries(options.geometries) } options.overview?.let { directionsBuilder.overview(it) } - options.steps?.let { - directionsBuilder.steps(it) - } + directionsBuilder.steps(options.steps) options.annotations?.let { directionsBuilder.annotations(it) } - options.voiceInstructions?.let { - directionsBuilder.voiceInstructions(it) - } + directionsBuilder.voiceInstructions(options.voiceInstructions) - options.bannerInstructions?.let { - directionsBuilder.bannerInstructions(it) - } + directionsBuilder.bannerInstructions(options.bannerInstructions) options.voiceUnits?.let { directionsBuilder.voiceUnits(it) } - options.accessToken?.let { - directionsBuilder.accessToken(it) - } + directionsBuilder.accessToken(options.accessToken) options.requestUuid?.let { // TODO Check if needed as it is only set at response time @@ -381,7 +360,7 @@ constructor( * settings for navigation to work correctly. * * @return a new instance of Navigation Route - * @since 0.5.0 + * @since 1.0.0 */ fun build(): NavigationOffboardRoute { // Set the default values which the user cannot alter. @@ -395,11 +374,10 @@ constructor( val splitWaypointIndices = waypointIndices.split(SEMICOLON.toRegex()).dropLastWhile { it.isEmpty() } .toTypedArray() - val indices = Array(splitWaypointIndices.size, { 0 }) - var index = 0 - for (waypointIndex in splitWaypointIndices) { + val indices = Array(splitWaypointIndices.size) { 0 } + for ((index, waypointIndex) in splitWaypointIndices.withIndex()) { val parsedIndex = Integer.valueOf(waypointIndex) - indices[index++] = parsedIndex + indices[index] = parsedIndex } return indices } diff --git a/libdirections-offboard/src/test/java/com/mapbox/navigation/route/offboard/MapboxOffboardRouterTest.kt b/libdirections-offboard/src/test/java/com/mapbox/navigation/route/offboard/MapboxOffboardRouterTest.kt index 2e90ff5e9a5..171319b1acc 100644 --- a/libdirections-offboard/src/test/java/com/mapbox/navigation/route/offboard/MapboxOffboardRouterTest.kt +++ b/libdirections-offboard/src/test/java/com/mapbox/navigation/route/offboard/MapboxOffboardRouterTest.kt @@ -6,7 +6,6 @@ import com.mapbox.api.directions.v5.models.DirectionsRoute import com.mapbox.navigation.base.route.Router import com.mapbox.navigation.base.route.model.RouteOptionsNavigation import com.mapbox.navigation.route.offboard.base.BaseTest -import com.mapbox.navigation.route.offboard.extension.mapToRoute import com.mapbox.navigation.route.offboard.router.NavigationOffboardRoute import io.mockk.every import io.mockk.mockk @@ -80,7 +79,7 @@ class MapboxOffboardRouterTest : BaseTest() { callback.onResponse(mockk(), response) - verify { routerCallback.onResponse(listOf(route.mapToRoute())) } + verify { routerCallback.onResponse(any()) } } @Test diff --git a/libdirections-onboard/build.gradle b/libdirections-onboard/build.gradle index feb58a297d1..95b230534c0 100644 --- a/libdirections-onboard/build.gradle +++ b/libdirections-onboard/build.gradle @@ -27,9 +27,18 @@ dependencies { ktlint dependenciesList.ktlint implementation dependenciesList.kotlinStdLib + implementation dependenciesList.supportAnnotation + + //networks + implementation dependenciesList.okhttp + implementation dependenciesList.okhttpInterceptor + + // I/O + implementation dependenciesList.okio testImplementation dependenciesList.mockk testImplementation dependenciesList.junit + testImplementation dependenciesList.robolectric } apply from: "${rootDir}/gradle/bintray-publish.gradle" \ No newline at end of file diff --git a/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/MapboxOnboardRouter.kt b/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/MapboxOnboardRouter.kt index 0ce6fa0d3d1..74787bcdb1c 100644 --- a/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/MapboxOnboardRouter.kt +++ b/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/MapboxOnboardRouter.kt @@ -2,21 +2,90 @@ package com.mapbox.navigation.route.onboard import com.mapbox.annotation.navigation.module.MapboxNavigationModule import com.mapbox.annotation.navigation.module.MapboxNavigationModuleType +import com.mapbox.navigation.base.logger.Logger +import com.mapbox.navigation.base.route.RouteUrl import com.mapbox.navigation.base.route.Router +import com.mapbox.navigation.base.route.model.Route import com.mapbox.navigation.base.route.model.RouteOptionsNavigation import com.mapbox.navigation.navigator.MapboxNativeNavigator +import com.mapbox.navigation.navigator.MapboxNativeNavigatorImpl +import com.mapbox.navigation.route.onboard.model.Config +import com.mapbox.navigation.route.onboard.model.OfflineError +import com.mapbox.navigation.route.onboard.model.mapToRouteConfig +import com.mapbox.navigation.route.onboard.network.HttpClient +import com.mapbox.navigation.route.onboard.task.OfflineRouteRetrievalTask +import com.mapbox.navigation.utils.exceptions.NavigationException +import java.io.File @MapboxNavigationModule(MapboxNavigationModuleType.OnboardRouter, skipConfiguration = true) -class MapboxOnboardRouter(private val navigator: MapboxNativeNavigator) : Router { +class MapboxOnboardRouter : Router { + + companion object { + private const val TILES_DIR_NAME = "tiles" + } + + private val navigatorNative: MapboxNativeNavigator + private val config: Config + private val logger: Logger? + + /** + * Creates an offline router which uses the specified offline path for storing and retrieving + * data. + * + * @param config offline config + */ + constructor(config: Config, logger: Logger?) { + val tileDir = File(config.tilePath, TILES_DIR_NAME) + if (!tileDir.exists()) { + tileDir.mkdirs() + } + + this.navigatorNative = MapboxNativeNavigatorImpl + this.config = config + this.logger = logger + val httpClient = HttpClient() + MapboxNativeNavigatorImpl.configureRouter(config.mapToRouteConfig(), httpClient, httpClient.userAgent) + } + + // Package private for testing purposes + internal constructor( + navigator: MapboxNativeNavigator, + config: Config + ) { + this.navigatorNative = navigator + this.config = config + this.logger = null + } override fun getRoute( routeOptions: RouteOptionsNavigation, callback: Router.Callback - ) = Unit + ) { + val offlineRouter = OfflineRoute.builder( + RouteUrl( + accessToken = routeOptions.accessToken, + user = routeOptions.user, + profile = routeOptions.profile, + orgin = routeOptions.origin.point, + waypoints = routeOptions.waypoints.map { it.point }, + destination = routeOptions.destination.point, + steps = routeOptions.steps, + voiceIntruction = routeOptions.voiceInstructions, + bannerIntruction = routeOptions.bannerInstructions, + roundaboutExits = routeOptions.roundaboutExits + ) + ).build() - override fun cancel() = Unit + OfflineRouteRetrievalTask(navigatorNative, logger, object : OnOfflineRouteFoundCallback { + override fun onRouteFound(routes: List) { + callback.onResponse(routes) + } - class Config { - fun compile(): String = TODO("not implemented") + override fun onError(error: OfflineError) { + callback.onFailure(NavigationException(error.message)) + } + }).execute(offlineRouter.buildUrl()) } + + override fun cancel() = Unit } diff --git a/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/OfflineCriteria.kt b/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/OfflineCriteria.kt new file mode 100644 index 00000000000..9a47f080ae1 --- /dev/null +++ b/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/OfflineCriteria.kt @@ -0,0 +1,49 @@ +package com.mapbox.navigation.route.onboard + +object OfflineCriteria { + + /** + * BicycleType parameter in the Directions API. + */ + enum class BicycleType(val type: String) { + /** + * Bicycle type for road bike. + */ + ROAD("Road"), + + /** + * Bicycle type for hybrid bike. + */ + HYBRID("Hybrid"), + + /** + * Bicycle type for city bike. + */ + CITY("City"), + + /** + * Bicycle type for cross bike. + */ + CROSS("Cross"), + + /** + * Bicycle type for mountain bike. + */ + MOUNTAIN("Mountain"); + } + + /** + * WaypointType parameter in the Directions API. + */ + enum class WaypointType(val type: String) { + /** + * Break waypoint type. + */ + BREAK("break"), + + /** + * Through waypoint type. + */ + THROUGH("through") + } +} diff --git a/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/OfflineRoute.kt b/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/OfflineRoute.kt new file mode 100644 index 00000000000..e680caaf2c3 --- /dev/null +++ b/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/OfflineRoute.kt @@ -0,0 +1,228 @@ +package com.mapbox.navigation.route.onboard + +import android.net.Uri +import androidx.annotation.FloatRange +import com.mapbox.navigation.base.route.RouteUrl +import com.mapbox.navigation.utils.extensions.ifNonNull + +/** + * The [OfflineRoute] class wraps the [NavigationRoute] class with parameters which + * could be set in order for an offline navigation session to successfully begin. + */ +internal class OfflineRoute +private constructor( + private val routeUrl: RouteUrl, + bicycleType: OfflineCriteria.BicycleType?, + private val cyclingSpeed: Float?, + private val cyclewayBias: Float?, + private val hillBias: Float?, + private val ferryBias: Float?, + private val roughSurfaceBias: Float?, + waypointTypes: List? +) { + private val bicycleType: String? + private val waypointTypes: String? + + init { + this.bicycleType = bicycleType?.type + this.waypointTypes = checkWaypointTypes(waypointTypes) + } + + companion object { + + private const val BICYCLE_TYPE_QUERY_PARAMETER = "bicycle_type" + private const val CYCLING_SPEED_QUERY_PARAMETER = "cycling_speed" + private const val CYCLEWAY_BIAS_QUERY_PARAMETER = "cycleway_bias" + private const val HILL_BIAS_QUERY_PARAMETER = "hill_bias" + private const val FERRY_BIAS_QUERY_PARAMETER = "ferry_bias" + private const val ROUGH_SURFACE_BIAS_QUERY_PARAMETER = "rough_surface_bias" + private const val WAYPOINT_TYPES_QUERY_PARAMETER = "waypoint_types" + + /** + * Build a new [OfflineRoute] object with the proper offline navigation parameters already setup. + * + * @return a [Builder] object for creating this object + */ + @JvmStatic + fun builder(routeUrl: RouteUrl): Builder { + return Builder(routeUrl) + } + } + + /** + * Builds a URL string for offline. + * + * @return the offline url string + */ + fun buildUrl(): String { + return buildOfflineUrl(routeUrl.getRequest()) + } + + private fun checkWaypointTypes(waypointTypes: List?): String? { + return if (waypointTypes.isNullOrEmpty()) { + null + } else { + formatWaypointTypes(waypointTypes) + ?: throw IllegalStateException("All waypoint types values must be one of break, through or null") + } + } + + private fun formatWaypointTypes(waypointTypesToFormat: List): String? { + val waypointTypes = waypointTypesToFormat.map { it?.type ?: "" }.toTypedArray() + return waypointTypes.joinTo(StringBuilder(), ";").toString() + } + + private fun buildOfflineUrl(url: Uri): String { + val offlineUrlBuilder = url.buildUpon() + + offlineUrlBuilder.appendQueryParamIfNonNull(BICYCLE_TYPE_QUERY_PARAMETER, bicycleType) + offlineUrlBuilder.appendQueryParamIfNonNull(CYCLING_SPEED_QUERY_PARAMETER, cyclingSpeed) + offlineUrlBuilder.appendQueryParamIfNonNull(CYCLEWAY_BIAS_QUERY_PARAMETER, cyclewayBias) + offlineUrlBuilder.appendQueryParamIfNonNull(HILL_BIAS_QUERY_PARAMETER, hillBias) + offlineUrlBuilder.appendQueryParamIfNonNull(FERRY_BIAS_QUERY_PARAMETER, ferryBias) + offlineUrlBuilder.appendQueryParamIfNonNull( + ROUGH_SURFACE_BIAS_QUERY_PARAMETER, + roughSurfaceBias + ) + offlineUrlBuilder.appendQueryParamIfNonNull(WAYPOINT_TYPES_QUERY_PARAMETER, waypointTypes) + + return offlineUrlBuilder.build().toString() + } + + private fun Uri.Builder.appendQueryParamIfNonNull(key: String, value: Float?): Uri.Builder = + appendQueryParamIfNonNull(key, value?.toString()) + + private fun Uri.Builder.appendQueryParamIfNonNull(key: String, value: String?): Uri.Builder = + ifNonNull(value) { + appendQueryParameter(key, it) + } ?: this + + class Builder internal constructor(private val routeUrl: RouteUrl) { + private var bicycleType: OfflineCriteria.BicycleType? = null + private var cyclingSpeed: Float? = null + private var cyclewayBias: Float? = null + private var hillBias: Float? = null + private var ferryBias: Float? = null + private var roughSurfaceBias: Float? = null + private var waypointTypes: List? = null + + /** + * The type of bicycle, either Road, Hybrid, City, Cross, Mountain. + * The default type is Hybrid. + * + * @param bicycleType the type of bicycle + * @return this builder for chaining options together + */ + fun bicycleType(bicycleType: OfflineCriteria.BicycleType?): Builder { + this.bicycleType = bicycleType + return this + } + + /** + * Cycling speed is the average travel speed along smooth, flat roads. This is meant to be the + * speed a rider can comfortably maintain over the desired distance of the route. It can be + * modified (in the costing method) by surface type in conjunction with bicycle type and + * (coming soon) by hilliness of the road section. When no speed is specifically provided, the + * default speed is determined by the bicycle type and are as follows: Road = 25 KPH (15.5 MPH), + * Cross = 20 KPH (13 MPH), Hybrid/City = 18 KPH (11.5 MPH), and Mountain = 16 KPH (10 MPH). + * + * @param cyclingSpeed in kmh + * @return this builder for chaining options together + */ + fun cyclingSpeed(@FloatRange(from = 5.0, to = 60.0) cyclingSpeed: Float?): Builder { + this.cyclingSpeed = cyclingSpeed + return this + } + + /** + * A cyclist's propensity to use roads alongside other vehicles. This is a range of values from -1 + * to 1, where -1 attempts to avoid roads and stay on cycleways and paths, and 1 indicates the + * rider is more comfortable riding on roads. Based on the use_roads factor, roads with certain + * classifications and higher speeds are penalized in an attempt to avoid them when finding the + * best path. The default value is 0. + * + * @param cyclewayBias a cyclist's propensity to use roads alongside other vehicles + * @return this builder for chaining options together + */ + fun cyclewayBias(@FloatRange(from = -1.0, to = 1.0) cyclewayBias: Float?): Builder { + this.cyclewayBias = cyclewayBias + return this + } + + /** + * A cyclist's desire to tackle hills in their routes. This is a range of values from -1 to 1, + * where -1 attempts to avoid hills and steep grades even if it means a longer (time and + * distance) path, while 1 indicates the rider does not fear hills and steeper grades. Based on + * the hill bias factor, penalties are applied to roads based on elevation change and grade. + * These penalties help the path avoid hilly roads in favor of flatter roads or less steep + * grades where available. Note that it is not always possible to find alternate paths to avoid + * hills (for example when route locations are in mountainous areas). The default value is 0. + * + * @param hillBias a cyclist's desire to tackle hills in their routes + * @return this builder for chaining options together + */ + fun hillBias(@FloatRange(from = -1.0, to = 1.0) hillBias: Float?): Builder { + this.hillBias = hillBias + return this + } + + /** + * This value indicates the willingness to take ferries. This is a range of values between -1 and 1. + * Values near -1 attempt to avoid ferries and values near 1 will favor ferries. Note that + * sometimes ferries are required to complete a route so values of -1 are not guaranteed to avoid + * ferries entirely. The default value is 0. + * + * @param ferryBias the willingness to take ferries + * @return this builder for chaining options together + */ + fun ferryBias(@FloatRange(from = -1.0, to = 1.0) ferryBias: Float?): Builder { + this.ferryBias = ferryBias + return this + } + + /** + * This value is meant to represent how much a cyclist wants to favor or avoid roads with poor/rough + * surfaces relative to the bicycle type being used. This is a range of values between -1 and 1. + * When the value approaches -1, we attempt to penalize heavier or avoid roads with rough surface types + * so that they are only taken if they significantly improve travel time; only bicycle + * speed on each surface is taken into account. As the value approaches 1, we will favor rough surfaces. + * When the value is equal to -1, all bad surfaces are completely disallowed from routing, + * including start and end points. The default value is 0. + * + * @param roughSurfaceBias how much a cyclist wants to avoid roads with poor surfaces + * @return this builder for chaining options together + */ + fun roughSurfaceBias(@FloatRange(from = -1.0, to = 1.0) roughSurfaceBias: Float?): Builder { + this.roughSurfaceBias = roughSurfaceBias + return this + } + + /** + * The same waypoint types the user originally made when the request was made. + * + * @param waypointTypes break, through or omitted null + * @return this builder for chaining options together + */ + fun waypointTypes(waypointTypes: List?): Builder { + this.waypointTypes = waypointTypes + return this + } + + /** + * This uses the provided parameters set using the [Builder] and adds the required + * settings for offline navigation to work correctly. + * + * @return a new instance of [OfflineRoute] + */ + fun build(): OfflineRoute = OfflineRoute( + routeUrl, + bicycleType, + cyclingSpeed, + cyclewayBias, + hillBias, + ferryBias, + roughSurfaceBias, + waypointTypes + ) + } +} diff --git a/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/OnOfflineRouteFoundCallback.kt b/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/OnOfflineRouteFoundCallback.kt new file mode 100644 index 00000000000..4cab47e8f76 --- /dev/null +++ b/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/OnOfflineRouteFoundCallback.kt @@ -0,0 +1,24 @@ +package com.mapbox.navigation.route.onboard + +import com.mapbox.navigation.base.route.model.Route +import com.mapbox.navigation.route.onboard.model.OfflineError + +/** + * Callback used for finding offline routes. + */ +internal interface OnOfflineRouteFoundCallback { + + /** + * Called when an offline routes are found. + * + * @param routes offline routes + */ + fun onRouteFound(routes: List) + + /** + * Called when there was an error fetching the offline route. + * + * @param error with message explanation + */ + fun onError(error: OfflineError) +} diff --git a/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/OnOfflineTilesRemovedCallback.kt b/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/OnOfflineTilesRemovedCallback.kt new file mode 100644 index 00000000000..f9e916a2f00 --- /dev/null +++ b/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/OnOfflineTilesRemovedCallback.kt @@ -0,0 +1,18 @@ +package com.mapbox.navigation.route.onboard + +import com.mapbox.geojson.BoundingBox + +/** + * Listener that needs to be added to + * [MapboxOfflineRouter.removeTiles] to know when the routing + * tiles within the provided [BoundingBox] have been removed + */ +interface OnOfflineTilesRemovedCallback { + + /** + * Called when the routing tiles within the provided [BoundingBox] have been removed completely. + * + * @param numberOfTiles removed within the [BoundingBox] provided + */ + fun onRemoved(numberOfTiles: Long) +} diff --git a/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/model/Config.kt b/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/model/Config.kt new file mode 100644 index 00000000000..e9e01925958 --- /dev/null +++ b/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/model/Config.kt @@ -0,0 +1,20 @@ +package com.mapbox.navigation.route.onboard.model + +import com.mapbox.navigation.navigator.model.RouterConfig + +data class Config( + val tilePath: String, + val inMemoryTileCache: Int? = null, + val mapMatchingSpatialCache: Int? = null, + val threadsCount: Int? = null, + val endpoint: Endpoint? = null +) + +fun Config.mapToRouteConfig(): RouterConfig = + RouterConfig( + tilePath = tilePath, + inMemoryTileCache = inMemoryTileCache, + mapMatchingSpatialCache = mapMatchingSpatialCache, + threadsCount = threadsCount, + endpointConfig = endpoint?.mapToEndpointConfig() + ) diff --git a/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/model/Endpoint.kt b/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/model/Endpoint.kt new file mode 100644 index 00000000000..59e3ad38c45 --- /dev/null +++ b/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/model/Endpoint.kt @@ -0,0 +1,18 @@ +package com.mapbox.navigation.route.onboard.model + +import com.mapbox.navigation.navigator.model.EndpointConfig + +data class Endpoint( + val host: String, + val version: String, + val token: String, + val userAgent: String +) + +fun Endpoint.mapToEndpointConfig() = + EndpointConfig( + host = host, + version = version, + token = token, + userAgent = userAgent + ) diff --git a/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/model/OfflineError.kt b/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/model/OfflineError.kt new file mode 100644 index 00000000000..a22627b9a61 --- /dev/null +++ b/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/model/OfflineError.kt @@ -0,0 +1,5 @@ +package com.mapbox.navigation.route.onboard.model + +data class OfflineError( + val message: String +) diff --git a/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/model/OfflineRouteError.kt b/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/model/OfflineRouteError.kt new file mode 100644 index 00000000000..6c09dc11ac5 --- /dev/null +++ b/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/model/OfflineRouteError.kt @@ -0,0 +1,12 @@ +package com.mapbox.navigation.route.onboard.model + +import com.google.gson.annotations.SerializedName + +internal data class OfflineRouteError( + val status: String, + @SerializedName("status_code") + val statusCode: Int, + val error: String, + @SerializedName("error_code") + val errorCode: Int +) diff --git a/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/network/HttpClient.kt b/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/network/HttpClient.kt new file mode 100644 index 00000000000..e139d94cf9b --- /dev/null +++ b/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/network/HttpClient.kt @@ -0,0 +1,84 @@ +package com.mapbox.navigation.route.onboard.network + +import androidx.annotation.Keep +import com.mapbox.navigation.base.logger.Logger +import com.mapbox.navigation.base.logger.model.Message +import com.mapbox.navigator.BuildConfig +import com.mapbox.navigator.HttpCode +import com.mapbox.navigator.HttpInterface +import com.mapbox.navigator.HttpResponse +import java.io.ByteArrayOutputStream +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.logging.HttpLoggingInterceptor +import okio.buffer +import okio.sink + +@Keep +internal class HttpClient( + internal val userAgent: String = USER_AGENT, + private val acceptGzipEncoding: Boolean = false, + private val logger: Logger? = null, + private val clientBuilder: OkHttpClient.Builder = OkHttpClient.Builder() +) : HttpInterface() { + + companion object { + private const val USER_AGENT = "MapboxNavigationNative" + + private const val ERROR_EMPTY_USER_AGENT = "Empty UserAgent is not allowed" + private const val HEADER_USER_AGENT = "User-Agent" + private const val HEADER_ENCODING = "Accept-Encoding" + private const val GZIP = "gzip" + } + + private val client: OkHttpClient by lazy { + if (BuildConfig.DEBUG) { + val interceptor = HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger { + override fun log(message: String) { + logger?.d(msg = Message(message)) + } + }).setLevel(HttpLoggingInterceptor.Level.BASIC) + + clientBuilder.addInterceptor(interceptor) + } + + clientBuilder.build() + } + + init { + check(userAgent.isNotEmpty()) { + ERROR_EMPTY_USER_AGENT + } + } + + override fun isGzipped(): Boolean { + return acceptGzipEncoding + } + + override fun get(url: String): HttpResponse { + val requestBuilder = Request.Builder() + .addHeader(HEADER_USER_AGENT, userAgent) + .url(url) + + if (acceptGzipEncoding) { + requestBuilder.addHeader(HEADER_ENCODING, GZIP) + } + + client.newCall(requestBuilder.build()).execute().use { response -> + val outputStream = ByteArrayOutputStream() + val result = if (response.isSuccessful) HttpCode.SUCCESS else HttpCode.FAILURE + + response.body()?.let { body -> + val sink = outputStream.sink().buffer() + sink.writeAll(body.source()) + sink.close() + } + + // FIXME core should receive Array, not List. It is List now because of bindgen + val bytes = outputStream.toByteArray().toList() + outputStream.close() + + return HttpResponse(bytes, result) + } + } +} diff --git a/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/task/OfflineRouteRetrievalTask.kt b/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/task/OfflineRouteRetrievalTask.kt new file mode 100644 index 00000000000..18482579a4f --- /dev/null +++ b/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/task/OfflineRouteRetrievalTask.kt @@ -0,0 +1,66 @@ +package com.mapbox.navigation.route.onboard.task + +import android.os.AsyncTask +import com.google.gson.Gson +import com.mapbox.navigation.base.logger.Logger +import com.mapbox.navigation.base.logger.model.Message +import com.mapbox.navigation.base.logger.model.Tag +import com.mapbox.navigation.base.route.dto.RouteResponseDto +import com.mapbox.navigation.base.route.dto.mapToModel +import com.mapbox.navigation.base.route.model.Route +import com.mapbox.navigation.navigator.MapboxNativeNavigator +import com.mapbox.navigation.route.onboard.OnOfflineRouteFoundCallback +import com.mapbox.navigation.route.onboard.model.OfflineError +import com.mapbox.navigation.route.onboard.model.OfflineRouteError +import com.mapbox.navigator.RouterResult + +internal class OfflineRouteRetrievalTask( + private val navigator: MapboxNativeNavigator, + private val logger: Logger?, + private val callback: OnOfflineRouteFoundCallback +) : AsyncTask>() { + + @Volatile + private lateinit var routerResult: RouterResult + + private val gson = Gson() + + // For testing only + internal constructor( + navigator: MapboxNativeNavigator, + callback: OnOfflineRouteFoundCallback, + routerResult: RouterResult + ) : this(navigator, null, callback) { + this.routerResult = routerResult + } + + override fun doInBackground(vararg params: String): List? { + val url = params.first() + + synchronized(navigator) { + routerResult = navigator.getRoute(url) + } + + return gson.fromJson(routerResult.json, RouteResponseDto::class.java)?.mapToModel()?.routes + } + + public override fun onPostExecute(offlineRoute: List?) { + if (!offlineRoute.isNullOrEmpty()) { + callback.onRouteFound(offlineRoute) + } else { + callback.onError(OfflineError(generateErrorMessage())) + } + } + + private fun generateErrorMessage(): String { + val (_, _, error, errorCode) = gson.fromJson( + routerResult.json, + OfflineRouteError::class.java + ) + + val errorMessage = "Error occurred fetching offline route: $error - Code: $errorCode" + + logger?.e(Tag("OfflineRouteRetrievalTask"), Message(errorMessage)) + return errorMessage + } +} diff --git a/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/task/RemoveTilesTask.kt b/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/task/RemoveTilesTask.kt new file mode 100644 index 00000000000..c5d262645da --- /dev/null +++ b/libdirections-onboard/src/main/java/com/mapbox/navigation/route/onboard/task/RemoveTilesTask.kt @@ -0,0 +1,20 @@ +package com.mapbox.navigation.route.onboard.task + +import android.os.AsyncTask +import com.mapbox.geojson.Point +import com.mapbox.navigation.route.onboard.OnOfflineTilesRemovedCallback +import com.mapbox.navigator.Navigator + +internal class RemoveTilesTask( + private val navigator: Navigator, + private val tilePath: String, + private val southwest: Point, + private val northeast: Point, + private val callback: OnOfflineTilesRemovedCallback +) : AsyncTask() { + + override fun doInBackground(vararg paramsUnused: Void): Long = + navigator.removeTiles(tilePath, southwest, northeast) + + public override fun onPostExecute(numberOfTiles: Long) = callback.onRemoved(numberOfTiles) +} diff --git a/libdirections-onboard/src/test/java/com/mapbox/navigation/route/onboard/MapboxOnboardRouterGenerationTest.kt b/libdirections-onboard/src/test/java/com/mapbox/navigation/route/onboard/MapboxOnboardRouterTest.kt similarity index 69% rename from libdirections-onboard/src/test/java/com/mapbox/navigation/route/onboard/MapboxOnboardRouterGenerationTest.kt rename to libdirections-onboard/src/test/java/com/mapbox/navigation/route/onboard/MapboxOnboardRouterTest.kt index d4738b70240..2e3a9fe3185 100644 --- a/libdirections-onboard/src/test/java/com/mapbox/navigation/route/onboard/MapboxOnboardRouterGenerationTest.kt +++ b/libdirections-onboard/src/test/java/com/mapbox/navigation/route/onboard/MapboxOnboardRouterTest.kt @@ -1,19 +1,20 @@ package com.mapbox.navigation.route.onboard import com.mapbox.navigation.navigator.MapboxNativeNavigator +import com.mapbox.navigation.route.onboard.model.Config import io.mockk.mockk import org.junit.Assert import org.junit.Before import org.junit.Test -class MapboxOnboardRouterGenerationTest { - +class MapboxOnboardRouterTest { private lateinit var onboardRouter: MapboxOnboardRouter private val navigator: MapboxNativeNavigator = mockk() + private val tilePath = "tiles" @Before fun setUp() { - onboardRouter = MapboxOnboardRouter(navigator) + onboardRouter = MapboxOnboardRouter(navigator, Config(tilePath)) } @Test diff --git a/libdirections-onboard/src/test/java/com/mapbox/navigation/route/onboard/OfflineRouteTest.kt b/libdirections-onboard/src/test/java/com/mapbox/navigation/route/onboard/OfflineRouteTest.kt new file mode 100644 index 00000000000..a87d048d0a9 --- /dev/null +++ b/libdirections-onboard/src/test/java/com/mapbox/navigation/route/onboard/OfflineRouteTest.kt @@ -0,0 +1,111 @@ +package com.mapbox.navigation.route.onboard + +import com.mapbox.geojson.Point +import com.mapbox.navigation.base.route.RouteUrl +import java.io.UnsupportedEncodingException +import java.net.URLDecoder +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config(manifest = Config.NONE) +class OfflineRouteTest { + + @Test + fun addBicycleTypeIncludedInRequest() { + val routeUrl = provideOnlineRouteBuilder() + val offlineRoute = OfflineRoute.builder(routeUrl) + .bicycleType(OfflineCriteria.BicycleType.ROAD).build() + + val offlineUrl = offlineRoute.buildUrl() + + assertTrue(offlineUrl.contains("bicycle_type=Road")) + } + + @Test + fun addCyclingSpeedIncludedInRequest() { + val routeUrl = provideOnlineRouteBuilder() + val offlineRoute = OfflineRoute.builder(routeUrl) + .cyclingSpeed(10.0f).build() + + val offlineUrl = offlineRoute.buildUrl() + + assertTrue(offlineUrl.contains("cycling_speed=10.0")) + } + + @Test + fun addCyclewayBiasIncludedInRequest() { + val routeUrl = provideOnlineRouteBuilder() + val offlineRoute = OfflineRoute.builder(routeUrl) + .cyclewayBias(0.0f).build() + + val offlineUrl = offlineRoute.buildUrl() + + assertTrue(offlineUrl.contains("cycleway_bias=0.0")) + } + + @Test + fun addHillBiasIncludedInRequest() { + val routeUrl = provideOnlineRouteBuilder() + val offlineRoute = OfflineRoute.builder(routeUrl) + .hillBias(0.0f).build() + + val offlineUrl = offlineRoute.buildUrl() + + assertTrue(offlineUrl.contains("hill_bias=0.0")) + } + + @Test + fun addFerryBiasIncludedInRequest() { + val routeUrl = provideOnlineRouteBuilder() + val offlineRoute = OfflineRoute.builder(routeUrl) + .ferryBias(0.0f).build() + + val offlineUrl = offlineRoute.buildUrl() + + assertTrue(offlineUrl.contains("ferry_bias=0.0")) + } + + @Test + fun addRoughSurfaceBiasIncludedInRequest() { + val routeUrl = provideOnlineRouteBuilder() + val offlineRoute = OfflineRoute.builder(routeUrl) + .roughSurfaceBias(0.0f).build() + + val offlineUrl = offlineRoute.buildUrl() + + assertTrue(offlineUrl.contains("rough_surface_bias=0.0")) + } + + @Test + @Throws(UnsupportedEncodingException::class) + fun addWaypointTypesIncludedInRequest() { + val routeUrl = provideOnlineRouteBuilder() + val waypointTypes = listOf( + OfflineCriteria.WaypointType.BREAK, + OfflineCriteria.WaypointType.THROUGH, + null, + OfflineCriteria.WaypointType.BREAK + ) + val offlineRoute = OfflineRoute.builder(routeUrl) + .waypointTypes(waypointTypes).build() + val offlineUrl = offlineRoute.buildUrl() + + val offlineUrlDecoded = URLDecoder.decode(offlineUrl, "UTF-8") + + assertTrue(offlineUrlDecoded.contains("break;through;;break")) + } + + private fun provideOnlineRouteBuilder(): RouteUrl { + return RouteUrl( + accessToken = "pk.XXX", + profile = RouteUrl.PROFILE_CYCLING, + orgin = Point.fromLngLat(1.0, 2.0), + waypoints = listOf(Point.fromLngLat(3.0, 2.0)), + destination = Point.fromLngLat(1.0, 5.0) + ) + } +} diff --git a/libdirections-onboard/src/test/java/com/mapbox/navigation/route/onboard/task/OfflineRouteRetrievalTaskTest.kt b/libdirections-onboard/src/test/java/com/mapbox/navigation/route/onboard/task/OfflineRouteRetrievalTaskTest.kt new file mode 100644 index 00000000000..5dec5fd28e3 --- /dev/null +++ b/libdirections-onboard/src/test/java/com/mapbox/navigation/route/onboard/task/OfflineRouteRetrievalTaskTest.kt @@ -0,0 +1,74 @@ +package com.mapbox.navigation.route.onboard.task + +import com.mapbox.navigation.base.route.model.Route +import com.mapbox.navigation.navigator.MapboxNativeNavigator +import com.mapbox.navigation.route.onboard.OnOfflineRouteFoundCallback +import com.mapbox.navigation.route.onboard.model.OfflineError +import com.mapbox.navigator.RouterResult +import io.mockk.every +import io.mockk.mockk +import io.mockk.slot +import io.mockk.verify +import org.junit.Assert.assertEquals +import org.junit.Test + +class OfflineRouteRetrievalTaskTest { + + @Test + fun checksOnErrorIsCalledIfRouteIsNotFetched() { + val mockedNavigator = mockk() + val mockedCallback = mockk(relaxed = true) + val mockedResult = mockk() + every { mockedResult.json } returns "{\"status\": \"Bad Request\", \"status_code\": 400, \"error\": \"No suitable edges near location\", \"error_code\": 171}" + val theOfflineRouteRetrievalTask = OfflineRouteRetrievalTask( + mockedNavigator, + mockedCallback, + mockedResult + ) + val nullRoute = null + + theOfflineRouteRetrievalTask.onPostExecute(nullRoute) + + verify { mockedCallback.onError(any()) } + } + + @Test + fun checksErrorMessageIsWellFormedIfRouteIsNotFetched() { + val mockedNavigator = mockk() + val mockedCallback = mockk(relaxed = true) + val slot = slot() + every { mockedCallback.onError(capture(slot)) } answers {} + val mockedResult = mockk() + every { mockedResult.json } returns "{\"status\": \"Bad Request\", \"status_code\": 400, \"error\": \"No suitable edges near location\", \"error_code\": 171}" + val theOfflineRouteRetrievalTask = OfflineRouteRetrievalTask( + mockedNavigator, + mockedCallback, + mockedResult + ) + val nullRoute = null + + theOfflineRouteRetrievalTask.onPostExecute(nullRoute) + + verify { mockedCallback.onError(eq(slot.captured)) } + assertEquals( + "Error occurred fetching offline route: No suitable edges near location - Code: 171", + slot.captured.message + ) + } + + @Test + fun checksOnRouteFoundIsCalledIfRouteIsFetched() { + val mockedNavigator = mockk() + val mockedCallback = mockk(relaxed = true) + val theOfflineRouteRetrievalTask = OfflineRouteRetrievalTask( + mockedNavigator, + null, + mockedCallback + ) + val routes = listOf(mockk()) + + theOfflineRouteRetrievalTask.onPostExecute(routes) + + verify { mockedCallback.onRouteFound(eq(routes)) } + } +} diff --git a/libdirections-onboard/src/test/java/com/mapbox/navigation/route/onboard/task/RemoveTilesTaskTestTest.kt b/libdirections-onboard/src/test/java/com/mapbox/navigation/route/onboard/task/RemoveTilesTaskTestTest.kt new file mode 100644 index 00000000000..ecb3ff731b7 --- /dev/null +++ b/libdirections-onboard/src/test/java/com/mapbox/navigation/route/onboard/task/RemoveTilesTaskTestTest.kt @@ -0,0 +1,28 @@ +package com.mapbox.navigation.route.onboard.task + +import com.mapbox.geojson.Point +import com.mapbox.navigation.route.onboard.OnOfflineTilesRemovedCallback +import com.mapbox.navigator.Navigator +import io.mockk.mockk +import io.mockk.verify +import org.junit.Test + +class RemoveTilesTaskTestTest { + + @Test + fun checksOnRemoveIsCalledWhenTilesAreRemoved() { + val mockedNavigator = mockk() + val aTilePath = "/some/path/version" + val southwest = Point.fromLngLat(1.0, 2.0) + val northeast = Point.fromLngLat(3.0, 4.0) + val mockedCallback = mockk(relaxed = true) + val theRemoveTilesTask = RemoveTilesTask( + mockedNavigator, aTilePath, southwest, + northeast, mockedCallback + ) + + theRemoveTilesTask.onPostExecute(9L) + + verify { mockedCallback.onRemoved(eq(9L)) } + } +} diff --git a/libnavigation-base/build.gradle b/libnavigation-base/build.gradle index 9e6c7e06b25..608b13cdbf5 100644 --- a/libnavigation-base/build.gradle +++ b/libnavigation-base/build.gradle @@ -28,6 +28,6 @@ dependencies { // Unit testing testImplementation dependenciesList.junit testImplementation dependenciesList.mockk + testImplementation dependenciesList.robolectric } - apply from: "${rootDir}/gradle/bintray-publish.gradle" \ No newline at end of file diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/RouteUrl.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/RouteUrl.kt new file mode 100644 index 00000000000..fa6a3f1c1d3 --- /dev/null +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/RouteUrl.kt @@ -0,0 +1,135 @@ +package com.mapbox.navigation.base.route + +import android.net.Uri +import com.mapbox.geojson.Point + +class RouteUrl( + val accessToken: String, + val orgin: Point, + val waypoints: List? = null, + val destination: Point, + val user: String = PROFILE_DEFAULT_USER, + val profile: String = PROFILE_DRIVING, + val steps: Boolean = true, + val geometries: String = GEOMETRY_POLYLINE6, + val overview: String = OVERVIEW_FULL, + val voiceIntruction: Boolean = true, + val bannerIntruction: Boolean = true, + val roundaboutExits: Boolean = true, + val enableRefresh: Boolean = true +) { + + companion object { + const val BASE_URL = "https://api.mapbox.com" + + const val BASE_URL_API_NAME = "directions" + const val BASE_URL_API_VERSION = "v5" + + private const val QUERY_PARAM_ACCESS_TOKEN = "access_token" + private const val QUERY_PARAM_STEPS = "steps" + private const val QUERY_PARAM_GEOMERTY = "geometries" + private const val QUERY_PARAM_OVERVIEW = "overview" + private const val QUERY_PARAM_VOICE_INSTRUCTIONS = "voice_instructions" + private const val QUERY_PARAM_BANNER_INSTRUCTIONS = "banner_instructions" + private const val QUERY_PARAM_ROUNDABOUT_EXITS = "roundabout_exits" + private const val QUERY_PARAM_ENABLE_REFRESH = "enable_refresh" + + /** + * Mapbox default username. + * + * @since 1.0 + */ + const val PROFILE_DEFAULT_USER = "mapbox" + /** + * For car and motorcycle routing. This profile factors in current and historic traffic + * conditions to avoid slowdowns. + * + * @since 1.0 + */ + const val PROFILE_DRIVING_TRAFFIC = "driving-traffic" + + /** + * For car and motorcycle routing. This profile shows the fastest routes by preferring + * high-speed roads like highways. + * + * @since 1.0 + */ + const val PROFILE_DRIVING = "driving" + + /** + * For pedestrian and hiking routing. This profile shows the shortest path by using sidewalks + * and trails. + * + * @since 1.0 + */ + const val PROFILE_WALKING = "walking" + + /** + * For bicycle routing. This profile shows routes that are short and safe for cyclist, avoiding + * highways and preferring streets with bike lanes. + * + * @since 1.0 + */ + const val PROFILE_CYCLING = "cycling" + + /** + * Format to return route geometry will be an encoded polyline. + * + * @since 1.0 + */ + const val GEOMETRY_POLYLINE = "polyline" + + /** + * Format to return route geometry will be an encoded polyline with precision 6. + * + * @since 1.0 + */ + const val GEOMETRY_POLYLINE6 = "polyline6" + + /** + * A simplified version of the [.OVERVIEW_FULL] geometry. If not specified simplified is + * the default. + * + * @since 1.0 + */ + const val OVERVIEW_SIMPLIFIED = "simplified" + + /** + * The most detailed geometry available. + * + * @since 1.0 + */ + const val OVERVIEW_FULL = "full" + + /** + * No overview geometry. + * + * @since 1.0 + */ + const val OVERVIEW_FALSE = "false" + } + + fun getRequest(): Uri = + Uri.parse(BASE_URL) + .buildUpon() + .appendPath(BASE_URL_API_NAME) + .appendPath(BASE_URL_API_VERSION) + .appendPath(user) + .appendPath(profile) + .appendPath(retrieveCoordinates()) + .appendQueryParameter(QUERY_PARAM_ACCESS_TOKEN, accessToken) + .appendQueryParameter(QUERY_PARAM_STEPS, steps.toString()) + .appendQueryParameter(QUERY_PARAM_GEOMERTY, geometries) + .appendQueryParameter(QUERY_PARAM_OVERVIEW, overview) + .appendQueryParameter(QUERY_PARAM_VOICE_INSTRUCTIONS, voiceIntruction.toString()) + .appendQueryParameter(QUERY_PARAM_BANNER_INSTRUCTIONS, bannerIntruction.toString()) + .appendQueryParameter(QUERY_PARAM_ROUNDABOUT_EXITS, roundaboutExits.toString()) + .appendQueryParameter(QUERY_PARAM_ENABLE_REFRESH, enableRefresh.toString()) + .build() + + private fun retrieveCoordinates(): String { + val route: List = listOf(orgin) + (waypoints ?: emptyList()) + destination + + return route.joinToString(separator = ";") { "${it.longitude()},${it.latitude()}" } + } +} diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/BannerComponentsNavigationDto.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/BannerComponentsNavigationDto.kt new file mode 100644 index 00000000000..c413429ff2b --- /dev/null +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/BannerComponentsNavigationDto.kt @@ -0,0 +1,27 @@ +package com.mapbox.navigation.base.route.dto + +import com.google.gson.annotations.SerializedName +import com.mapbox.navigation.base.route.model.BannerComponentsNavigation + +class BannerComponentsNavigationDto( + val text: String, + val type: String, + @SerializedName("abbr") + val abbreviation: String?, + @SerializedName("abbr_priority") + val abbreviationPriority: Int?, + @SerializedName("imageBaseURL") + val imageBaseUrl: String?, + val directions: List?, + val active: Boolean? +) + +fun BannerComponentsNavigationDto.mapToModel() = BannerComponentsNavigation( + text = text, + type = type, + abbreviation = abbreviation, + abbreviationPriority = abbreviationPriority, + imageBaseUrl = imageBaseUrl, + directions = directions, + active = active +) diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/BannerInstructionsNavigationDto.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/BannerInstructionsNavigationDto.kt new file mode 100644 index 00000000000..edf9f5d354f --- /dev/null +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/BannerInstructionsNavigationDto.kt @@ -0,0 +1,17 @@ +package com.mapbox.navigation.base.route.dto + +import com.mapbox.navigation.base.route.model.BannerInstructionsNavigation + +class BannerInstructionsNavigationDto( + val distanceAlongGeometry: Double, + val primary: BannerTextNavigationDto?, + val secondary: BannerTextNavigationDto?, + val sub: BannerTextNavigationDto? +) + +fun BannerInstructionsNavigationDto.mapToModel() = BannerInstructionsNavigation( + distanceAlongGeometry = distanceAlongGeometry, + primary = primary?.mapToModel(), + secondary = secondary?.mapToModel(), + sub = sub?.mapToModel() +) diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/BannerTextNavigationDto.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/BannerTextNavigationDto.kt new file mode 100644 index 00000000000..212b76a0aa9 --- /dev/null +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/BannerTextNavigationDto.kt @@ -0,0 +1,23 @@ +package com.mapbox.navigation.base.route.dto + +import com.google.gson.annotations.SerializedName +import com.mapbox.navigation.base.route.model.BannerTextNavigation + +class BannerTextNavigationDto( + val text: String?, + val components: List?, + val type: String?, + val modifier: String?, + val degrees: Double?, + @SerializedName("driving_side") + val drivingSide: String? +) + +fun BannerTextNavigationDto.mapToModel() = BannerTextNavigation( + text = text, + components = components?.map { it.mapToModel() }, + type = type, + modifier = modifier, + degrees = degrees, + drivingSide = drivingSide +) diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/IntersectionLanesNavigationDto.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/IntersectionLanesNavigationDto.kt new file mode 100644 index 00000000000..3e0b17a499e --- /dev/null +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/IntersectionLanesNavigationDto.kt @@ -0,0 +1,13 @@ +package com.mapbox.navigation.base.route.dto + +import com.mapbox.navigation.base.route.model.IntersectionLanesNavigation + +class IntersectionLanesNavigationDto( + val valid: Boolean?, + val indications: List? +) + +fun IntersectionLanesNavigationDto.mapToModel() = IntersectionLanesNavigation( + valid = valid, + indications = indications +) diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/LegAnnotationNavigationDto.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/LegAnnotationNavigationDto.kt new file mode 100644 index 00000000000..ad80dee07a7 --- /dev/null +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/LegAnnotationNavigationDto.kt @@ -0,0 +1,19 @@ +package com.mapbox.navigation.base.route.dto + +import com.mapbox.navigation.base.route.model.LegAnnotationNavigation + +class LegAnnotationNavigationDto( + val distance: List?, + val duration: List?, + val speed: List?, + val maxspeed: List?, + val congestion: List? +) + +fun LegAnnotationNavigationDto.mapToModel() = LegAnnotationNavigation( + distance = distance, + duration = duration, + speed = speed, + maxspeed = maxspeed?.map(MaxSpeedNavigationDto::mapToModel), + congestion = congestion +) diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/LegStepNavigationDto.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/LegStepNavigationDto.kt new file mode 100644 index 00000000000..a448632f06d --- /dev/null +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/LegStepNavigationDto.kt @@ -0,0 +1,35 @@ +package com.mapbox.navigation.base.route.dto + +import com.google.gson.annotations.SerializedName +import com.mapbox.navigation.base.route.model.LegStepNavigation + +class LegStepNavigationDto( + val distance: Double, + val duration: Double, + val geometry: String?, + val name: String?, + val ref: String?, + val destinations: String?, + val mode: String, + val pronunciation: String?, + @SerializedName("rotary_name") + val rotaryName: String?, + @SerializedName("rotary_pronunciation") + val rotaryPronunciation: String?, + val maneuver: StepManeuverNavigationDto, + val voiceInstructions: List?, + val bannerInstructions: List?, + @SerializedName("driving_side") + val drivingSide: String?, + val weight: Double, + val intersections: List?, + val exits: String? +) + +fun LegStepNavigationDto.mapToModel() = LegStepNavigation.Builder() + .distance(distance) + .drivingSide(drivingSide) + .duration(duration) + .geometry(geometry) + .stepManeuver(maneuver.mapToModel()) + .build() diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/MaxSpeedNavigationDto.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/MaxSpeedNavigationDto.kt new file mode 100644 index 00000000000..78aa10812be --- /dev/null +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/MaxSpeedNavigationDto.kt @@ -0,0 +1,17 @@ +package com.mapbox.navigation.base.route.dto + +import com.mapbox.navigation.base.route.model.MaxSpeedNavigation + +class MaxSpeedNavigationDto( + val speed: Int?, + val unit: String?, + val unknown: Boolean?, + val none: Boolean? +) + +fun MaxSpeedNavigationDto.mapToModel() = MaxSpeedNavigation( + speed = speed, + unit = unit, + unknown = unknown, + none = none +) diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/RouteDto.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/RouteDto.kt new file mode 100644 index 00000000000..25c86a05ad1 --- /dev/null +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/RouteDto.kt @@ -0,0 +1,27 @@ +package com.mapbox.navigation.base.route.dto + +import com.mapbox.navigation.base.route.model.Route + +class RouteDto( + val routeIndex: String?, + val distance: Double, + val duration: Long, + val geometry: String?, + val weight: Double?, + val weightName: String?, + val legs: List?, + val routeOptions: RouteOptionsNavigationDto?, + val voiceLanguage: String? +) + +fun RouteDto.mapToModelRoute() = Route( + routeIndex = routeIndex, + distance = distance, + duration = duration, + geometry = geometry, + weight = weight, + weightName = weightName, + legs = legs?.map { it.mapToRouteLegNavigation() }, + routeOptions = routeOptions?.mapToModel(), + voiceLanguage = voiceLanguage +) diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/RouteLegNavigationDto.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/RouteLegNavigationDto.kt new file mode 100644 index 00000000000..8fc1b1939a1 --- /dev/null +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/RouteLegNavigationDto.kt @@ -0,0 +1,19 @@ +package com.mapbox.navigation.base.route.dto + +import com.mapbox.navigation.base.route.model.RouteLegNavigation + +class RouteLegNavigationDto( + val distance: Double?, + val duration: Double?, + val summary: String?, + val steps: List?, + val annotation: LegAnnotationNavigationDto? +) + +fun RouteLegNavigationDto.mapToRouteLegNavigation(): RouteLegNavigation = + RouteLegNavigation.Builder() + .distance(distance) + .duration(duration) + .summary(summary) + .steps(steps?.map { it.mapToModel() }) + .build() diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/RouteOptionsNavigationDto.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/RouteOptionsNavigationDto.kt new file mode 100644 index 00000000000..debbf297c9a --- /dev/null +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/RouteOptionsNavigationDto.kt @@ -0,0 +1,91 @@ +package com.mapbox.navigation.base.route.dto + +import com.google.gson.annotations.SerializedName +import com.mapbox.geojson.Point +import com.mapbox.navigation.base.route.RouteUrl +import com.mapbox.navigation.base.route.model.RouteOptionsNavigation + +data class RouteOptionsNavigationDto( + val baseUrl: String?, + val user: String?, + val profile: String?, + val coordinates: List, + val alternatives: Boolean?, + val language: String?, + val radiuses: String?, + val bearings: String?, + @SerializedName("continue_straight") + val continueStraight: Boolean?, + @SerializedName("roundabout_exits") + val roundaboutExits: Boolean?, + val geometries: String?, + val overview: String?, + val steps: Boolean?, + val annotations: String?, + @SerializedName("voice_instructions") + val voiceInstructions: Boolean?, + @SerializedName("banner_instructions") + val bannerInstructions: Boolean?, + @SerializedName("voice_units") + val voiceUnits: String?, + @SerializedName("access_token") + val accessToken: String?, + @SerializedName("uuid") + val requestUuid: String?, + val exclude: String?, + val approaches: String?, + @SerializedName("waypoints") + val waypointIndices: String?, + @SerializedName("waypoint_names") + val waypointNames: String?, + @SerializedName("waypoint_targets") + val waypointTargets: String?, + val walkingOptions: WalkingOptionsNavigationDto? +) + +fun RouteOptionsNavigationDto.mapToModel() = RouteOptionsNavigation( + baseUrl = baseUrl ?: RouteUrl.BASE_URL, + user = user ?: RouteUrl.PROFILE_DEFAULT_USER, + profile = profile ?: RouteUrl.PROFILE_DRIVING, + origin = coordinates.retrieveOrigin().mapToModel(), + waypoints = coordinates.retrieveWaypoints().map { it.mapToModel() }, + destination = coordinates.retrieveDestination().mapToModel(), + alternatives = alternatives ?: RouteOptionsNavigation.ALTERNATIVES_DEFAULT_VALUE, + language = language, + radiuses = radiuses, + bearings = bearings, + continueStraight = continueStraight ?: RouteOptionsNavigation.CONTINUE_STRAIGHT_DEFAULT_VALUE, + roundaboutExits = roundaboutExits ?: RouteOptionsNavigation.ROUNDABOUT_EXITS_DEFAULT_VALUE, + geometries = geometries, + overview = overview, + steps = steps ?: RouteOptionsNavigation.STEPS_DEFAULT_VALUE, + annotations = annotations, + voiceInstructions = voiceInstructions + ?: RouteOptionsNavigation.VOICE_INSTRUCTIONS_DEFAULT_VALUE, + bannerInstructions = bannerInstructions + ?: RouteOptionsNavigation.BANNER_INSTRUCTIONS_DEFAULT_VALUE, + voiceUnits = voiceUnits, + accessToken = accessToken ?: "", + requestUuid = requestUuid, + exclude = exclude, + approaches = approaches, + waypointIndices = waypointIndices, + waypointNames = waypointNames, + waypointTargets = waypointTargets, + walkingOptions = walkingOptions?.mapToModel() +) + +private fun List.retrieveOrigin(): RoutePointNavigationDto = + this.first().let { RoutePointNavigationDto(Point.fromLngLat(it[0], it[1]), null, null) } + +private fun List.retrieveWaypoints(): List = + this.drop(1).dropLast(1).map { + RoutePointNavigationDto( + Point.fromLngLat(it[0], it[1]), + null, + null + ) + } + +private fun List.retrieveDestination(): RoutePointNavigationDto = + this.last().let { RoutePointNavigationDto(Point.fromLngLat(it[0], it[1]), null, null) } diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/RoutePointNavigationDto.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/RoutePointNavigationDto.kt new file mode 100644 index 00000000000..f1be38a08d5 --- /dev/null +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/RoutePointNavigationDto.kt @@ -0,0 +1,16 @@ +package com.mapbox.navigation.base.route.dto + +import com.mapbox.geojson.Point +import com.mapbox.navigation.base.route.model.RoutePointNavigation + +data class RoutePointNavigationDto( + val point: Point, + val bearingAngle: Double?, + val tolerance: Double? +) + +fun RoutePointNavigationDto.mapToModel() = RoutePointNavigation( + point = point, + bearingAngle = bearingAngle, + tolerance = tolerance +) diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/RouteResponseDto.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/RouteResponseDto.kt new file mode 100644 index 00000000000..16ecc4315ed --- /dev/null +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/RouteResponseDto.kt @@ -0,0 +1,17 @@ +package com.mapbox.navigation.base.route.dto + +import com.mapbox.navigation.base.route.model.RouteResponse + +class RouteResponseDto( + val message: String?, + val code: String?, + val uuid: String?, + val routes: List? +) + +fun RouteResponseDto.mapToModel() = RouteResponse( + message = message, + code = code, + uuid = uuid, + routes = routes?.map(RouteDto::mapToModelRoute) +) diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/StepIntersectionNavigationDto.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/StepIntersectionNavigationDto.kt new file mode 100644 index 00000000000..746363c448a --- /dev/null +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/StepIntersectionNavigationDto.kt @@ -0,0 +1,26 @@ +package com.mapbox.navigation.base.route.dto + +import com.google.gson.annotations.SerializedName +import com.mapbox.geojson.Point +import com.mapbox.navigation.base.route.model.StepIntersectionNavigation + +class StepIntersectionNavigationDto( + @SerializedName("location") + val rawLocation: DoubleArray, + val bearings: List?, + val classes: List?, + val entry: List?, + val `in`: Int?, + val out: Int?, + val lanes: List? +) + +fun StepIntersectionNavigationDto.mapToModel() = StepIntersectionNavigation( + location = Point.fromLngLat(rawLocation[0], rawLocation[1]), + bearings = bearings, + classes = classes, + entry = entry, + into = `in`, + out = out, + lanes = lanes?.map(IntersectionLanesNavigationDto::mapToModel) +) diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/StepManeuverNavigationDto.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/StepManeuverNavigationDto.kt new file mode 100644 index 00000000000..853764bca91 --- /dev/null +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/StepManeuverNavigationDto.kt @@ -0,0 +1,24 @@ +package com.mapbox.navigation.base.route.dto + +import com.google.gson.annotations.SerializedName +import com.mapbox.navigation.base.route.model.StepManeuverNavigation +import com.mapbox.navigation.base.route.model.StepManeuverType + +class StepManeuverNavigationDto( + @SerializedName("location") + val rawLocation: DoubleArray, + @SerializedName("bearing_before") + val bearingBefore: Double?, + @SerializedName("bearing_after") + val bearingAfter: Double?, + val instruction: String?, + @StepManeuverType + val type: String?, + val modifier: String?, + val exit: Int? +) + +fun StepManeuverNavigationDto.mapToModel() = StepManeuverNavigation.Builder() + .modifier(modifier) + .type(type) + .build() diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/VoiceInstructionsNavigationDto.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/VoiceInstructionsNavigationDto.kt new file mode 100644 index 00000000000..ebddf3640ce --- /dev/null +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/VoiceInstructionsNavigationDto.kt @@ -0,0 +1,15 @@ +package com.mapbox.navigation.base.route.dto + +import com.mapbox.navigation.base.route.model.VoiceInstructionsNavigation + +class VoiceInstructionsNavigationDto( + val distanceAlongGeometry: Double?, + val announcement: String?, + val ssmlAnnouncement: String? +) + +fun VoiceInstructionsNavigationDto.mapToModel() = VoiceInstructionsNavigation( + distanceAlongGeometry = distanceAlongGeometry, + announcement = announcement, + ssmlAnnouncement = ssmlAnnouncement +) diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/WalkingOptionsNavigationDto.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/WalkingOptionsNavigationDto.kt new file mode 100644 index 00000000000..176f1a1dc02 --- /dev/null +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/dto/WalkingOptionsNavigationDto.kt @@ -0,0 +1,34 @@ +package com.mapbox.navigation.base.route.dto + +import com.google.gson.annotations.SerializedName +import com.mapbox.navigation.base.route.model.WalkingOptionsNavigation + +/** + * Class for specifying options for use with the walking profile. + * + * @param walkingSpeed Walking speed in meters per second. Must be between 0.14 and 6.94 meters per second. + * Defaults to 1.42 meters per second + * + * @param walkwayBias A bias which determines whether the route should prefer or avoid the use of roads or paths + * that are set aside for pedestrian-only use (walkways). The allowed range of values is from + * -1 to 1, where -1 indicates indicates preference to avoid walkways, 1 indicates preference + * to favor walkways, and 0 indicates no preference (the default). + * + * @param alleyBias A bias which determines whether the route should prefer or avoid the use of alleys. The + * allowed range of values is from -1 to 1, where -1 indicates indicates preference to avoid + * alleys, 1 indicates preference to favor alleys, and 0 indicates no preference (the default). + */ +class WalkingOptionsNavigationDto( + @SerializedName("walking_speed") + val walkingSpeed: Double?, + @SerializedName("walkway_bias") + val walkwayBias: Double?, + @SerializedName("alley_bias") + val alleyBias: Double? +) + +fun WalkingOptionsNavigationDto.mapToModel() = WalkingOptionsNavigation( + walkingSpeed = walkingSpeed, + walkwayBias = walkwayBias, + alleyBias = alleyBias +) diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/BannerComponentsNavigation.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/BannerComponentsNavigation.kt new file mode 100644 index 00000000000..8ef1049c92f --- /dev/null +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/BannerComponentsNavigation.kt @@ -0,0 +1,62 @@ +package com.mapbox.navigation.base.route.model + +/** + * + * @property text A snippet of the full [BannerTextNavigation.text] which can be used for visually + * altering parts of the full string. + * @since 1.0 + * + * @property type String giving you more context about the component which may help in visual markup/display + * choices. If the type of the components is unknown it should be treated as text. + * + * Possible values: + * + * * **text (default)**: indicates the text is part of + * the instructions and no other type + * * **icon**: this is text that can be replaced by an icon, see imageBaseURL + * * **delimiter**: this is text that can be dropped and + * should be dropped if you are rendering icons + * * **exit-number**: the exit number for the maneuver + * * **exit**: the word for exit in the local language + * @since 1.0 + * + * @property abbreviation The abbreviated form of text. + * + * If this is present, there will also be an abbr_priority value + * @since 1.0 + * + * @property abbreviationPriority An integer indicating the order in which the abbreviation abbr should be used in + * place of text. The highest priority is 0 and a higher integer value indicates a lower + * priority. There are no gaps in integer values. + * + * Multiple components can have the same abbreviationPriority and when this happens all + * components with the same abbr_priority should be abbreviated at the same time. + * Finding no larger values of abbreviationPriority indicates that the string is + * fully abbreviated. + * @since 1.0 + * + * @property imageBaseUrl In some cases when the [LegStepNavigation] is a highway or major roadway, + * there might be a shield icon that's included to better identify to your user to roadway. + * Note that this doesn't return the image itself but rather the url which can be used to download the file. + * @since 1.0 + * + * @property directions A List of directions indicating which way you can go from a lane + * (left, right, or straight). If the value is ['left', 'straight'], + * the driver can go straight or left from that lane. + * Present if this is a lane component. + * @since 1.0 + * + * @property active A boolean telling you if that lane can be used to complete the upcoming maneuver. + * If multiple lanes are active, then they can all be used to complete the upcoming maneuver. + * Present if this is a lane component. + * @since 1.0 + */ +data class BannerComponentsNavigation( + val text: String, + val type: String, + val abbreviation: String?, + val abbreviationPriority: Int?, + val imageBaseUrl: String?, + val directions: List?, + val active: Boolean? +) diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/BannerInstructionsNavigation.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/BannerInstructionsNavigation.kt new file mode 100644 index 00000000000..97553f6f58d --- /dev/null +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/BannerInstructionsNavigation.kt @@ -0,0 +1,31 @@ +package com.mapbox.navigation.base.route.model + +/** + * Visual instruction information related to a particular [LegStepNavigation] useful for making UI + * elements inside your application such as banners. To receive this information, your request must + * have {@link MapboxDirections#bannerInstructions()} set to true. + * + * @property distanceAlongGeometry Distance in meters from the beginning of the step at which the visual instruction should be + * visible. + * @since 1.0 + * + * @property primary A plain text representation stored inside a [BannerTextNavigation] object. + * @since 1.0 + * + * @property secondary Ancillary visual information about the [LegStepNavigation]. + * @return [BannerTextNavigation] representing the secondary visual information + * @since 1.0 + * + * @property sub Additional information that is included if we feel the driver needs a heads up about something. + * Can include information about the next maneuver (the one after the upcoming one), + * if the step is short - can be null, or can be lane information. + * If we have lane information, that trumps information about the next maneuver. + * @return [BannerTextNavigation] representing the sub visual information + * @since 1.0 + */ +class BannerInstructionsNavigation( + val distanceAlongGeometry: Double, + val primary: BannerTextNavigation?, + val secondary: BannerTextNavigation?, + val sub: BannerTextNavigation? +) diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/BannerTextNavigation.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/BannerTextNavigation.kt new file mode 100644 index 00000000000..f34fe118371 --- /dev/null +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/BannerTextNavigation.kt @@ -0,0 +1,41 @@ +package com.mapbox.navigation.base.route.model + +/** + * Includes both plain text information that can be visualized inside your navigation application + * along with the text string broken down into {@link BannerComponents} which may or may not + * include a image url. To receive this information, your request must have + * {@link MapboxDirections#bannerInstructions()} set to true. + * + * @property text Plain text with all the [BannerComponentsNavigation] text combined. + * @since 1.0 + * + * @property components A part or element of the [BannerInstructionsNavigation]. + * Return [BannerComponentsNavigation] specific to a [LegStepNavigation] + * @since 1.0 + * + * @property type This indicates the type of maneuver. + * @see StepManeuverNavigation.StepManeuverTypeNavigation + * @since 1.0 + * + * @property modifier This indicates the mode of the maneuver. If type is of turn, the modifier indicates the + * change in direction accomplished through the turn. If the type is of depart/arrive, the + * modifier indicates the position of waypoint from the current direction of travel. + * @since 1.0 + * + * @property degrees The degrees at which you will be exiting a roundabout, assuming `180` indicates + * going straight through the roundabout. + * @since 1.0 + * + * @property drivingSide A string representing which side the of the street people drive on + * in that location. Can be 'left' or 'right'. + * @since 1.0 + */ +class BannerTextNavigation( + val text: String?, + val components: List?, + @StepManeuverType + val type: String?, + val modifier: String?, + val degrees: Double?, + val drivingSide: String? +) diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/IntersectionLanesNavigation.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/IntersectionLanesNavigation.kt new file mode 100644 index 00000000000..088de249a33 --- /dev/null +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/IntersectionLanesNavigation.kt @@ -0,0 +1,18 @@ +package com.mapbox.navigation.base.route.model + +/** + * + * @property valid Boolean value for whether this lane can be taken to complete the maneuver. For + * instance, if the lane array has four objects and the first two are marked as valid, then the + * driver can take either of the left lanes and stay on the route. + * @since 1.0 + * + * @property indications Array of signs for each turn lane. There can be multiple signs. For example, a turning + * lane can have a sign with an arrow pointing left and another sign with an arrow pointing + * straight. + * @since 1.0 + */ +class IntersectionLanesNavigation( + val valid: Boolean?, + val indications: List? +) diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/LegAnnotationNavigation.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/LegAnnotationNavigation.kt new file mode 100644 index 00000000000..0bdcb689920 --- /dev/null +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/LegAnnotationNavigation.kt @@ -0,0 +1,28 @@ +package com.mapbox.navigation.base.route.model + +/** + * + * @property distance The distance, in meters, between each pair of coordinates. + * @since 1.0 + * + * @property duration The speed, in meters per second, between each pair of coordinates. + * @since 1.0 + * + * @property speed The speed, in meters per second, between each pair of coordinates. + * @since 1.0 + * + * @property maxspeed The posted speed limit, between each pair of coordinates. + * Maxspeed is only available for the `mapbox/driving` and `mapbox/driving-traffic` + * profiles, other profiles will return `unknown`s only. + * @since 1.0 + * + * @property congestion The congestion between each pair of coordinates. + * @since 1.0 + */ +data class LegAnnotationNavigation( + val distance: List?, + val duration: List?, + val speed: List?, + val maxspeed: List?, + val congestion: List? +) diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/MaxSpeedNavigation.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/MaxSpeedNavigation.kt new file mode 100644 index 00000000000..f8cc70db65d --- /dev/null +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/MaxSpeedNavigation.kt @@ -0,0 +1,22 @@ +package com.mapbox.navigation.base.route.model + +/** + * + * @property speed Number indicating the posted speed limit. + * @since 1.0 + * + * @property unit String indicating the unit of speed, either as `km/h` or `mph`. + * @since 1.0 + * + * @property unknown Boolean is true if the speed limit is not known, otherwise null. + * @since 1.0 + * + * @property none Boolean is `true` if the speed limit is unlimited, otherwise null. + * @since 1.0 + */ +class MaxSpeedNavigation( + val speed: Int?, + val unit: String?, + val unknown: Boolean?, + val none: Boolean? +) diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/RouteOptionsNavigation.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/RouteOptionsNavigation.kt index 7656e306525..061a01667dc 100644 --- a/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/RouteOptionsNavigation.kt +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/RouteOptionsNavigation.kt @@ -1,66 +1,75 @@ package com.mapbox.navigation.base.route.model -import com.google.gson.annotations.SerializedName import com.mapbox.geojson.Point - -class RouteOptionsNavigation private constructor( - val baseUrl: String?, - val user: String?, - val profile: String?, - val coordinates: List, - val alternatives: Boolean?, +import com.mapbox.navigation.base.route.RouteUrl + +class RouteOptionsNavigation( + val baseUrl: String, + val user: String, + val profile: String, + val origin: RoutePointNavigation, + val waypoints: List, + val destination: RoutePointNavigation, + val alternatives: Boolean, val language: String?, val radiuses: String?, val bearings: String?, - @SerializedName("continue_straight") val continueStraight: Boolean?, - @SerializedName("roundabout_exits") val roundaboutExits: Boolean?, + val continueStraight: Boolean, + val roundaboutExits: Boolean, val geometries: String?, val overview: String?, - val steps: Boolean?, + val steps: Boolean, val annotations: String?, - @SerializedName("voice_instructions") val voiceInstructions: Boolean?, - @SerializedName("banner_instructions") val bannerInstructions: Boolean?, - @SerializedName("voice_units") val voiceUnits: String?, - val accessToken: String?, - @SerializedName("uuid") val requestUuid: String?, + val voiceInstructions: Boolean, + val bannerInstructions: Boolean, + val voiceUnits: String?, + val accessToken: String, + val requestUuid: String?, val exclude: String?, val approaches: String?, - @SerializedName("waypoints") val waypointIndices: String?, - @SerializedName("waypoint_names") val waypointNames: String?, - @SerializedName("waypoint_targets") val waypointTargets: String?, + val waypointIndices: String?, + val waypointNames: String?, + val waypointTargets: String?, val walkingOptions: WalkingOptionsNavigation? ) { companion object { @JvmStatic - fun builder(): Builder { - return Builder() - } + fun builder(): Builder = Builder() + + const val ALTERNATIVES_DEFAULT_VALUE = false + const val STEPS_DEFAULT_VALUE = true + const val CONTINUE_STRAIGHT_DEFAULT_VALUE = false + const val ROUNDABOUT_EXITS_DEFAULT_VALUE = false + const val VOICE_INSTRUCTIONS_DEFAULT_VALUE = true + const val BANNER_INSTRUCTIONS_DEFAULT_VALUE = true } - class Builder internal constructor() { - private var _origin: RoutePointNavigation? = null - private var _destination: RoutePointNavigation? = null + val coordinates: List + get() = listOf(origin.point) + waypoints.map { it.point } + destination.point + + class Builder { + private lateinit var _origin: RoutePointNavigation + private lateinit var _destination: RoutePointNavigation private val _waypoints = mutableListOf() + private lateinit var _accessToken: String private var baseUrl: String? = null private var user: String? = null private var profile: String? = null - private val coordinates = mutableListOf() - private var alternatives: Boolean? = null + private var alternatives: Boolean = ALTERNATIVES_DEFAULT_VALUE private var language: String? = null private var radiuses: String? = null private var bearings: String? = null - private var continueStraight: Boolean? = null - private var roundaboutExits: Boolean? = null + private var continueStraight: Boolean = CONTINUE_STRAIGHT_DEFAULT_VALUE + private var roundaboutExits: Boolean = ROUNDABOUT_EXITS_DEFAULT_VALUE private var geometries: String? = null private var overview: String? = null - private var steps: Boolean? = null + private var steps: Boolean = STEPS_DEFAULT_VALUE private var annotations: String? = null - private var voiceInstructions: Boolean? = null - private var bannerInstructions: Boolean? = null + private var voiceInstructions: Boolean = VOICE_INSTRUCTIONS_DEFAULT_VALUE + private var bannerInstructions: Boolean = BANNER_INSTRUCTIONS_DEFAULT_VALUE private var voiceUnits: String? = null - private var accessToken: String? = null private var requestUuid: String? = null private var exclude: String? = null private var approaches: String? = null @@ -133,7 +142,7 @@ class RouteOptionsNavigation private constructor( fun voiceUnits(voiceUnits: String): Builder = also { this.voiceUnits = voiceUnits } - fun accessToken(accessToken: String): Builder = also { this.accessToken = accessToken } + fun accessToken(accessToken: String): Builder = also { this._accessToken = accessToken } fun requestUuid(requestUuid: String): Builder = also { this.requestUuid = requestUuid } @@ -154,12 +163,14 @@ class RouteOptionsNavigation private constructor( also { this.walkingOptions = walkingOptions } fun build(): RouteOptionsNavigation { - assembleCoordinates() + checkFields() return RouteOptionsNavigation( - baseUrl, - user, - profile, - coordinates, + baseUrl ?: RouteUrl.BASE_URL, + user ?: RouteUrl.PROFILE_DEFAULT_USER, + profile ?: RouteUrl.PROFILE_DRIVING, + _origin, + _waypoints, + _destination, alternatives, language, radiuses, @@ -173,7 +184,7 @@ class RouteOptionsNavigation private constructor( voiceInstructions, bannerInstructions, voiceUnits, - accessToken, + _accessToken, requestUuid, exclude, approaches, @@ -184,18 +195,12 @@ class RouteOptionsNavigation private constructor( ) } - private fun assembleCoordinates() { - _origin?.let { origin -> - coordinates.add(origin) - } + private fun checkFields() { + check(::_origin.isInitialized) { "Property origin hasn't been initialized" } - for (waypoint in _waypoints) { - coordinates.add(waypoint) - } + check(::_destination.isInitialized) { "Property destination hasn't been initialized" } - _destination?.let { destination -> - coordinates.add(destination) - } + check(::_accessToken.isInitialized) { "Property accessToken hasn't been initialized" } } } } diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/RouteResponse.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/RouteResponse.kt new file mode 100644 index 00000000000..7d941eec275 --- /dev/null +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/RouteResponse.kt @@ -0,0 +1,8 @@ +package com.mapbox.navigation.base.route.model + +class RouteResponse( + val message: String?, + val code: String?, + val uuid: String?, + val routes: List? +) diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/StepIntersectionNavigation.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/StepIntersectionNavigation.kt new file mode 100644 index 00000000000..b3aa567768b --- /dev/null +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/StepIntersectionNavigation.kt @@ -0,0 +1,69 @@ +package com.mapbox.navigation.base.route.model + +import com.mapbox.geojson.Point + +/** + * Object representing an intersection along the step. + * + * @property location A [Point] representing this intersection location. + * @since 1.0 + * + * @property bearings An integer list of bearing values available at the step intersection. + * @return An array of bearing values (for example [0,90,180,270]) that are available at the + * intersection. The bearings describe all available roads at the intersection. + * @since 1.0 + * + * @property classes A list of strings signifying the classes of the road exiting the intersection. Possible + * values: + * + * * **toll**: the road continues on a toll road + * * **ferry**: the road continues on a ferry + * * **restricted**: the road continues on with access restrictions + * * **motorway**: the road continues on a motorway + * * **tunnel**: the road continues on a tunnel + * + * @return a string list containing the classes of the road exiting the intersection + * @since 1.0 + * + * @property entry A list of entry flags, corresponding in a 1:1 relationship to the bearings. A value of true + * indicates that the respective road could be entered on a valid route. false indicates that the + * turn onto the respective road would violate a restriction. + * + * @return a list of entry flags, corresponding in a 1:1 relationship to the bearings + * @since 1.0 + * + * @property into 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 + * intersection. To get the bearing in the direction of driving, the bearing has to be rotated by + * a value of 180. The value is not supplied for departure + * maneuvers. + * + * @return index into bearings/entry array + * @since 1.0 + * + * @property out Index out of the bearings/entry array. Used to extract the bearing after the turn. Namely, The + * clockwise angle from true north to the direction of travel after the maneuver/passing the + * intersection. The value is not supplied for arrive maneuvers. + * + * @return index out of the bearings/entry array + * @since 1.0 + * + * @property lanes Array of lane objects that represent the available turn lanes at the intersection. If no lane + * information is available for an intersection, the lanes property will not be present. Lanes are + * provided in their order on the street, from left to right. + * + * @return array of lane objects that represent the available turn lanes at the intersection + * @since 1.0 + */ +class StepIntersectionNavigation( + val location: Point, + val bearings: List?, + val classes: List?, + val entry: List?, + val into: Int?, + val out: Int?, + val lanes: List? +) { + val rawLocation: DoubleArray + get() = doubleArrayOf(location.longitude(), location.latitude()) +} diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/VoiceInstructionsNavigation.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/VoiceInstructionsNavigation.kt new file mode 100644 index 00000000000..f6511ba6e58 --- /dev/null +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/VoiceInstructionsNavigation.kt @@ -0,0 +1,22 @@ +package com.mapbox.navigation.base.route.model + +/** + * + * @property distanceAlongGeometry This provides the missing piece in which is needed to announce + * instructions at accuratetimes. If the user is less distance away from the maneuver than + * what this `distanceAlongGeometry` than, the announcement should be called. + * @since 1.0 + * + * @property announcement Provides the instruction string which was build on the server-side and can sometimes + * concatenate instructions together if maneuver instructions are too close to each other. + * @since 1.0 + * + * @property ssmlAnnouncement Get the same instruction string you'd get from [.announcement] but this one includes + * Speech Synthesis Markup Language which helps voice synthesiser read information more humanely. + * @since 1.0 + */ +class VoiceInstructionsNavigation( + val distanceAlongGeometry: Double?, + val announcement: String?, + val ssmlAnnouncement: String? +) diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/WalkingOptionsNavigation.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/WalkingOptionsNavigation.kt index 6d906b822a1..198d2ca46f2 100644 --- a/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/WalkingOptionsNavigation.kt +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/model/WalkingOptionsNavigation.kt @@ -1,26 +1,24 @@ package com.mapbox.navigation.base.route.model -import com.google.gson.annotations.SerializedName - /** * Class for specifying options for use with the walking profile. * - * @param walkingSpeed Walking speed in meters per second. Must be between 0.14 and 6.94 meters per second. + * @property walkingSpeed Walking speed in meters per second. Must be between 0.14 and 6.94 meters per second. * Defaults to 1.42 meters per second * - * @param walkwayBias A bias which determines whether the route should prefer or avoid the use of roads or paths + * @property walkwayBias A bias which determines whether the route should prefer or avoid the use of roads or paths * that are set aside for pedestrian-only use (walkways). The allowed range of values is from * -1 to 1, where -1 indicates indicates preference to avoid walkways, 1 indicates preference * to favor walkways, and 0 indicates no preference (the default). * - * @param alleyBias A bias which determines whether the route should prefer or avoid the use of alleys. The + * @property alleyBias A bias which determines whether the route should prefer or avoid the use of alleys. The * allowed range of values is from -1 to 1, where -1 indicates indicates preference to avoid * alleys, 1 indicates preference to favor alleys, and 0 indicates no preference (the default). */ data class WalkingOptionsNavigation( - @SerializedName("walking_speed") val walkingSpeed: Double? = null, - @SerializedName("walkway_bias") val walkwayBias: Double? = null, - @SerializedName("alley_bias") val alleyBias: Double? = null + val walkingSpeed: Double? = null, + val walkwayBias: Double? = null, + val alleyBias: Double? = null ) { companion object { /** @@ -57,7 +55,7 @@ data class WalkingOptionsNavigation( * Walking speed in meters per second. Must be between 0.14 and 6.94 meters per second. * Defaults to 1.42 meters per second * - * @param walkingSpeed in meters per second + * @property walkingSpeed in meters per second * @return this builder */ fun walkingSpeed(walkingSpeed: Double?): Builder { @@ -71,7 +69,7 @@ data class WalkingOptionsNavigation( * -1 to 1, where -1 indicates preference to avoid walkways, 1 indicates preference to favor * walkways, and 0 indicates no preference (the default). * - * @param walkwayBias bias to prefer or avoid walkways + * @property walkwayBias bias to prefer or avoid walkways * @return this builder */ fun walkwayBias(walkwayBias: Double?): Builder { @@ -84,7 +82,7 @@ data class WalkingOptionsNavigation( * allowed range of values is from -1 to 1, where -1 indicates preference to avoid alleys, 1 * indicates preference to favor alleys, and 0 indicates no preference (the default). * - * @param alleyBias bias to prefer or avoid alleys + * @property alleyBias bias to prefer or avoid alleys * @return this builder */ fun alleyBias(alleyBias: Double?): Builder { diff --git a/libnavigation-base/src/test/java/com/mapbox/navigation/base/route/RouteUrlTest.kt b/libnavigation-base/src/test/java/com/mapbox/navigation/base/route/RouteUrlTest.kt new file mode 100644 index 00000000000..00470cfe194 --- /dev/null +++ b/libnavigation-base/src/test/java/com/mapbox/navigation/base/route/RouteUrlTest.kt @@ -0,0 +1,119 @@ +package com.mapbox.navigation.base.route + +import android.net.Uri +import com.mapbox.geojson.Point +import java.net.URLDecoder +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config(manifest = Config.NONE) +class RouteUrlTest { + + @Test + fun checkBaseUrl() { + setupRouteUrl() + .checkContain("${RouteUrl.BASE_URL}/${RouteUrl.BASE_URL_API_NAME}/${RouteUrl.BASE_URL_API_VERSION}/") + } + + @Test + fun checkCoordinates() { + val routeUrl = setupRouteUrl( + orgin = Point.fromLngLat(12.2, 43.4), + waypoints = listOf(Point.fromLngLat(54.0, 90.01), Point.fromLngLat(32.9, 81.23)), + destination = Point.fromLngLat(42.00210201, 13.123121) + ) + + assertNotNull(routeUrl.path) + assertTrue( + routeUrl.path?.contains("/12.2,43.4;54.0,90.01;32.9,81.23;42.00210201,13.123121") ?: false + ) + } + + @Test + fun checkUserAndProfile() { + val routeUrl = setupRouteUrl() + + routeUrl.checkContain("/${RouteUrl.PROFILE_DEFAULT_USER}/${RouteUrl.PROFILE_DRIVING}/") + } + + @Test + fun checkNonDefaultUserAndProfile() { + val routeUrl = setupRouteUrl(user = "vitalik", profile = RouteUrl.PROFILE_CYCLING) + + routeUrl.checkContain("/vitalik/${RouteUrl.PROFILE_CYCLING}/") + } + + @Test + fun checkQueries() { + val token = "pk_token1212.dsda" + val routeUri = setupRouteUrl( + accessToken = token, + steps = true, + geometries = RouteUrl.GEOMETRY_POLYLINE, + overview = RouteUrl.OVERVIEW_SIMPLIFIED, + voiceIntruction = false, + bannerIntruction = true, + roundaboutExits = true, + enableRefresh = false + ) + val expectedQueries = + listOf( + "access_token" to token, + "steps" to "true", + "geometries" to RouteUrl.GEOMETRY_POLYLINE, + "overview" to RouteUrl.OVERVIEW_SIMPLIFIED, + "voice_instructions" to "false", + "roundabout_exits" to "true", + "enable_refresh" to "false" + ) + + expectedQueries.forEach { (key, value) -> + assertEquals("Check Query param", value, routeUri.getQueryParameter(key)) + } + } + + private fun Uri.checkContain(string: String, decode: String? = "UTF-8") = + assertTrue(this.toString() + .let { url -> + decode?.let { decode -> URLDecoder.decode(url, decode) } ?: url + } + .contains(string) + ) + + private fun setupRouteUrl( + accessToken: String = "", + orgin: Point = Point.fromLngLat(.0, .0), + waypoints: List? = null, + destination: Point = Point.fromLngLat(.0, .0), + user: String = RouteUrl.PROFILE_DEFAULT_USER, + profile: String = RouteUrl.PROFILE_DRIVING, + steps: Boolean = true, + geometries: String = RouteUrl.GEOMETRY_POLYLINE6, + overview: String = RouteUrl.OVERVIEW_FULL, + voiceIntruction: Boolean = true, + bannerIntruction: Boolean = true, + roundaboutExits: Boolean = true, + enableRefresh: Boolean = true + ): Uri = + RouteUrl( + accessToken, + orgin, + waypoints, + destination, + user, + profile, + steps, + geometries, + overview, + voiceIntruction, + bannerIntruction, + roundaboutExits, + enableRefresh + ).getRequest() +} diff --git a/libnavigation-base/src/test/java/com/mapbox/navigation/base/route/model/WalkingOptionsNavigationTest.kt b/libnavigation-base/src/test/java/com/mapbox/navigation/base/route/model/WalkingOptionsNavigationTest.kt new file mode 100644 index 00000000000..ceb0c464430 --- /dev/null +++ b/libnavigation-base/src/test/java/com/mapbox/navigation/base/route/model/WalkingOptionsNavigationTest.kt @@ -0,0 +1,28 @@ +package com.mapbox.navigation.base.route.model + +import org.junit.Assert.assertEquals +import org.junit.Test + +class WalkingOptionsNavigationTest { + + @Test + fun alleyBias_walkingOptionSet() { + val options = WalkingOptionsNavigation.builder().alleyBias(0.7).build() + + assertEquals(0.7, options.alleyBias) + } + + @Test + fun walkwayBias_walkingOptionSet() { + val options = WalkingOptionsNavigation.builder().walkwayBias(0.8).build() + + assertEquals(0.8, options.walkwayBias) + } + + @Test + fun walkingSpeed_walkingOptionSet() { + val options = WalkingOptionsNavigation.builder().walkingSpeed(2.0).build() + + assertEquals(2.0, options.walkingSpeed) + } +} diff --git a/libnavigator/build.gradle b/libnavigator/build.gradle index 35af2cdc34e..0e3a74880d3 100644 --- a/libnavigator/build.gradle +++ b/libnavigator/build.gradle @@ -15,7 +15,9 @@ android { } dependencies { - api project(':libnavigation-base') + implementation(project(':libnavigation-base')) + implementation(project(':liblogger')) + implementation project(':libnavigation-util') // Navigator api dependenciesList.mapboxNavigator @@ -27,6 +29,14 @@ dependencies { ktlint dependenciesList.ktlint implementation dependenciesList.kotlinStdLib + + implementation dependenciesList.supportAnnotation + + implementation dependenciesList.mapboxSdkGeoJSON + + // Unit testing + testImplementation dependenciesList.junit + testImplementation dependenciesList.mockk } apply from: "${rootDir}/gradle/bintray-publish.gradle" \ No newline at end of file diff --git a/libnavigator/src/main/java/com/mapbox/navigation/navigator/MapboxNativeNavigator.kt b/libnavigator/src/main/java/com/mapbox/navigation/navigator/MapboxNativeNavigator.kt index 97d467b56ce..bf593bb9775 100644 --- a/libnavigator/src/main/java/com/mapbox/navigation/navigator/MapboxNativeNavigator.kt +++ b/libnavigator/src/main/java/com/mapbox/navigation/navigator/MapboxNativeNavigator.kt @@ -2,9 +2,15 @@ package com.mapbox.navigation.navigator import android.location.Location import com.mapbox.navigation.base.route.model.Route +import com.mapbox.navigation.navigator.model.RouterConfig +import com.mapbox.navigator.HttpInterface +import com.mapbox.navigator.RouterResult import java.util.Date interface MapboxNativeNavigator { + + fun configureRouter(routerConfig: RouterConfig, httpClient: HttpInterface, userAgent: String) + fun getRoute(url: String): RouterResult fun updateLocation(rawLocation: Location) fun getStatus(date: Date): TripStatus fun setRoute(route: Route) diff --git a/libnavigator/src/main/java/com/mapbox/navigation/navigator/MapboxNativeNavigatorImpl.kt b/libnavigator/src/main/java/com/mapbox/navigation/navigator/MapboxNativeNavigatorImpl.kt index b728089ec52..94a8a79358d 100644 --- a/libnavigator/src/main/java/com/mapbox/navigation/navigator/MapboxNativeNavigatorImpl.kt +++ b/libnavigator/src/main/java/com/mapbox/navigation/navigator/MapboxNativeNavigatorImpl.kt @@ -4,9 +4,14 @@ import android.location.Location import com.mapbox.geojson.Point import com.mapbox.navigation.base.route.model.Route import com.mapbox.navigation.base.trip.model.RouteProgress +import com.mapbox.navigation.navigator.model.RouterConfig import com.mapbox.navigator.FixLocation +import com.mapbox.navigator.HttpInterface import com.mapbox.navigator.NavigationStatus import com.mapbox.navigator.Navigator +import com.mapbox.navigator.RouterParams +import com.mapbox.navigator.RouterResult +import com.mapbox.navigator.TileEndpointConfiguration import java.util.Date object MapboxNativeNavigatorImpl : MapboxNativeNavigator { @@ -17,10 +22,32 @@ object MapboxNativeNavigatorImpl : MapboxNativeNavigator { private val navigator: Navigator = Navigator() + override fun configureRouter(routerConfig: RouterConfig, httpClient: HttpInterface, userAgent: String) { + navigator.configureRouter( + RouterParams( + routerConfig.tilePath, + routerConfig.inMemoryTileCache, + routerConfig.mapMatchingSpatialCache, + routerConfig.threadsCount, + routerConfig.endpointConfig?.let { + TileEndpointConfiguration( + it.host, + it.version, + it.token, + userAgent, + "" + ) + }), + httpClient + ) + } + override fun updateLocation(rawLocation: Location) { navigator.updateLocation(rawLocation.toFixLocation()) } + override fun getRoute(url: String): RouterResult = navigator.getRoute(url) + override fun setRoute(route: Route) { TODO("not implemented") } diff --git a/libnavigator/src/main/java/com/mapbox/navigation/navigator/model/EndpointConfig.kt b/libnavigator/src/main/java/com/mapbox/navigation/navigator/model/EndpointConfig.kt new file mode 100644 index 00000000000..32effba5c8d --- /dev/null +++ b/libnavigator/src/main/java/com/mapbox/navigation/navigator/model/EndpointConfig.kt @@ -0,0 +1,8 @@ +package com.mapbox.navigation.navigator.model + +data class EndpointConfig( + val host: String, + val version: String, + val token: String, + val userAgent: String +) diff --git a/libnavigator/src/main/java/com/mapbox/navigation/navigator/model/RouterConfig.kt b/libnavigator/src/main/java/com/mapbox/navigation/navigator/model/RouterConfig.kt new file mode 100644 index 00000000000..150a1ac7748 --- /dev/null +++ b/libnavigator/src/main/java/com/mapbox/navigation/navigator/model/RouterConfig.kt @@ -0,0 +1,9 @@ +package com.mapbox.navigation.navigator.model + +data class RouterConfig( + val tilePath: String, + val inMemoryTileCache: Int?, + val mapMatchingSpatialCache: Int?, + val threadsCount: Int?, + val endpointConfig: EndpointConfig? +)