diff --git a/MapboxAndroidDemo/src/global/java/com/mapbox/mapboxandroiddemo/MainActivity.java b/MapboxAndroidDemo/src/global/java/com/mapbox/mapboxandroiddemo/MainActivity.java index 566fd8a79..f4edea9d8 100644 --- a/MapboxAndroidDemo/src/global/java/com/mapbox/mapboxandroiddemo/MainActivity.java +++ b/MapboxAndroidDemo/src/global/java/com/mapbox/mapboxandroiddemo/MainActivity.java @@ -47,6 +47,7 @@ import com.mapbox.mapboxandroiddemo.examples.dds.KotlinStyleCirclesCategoricallyActivity; import com.mapbox.mapboxandroiddemo.examples.dds.LineGradientActivity; import com.mapbox.mapboxandroiddemo.examples.dds.MultipleGeometriesActivity; +import com.mapbox.mapboxandroiddemo.examples.javaservices.MultipleGeometriesDirectionsRouteActivity; import com.mapbox.mapboxandroiddemo.examples.dds.MultipleHeatmapStylingActivity; import com.mapbox.mapboxandroiddemo.examples.dds.PolygonHolesActivity; import com.mapbox.mapboxandroiddemo.examples.dds.PolygonSelectToggleActivity; @@ -1032,6 +1033,14 @@ private void initializeModels() { null, R.string.activity_java_services_tilequery_url, false, BuildConfig.MIN_SDK_VERSION)); + exampleItemModels.add(new ExampleItemModel( + R.id.nav_java_services, + R.string.activity_java_services_multiple_geometries_from_directions_route_title, + R.string.activity_java_services_multiple_geometries_from_directions_route_description, + new Intent(MainActivity.this, MultipleGeometriesDirectionsRouteActivity.class), + null, + R.string.activity_java_services_multiple_geometries_from_directions_route_url, false, BuildConfig.MIN_SDK_VERSION)); + exampleItemModels.add(new ExampleItemModel( R.id.nav_snapshot_image_generator, R.string.activity_image_generator_snapshot_notification_title, diff --git a/MapboxAndroidDemo/src/main/AndroidManifest.xml b/MapboxAndroidDemo/src/main/AndroidManifest.xml index 93de4ba63..bc5b5d0ea 100644 --- a/MapboxAndroidDemo/src/main/AndroidManifest.xml +++ b/MapboxAndroidDemo/src/main/AndroidManifest.xml @@ -95,6 +95,13 @@ android:name="android.support.PARENT_ACTIVITY" android:value="com.mapbox.mapboxandroiddemo.MainActivity" /> + + + diff --git a/MapboxAndroidDemo/src/main/java/com/mapbox/mapboxandroiddemo/examples/javaservices/MultipleGeometriesDirectionsRouteActivity.java b/MapboxAndroidDemo/src/main/java/com/mapbox/mapboxandroiddemo/examples/javaservices/MultipleGeometriesDirectionsRouteActivity.java new file mode 100644 index 000000000..202d9f11d --- /dev/null +++ b/MapboxAndroidDemo/src/main/java/com/mapbox/mapboxandroiddemo/examples/javaservices/MultipleGeometriesDirectionsRouteActivity.java @@ -0,0 +1,303 @@ +package com.mapbox.mapboxandroiddemo.examples.javaservices; + +import android.graphics.Color; +import android.os.Bundle; +import android.util.Log; +import android.widget.Toast; + +import com.mapbox.api.directions.v5.DirectionsCriteria; +import com.mapbox.api.directions.v5.MapboxDirections; +import com.mapbox.api.directions.v5.models.DirectionsResponse; +import com.mapbox.api.directions.v5.models.DirectionsRoute; +import com.mapbox.api.directions.v5.models.LegStep; +import com.mapbox.geojson.Feature; +import com.mapbox.geojson.FeatureCollection; +import com.mapbox.geojson.Geometry; +import com.mapbox.geojson.GeometryCollection; +import com.mapbox.geojson.LineString; +import com.mapbox.geojson.Point; +import com.mapbox.mapboxandroiddemo.R; +import com.mapbox.mapboxsdk.Mapbox; +import com.mapbox.mapboxsdk.geometry.LatLng; +import com.mapbox.mapboxsdk.geometry.LatLngBounds; +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.mapboxsdk.style.layers.CircleLayer; +import com.mapbox.mapboxsdk.style.layers.HillshadeLayer; +import com.mapbox.mapboxsdk.style.layers.LineLayer; +import com.mapbox.mapboxsdk.style.layers.Property; +import com.mapbox.mapboxsdk.style.sources.GeoJsonSource; +import com.mapbox.mapboxsdk.style.sources.RasterDemSource; + +import java.util.ArrayList; +import java.util.List; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; +import timber.log.Timber; + +import static com.mapbox.core.constants.Constants.PRECISION_6; +import static com.mapbox.mapboxsdk.camera.CameraUpdateFactory.newLatLngBounds; +import static com.mapbox.mapboxsdk.style.expressions.Expression.eq; +import static com.mapbox.mapboxsdk.style.expressions.Expression.literal; +import static com.mapbox.mapboxsdk.style.layers.Property.CIRCLE_PITCH_ALIGNMENT_MAP; +import static com.mapbox.mapboxsdk.style.layers.Property.CIRCLE_PITCH_ALIGNMENT_VIEWPORT; +import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleColor; +import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circlePitchAlignment; +import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleRadius; +import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.hillshadeHighlightColor; +import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.hillshadeShadowColor; +import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.lineCap; +import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.lineColor; +import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.lineWidth; + +/** + * Use the Mapbox Directions API to request and retrieve a Directions route. Show the route line and + * place a circle where each of the route's waypoints are. + */ +public class MultipleGeometriesDirectionsRouteActivity extends AppCompatActivity implements OnMapReadyCallback { + + private MapView mapView; + private static final String GEOJSON_SOURCE_ID = "source-id"; + private static final String STEPS_CIRCLE_LAYER_ID = "steps-circle-layer"; + private static final String STEPS_BACKGROUND_CIRCLE_LAYER_ID = "steps-background-circle-layer"; + private static final String DIRECTIONS_ROUTE_LINE_LAYER_ID = "directions-line-layer"; + + // Adjust the following static final variables to style this example's UI + private static final String LINE_COLOR = "#EE2E23"; + private static final float LINE_WIDTH = 8f; + private static final float CIRCLE_RADIUS = 5f; + private static final float RADIUS_DIFFERENCE_BETWEEN_WAYPOINT_CIRCLES_AND_BACKGROUND_CIRCLES = 4f; + private static final int CIRCLE_COLOR = Color.WHITE; + private static final float MAX_ZOOM = 15f; + private static final int BACKGROUND_CIRCLE_COLOR = Color.parseColor(LINE_COLOR); + private static final boolean ALIGN_CIRCLES_WITH_MAP = true; + + private MapboxMap mapboxMap; + private DirectionsRoute currentRoute; + private Point origin = Point.fromLngLat(-122.39648, 37.7914277); + private Point destination = Point.fromLngLat(-88.0430, 30.6944); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Mapbox access token is configured here. This needs to be called either in your application + // object or in the same activity which contains the mapview. + Mapbox.getInstance(this, getString(R.string.access_token)); + + // This contains the MapView in XML and needs to be called after the access token is configured. + setContentView(R.layout.activity_dds_multiple_geometries_from_directions_route); + + mapView = findViewById(R.id.mapView); + mapView.onCreate(savedInstanceState); + mapView.getMapAsync(this); + } + + @Override + public void onMapReady(@NonNull MapboxMap mapboxMap) { + mapboxMap.setStyle(new Style.Builder().fromUri(Style.SATELLITE_STREETS) + .withSource(new GeoJsonSource(GEOJSON_SOURCE_ID)), new Style.OnStyleLoaded() { + @Override + public void onStyleLoaded(@NonNull Style style) { + MultipleGeometriesDirectionsRouteActivity.this.mapboxMap = mapboxMap; + initDirectionsRouteLineLayer(style); + initDirectionStepsCircleLayer(style); + initDirectionStepsBackgroundCircleLayer(style); + getRoute(origin, destination); + } + }); + } + + private void initDirectionsRouteLineLayer(@NonNull Style loadedMapStyle) { + // Create and style a LineLayer that will draw the Mapbox Directions API route line. + LineLayer directionsRouteLineLayer = new LineLayer(DIRECTIONS_ROUTE_LINE_LAYER_ID, GEOJSON_SOURCE_ID); + directionsRouteLineLayer.setProperties( + lineColor(Color.parseColor(LINE_COLOR)), + lineCap(Property.LINE_CAP_ROUND), + lineWidth(LINE_WIDTH) + ); + + directionsRouteLineLayer.setFilter(eq(literal("$type"), literal("LineString"))); + + // Add the layer below the "settlement-label" layer (city name labels, etc.) + if (loadedMapStyle.getLayer("settlement-label") != null) { + loadedMapStyle.addLayerBelow(directionsRouteLineLayer, "settlement-label"); + } else { + loadedMapStyle.addLayer(directionsRouteLineLayer); + } + } + + private void initDirectionStepsCircleLayer(@NonNull Style loadedMapStyle) { + // Create and style a CircleLayer that will place circles for each of the Mapbox Directions API route's + // waypoint locations + CircleLayer individualCirclesLayer = new CircleLayer(STEPS_CIRCLE_LAYER_ID, GEOJSON_SOURCE_ID); + individualCirclesLayer.setProperties( + circleColor(CIRCLE_COLOR), + circlePitchAlignment(ALIGN_CIRCLES_WITH_MAP ? CIRCLE_PITCH_ALIGNMENT_MAP : CIRCLE_PITCH_ALIGNMENT_VIEWPORT), + circleRadius(CIRCLE_RADIUS)); + individualCirclesLayer.setFilter(eq(literal("$type"), literal("Point"))); + individualCirclesLayer.setMaxZoom(MAX_ZOOM); + loadedMapStyle.addLayer(individualCirclesLayer); + } + + private void initDirectionStepsBackgroundCircleLayer(@NonNull Style loadedMapStyle) { + // Create and style a CircleLayer that will place circles for each of the Mapbox Directions API route's + // waypoint locations + CircleLayer individualCirclesLayer = new CircleLayer(STEPS_BACKGROUND_CIRCLE_LAYER_ID, GEOJSON_SOURCE_ID); + individualCirclesLayer.setProperties( + circleColor(BACKGROUND_CIRCLE_COLOR), + circlePitchAlignment(ALIGN_CIRCLES_WITH_MAP ? CIRCLE_PITCH_ALIGNMENT_MAP : CIRCLE_PITCH_ALIGNMENT_VIEWPORT), + circleRadius(CIRCLE_RADIUS + RADIUS_DIFFERENCE_BETWEEN_WAYPOINT_CIRCLES_AND_BACKGROUND_CIRCLES)); + individualCirclesLayer.setFilter(eq(literal("$type"), literal("Point"))); + individualCirclesLayer.setMaxZoom(MAX_ZOOM); + loadedMapStyle.addLayerBelow(individualCirclesLayer, STEPS_CIRCLE_LAYER_ID); + } + + /** + * Make a request to the Mapbox Directions API. Once successful, pass the route to the + * route layer. + * + * @param origin the starting point of the route + * @param destination the desired finish point of the route + */ + private void getRoute(Point origin, Point destination) { + + MapboxDirections directionsApiClient = MapboxDirections.builder() + .origin(origin) + .destination(destination) + .overview(DirectionsCriteria.OVERVIEW_FULL) + .profile(DirectionsCriteria.PROFILE_DRIVING) + .steps(true) + .accessToken(getString(R.string.access_token)) + .build(); + + directionsApiClient.enqueueCall(new Callback() { + @Override + public void onResponse(Call call, Response response) { + System.out.println(call.request().url().toString()); + + // You can get the generic HTTP info about the response. + Timber.d("Response code: " + response.code()); + if (response.body() == null) { + Timber.e("No routes found, make sure you set the right user and access token."); + return; + } else if (response.body().routes().size() < 1) { + Timber.e("No routes found"); + return; + } + + if (response.body() != null) { + + // Get the directions route + currentRoute = response.body().routes().get(0); + + mapboxMap.getStyle(new Style.OnStyleLoaded() { + @Override + public void onStyleLoaded(@NonNull Style style) { + // Retrieve and update the source designated for showing the directions route + GeoJsonSource source = style.getSourceAs(GEOJSON_SOURCE_ID); + + // Create a LineString with the directions route's geometry and + // reset the GeoJSON source for the route LineLayer source. + if (source != null && response.body() != null) { + + List geometryList = new ArrayList<>(); + + // Add each step maneuvers to the geometry list. + if (currentRoute.legs().size() > 0) { + for (LegStep singleRouteLeg : currentRoute.legs().get(0).steps()) { + Point stepManeuverLocationPoint = singleRouteLeg.maneuver().location(); + geometryList.add(stepManeuverLocationPoint); + } + } else { + Timber.d("%s", getString(R.string.no_legs_toast)); + } + + // Add the Directions route LineString geometry to the geometry list. + if (currentRoute.geometry() != null) { + geometryList.add(LineString.fromPolyline(currentRoute.geometry(), PRECISION_6)); + } + + // Update the source's GeoJSON with a Feature that is of a GeometryCollection geometry. + // The GeometryCollection has the route LineString and the various step Points. + // The filters applied to the LineLayer and CircleLayer above, will be applied + // to the GeometryCollection. + source.setGeoJson(FeatureCollection.fromFeature( + Feature.fromGeometry( + GeometryCollection.fromGeometries(geometryList) + ))); + + // Ease the camera to fit to the Directions route. + easeCameraToShowEntireDirectionsRoute(new LatLng(origin.latitude(), origin.longitude()), + new LatLng(destination.latitude(), destination.longitude())); + } + } + }); + } + } + + @Override + public void onFailure(Call call, Throwable throwable) { + Timber.e("Error: " + throwable.getMessage()); + Toast.makeText(MultipleGeometriesDirectionsRouteActivity.this, "Error: " + throwable.getMessage(), + Toast.LENGTH_SHORT).show(); + } + }); + } + + private void easeCameraToShowEntireDirectionsRoute(LatLng origin, LatLng destination) { + mapboxMap.easeCamera(newLatLngBounds(new LatLngBounds.Builder() + .include(origin) + .include(destination) + .build(), 75), 2000); + } + + // Add the mapView lifecycle to the activity's lifecycle methods + @Override + public void onResume() { + super.onResume(); + mapView.onResume(); + } + + @Override + protected void onStart() { + super.onStart(); + mapView.onStart(); + } + + @Override + protected void onStop() { + super.onStop(); + mapView.onStop(); + } + + @Override + public void onPause() { + super.onPause(); + mapView.onPause(); + } + + @Override + public void onLowMemory() { + super.onLowMemory(); + mapView.onLowMemory(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mapView.onDestroy(); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + mapView.onSaveInstanceState(outState); + } +} diff --git a/MapboxAndroidDemo/src/main/res/layout/activity_dds_multiple_geometries_from_directions_route.xml b/MapboxAndroidDemo/src/main/res/layout/activity_dds_multiple_geometries_from_directions_route.xml new file mode 100644 index 000000000..4d883937c --- /dev/null +++ b/MapboxAndroidDemo/src/main/res/layout/activity_dds_multiple_geometries_from_directions_route.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/MapboxAndroidDemo/src/main/res/values/activity_strings.xml b/MapboxAndroidDemo/src/main/res/values/activity_strings.xml index 328266125..605eb8f83 100644 --- a/MapboxAndroidDemo/src/main/res/values/activity_strings.xml +++ b/MapboxAndroidDemo/src/main/res/values/activity_strings.xml @@ -406,4 +406,8 @@ Radians + + This directions route has no legs + The leg has no steps + \ No newline at end of file diff --git a/MapboxAndroidDemo/src/main/res/values/descriptions_strings.xml b/MapboxAndroidDemo/src/main/res/values/descriptions_strings.xml index aaf78a837..0824cad3a 100644 --- a/MapboxAndroidDemo/src/main/res/values/descriptions_strings.xml +++ b/MapboxAndroidDemo/src/main/res/values/descriptions_strings.xml @@ -94,6 +94,7 @@ Match raw GPS points to the map so they aligns with the roads/pathways. Use Turf to calculate coordinates to eventually draw a ring around a center coordinate. Use Turf to generate a circle with a radius expressed in physical units (e.g. miles, kilometers, etc). + Show multiple geometries based on a single Mapbox Directions API response. Use the traffic plugin to display live car congestion data on top of a map. Use the building plugin to easily display 3D building height Easily retrieve GeoJSON data from a url, asset, or path diff --git a/MapboxAndroidDemo/src/main/res/values/titles_strings.xml b/MapboxAndroidDemo/src/main/res/values/titles_strings.xml index aa68e5bb0..b3e27d2ec 100644 --- a/MapboxAndroidDemo/src/main/res/values/titles_strings.xml +++ b/MapboxAndroidDemo/src/main/res/values/titles_strings.xml @@ -92,6 +92,7 @@ Map Matching Hollow circle Define a circle in physical units + Multiple geometries from Directions route Display real-time traffic Display buildings in 3D Change map text to device language diff --git a/MapboxAndroidDemo/src/main/res/values/urls_strings.xml b/MapboxAndroidDemo/src/main/res/values/urls_strings.xml index 72091bb2f..84c7f70c2 100644 --- a/MapboxAndroidDemo/src/main/res/values/urls_strings.xml +++ b/MapboxAndroidDemo/src/main/res/values/urls_strings.xml @@ -93,6 +93,7 @@ https://i.imgur.com/ig8gGnY.png https://i.imgur.com/nG8xeXH.png https://i.imgur.com/nG8xeXH.png + https://i.imgur.com/Tz35fHA.png http://i.imgur.com/HRriOVR.png http://i.imgur.com/Vcu67UR.png https://i.imgur.com/oKHx3bv.png