Skip to content

Commit 42d33b1

Browse files
authored
Bring RouteUtils class to simplify work with RouteLeg objects (#180)
* first draft of RouteUtils with test Activity * small changes to RouteUtils * fix for 1-coordinate steps in getSnapToRoute() * make the floating number be shorter * ui improvements to RouteUtilsV5Activity * tests for RouteUtils
1 parent 04488bd commit 42d33b1

12 files changed

Lines changed: 594 additions & 8 deletions

File tree

libandroid/app/src/main/AndroidManifest.xml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@
3535
android:label="Directions v5"
3636
android:parentActivityName=".MainActivity"
3737
android:theme="@style/AppTheme.NoActionBar" />
38+
<activity
39+
android:name=".directions.RouteUtilsV5Activity"
40+
android:label="Route Utils v5"
41+
android:parentActivityName=".MainActivity"
42+
android:theme="@style/AppTheme.NoActionBar" />
3843
<activity
3944
android:name=".icons.DirectionsIconsActivity"
4045
android:label="Directions icons"
@@ -100,7 +105,6 @@
100105
android:label="Turf inside"
101106
android:parentActivityName=".MainActivity"
102107
android:theme="@style/AppTheme.NoActionBar" />
103-
104108
<activity
105109
android:name=".turf.TurfMidpointActivity"
106110
android:label="Turf midpoint"

libandroid/app/src/main/java/com/mapbox/services/android/testapp/MainActivity.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import com.mapbox.services.android.BuildConfig;
1616
import com.mapbox.services.android.testapp.directions.DirectionsV4Activity;
1717
import com.mapbox.services.android.testapp.directions.DirectionsV5Activity;
18+
import com.mapbox.services.android.testapp.directions.RouteUtilsV5Activity;
1819
import com.mapbox.services.android.testapp.geocoding.GeocodingReverseActivity;
1920
import com.mapbox.services.android.testapp.geocoding.GeocodingServiceActivity;
2021
import com.mapbox.services.android.testapp.geocoding.GeocodingWidgetActivity;
@@ -49,6 +50,7 @@ public class MainActivity extends AppCompatActivity {
4950

5051
private final static List<SampleItem> samples = new ArrayList<>(Arrays.asList(
5152
new SampleItem("Directions v5", "", DirectionsV5Activity.class),
53+
new SampleItem("Route Utils v5", "", RouteUtilsV5Activity.class),
5254
new SampleItem("Directions v4", "", DirectionsV4Activity.class),
5355
new SampleItem("Directions icons", "", DirectionsIconsActivity.class),
5456
new SampleItem("Reverse geocoding", "", GeocodingReverseActivity.class),

libandroid/app/src/main/java/com/mapbox/services/android/testapp/directions/DirectionsV5Activity.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
import java.util.ArrayList;
3131
import java.util.List;
32+
import java.util.Locale;
3233

3334
import retrofit2.Call;
3435
import retrofit2.Callback;
@@ -42,6 +43,7 @@ public class DirectionsV5Activity extends AppCompatActivity {
4243
private MapboxMap mapboxMap = null;
4344

4445
private DirectionsRoute currentRoute = null;
46+
4547
@Override
4648
protected void onCreate(Bundle savedInstanceState) {
4749
super.onCreate(savedInstanceState);
@@ -126,7 +128,7 @@ public void onResponse(Call<DirectionsResponse> call, Response<DirectionsRespons
126128
// Print some info about the route
127129
currentRoute = response.body().getRoutes().get(0);
128130
Log.d(LOG_TAG, "Distance: " + currentRoute.getDistance());
129-
showMessage(String.format("Route is %f meters long.", currentRoute.getDistance()));
131+
showMessage(String.format(Locale.US, "Route is %.1f meters long.", currentRoute.getDistance()));
130132

131133
// Draw the route on the map
132134
drawRoute(currentRoute);
Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
package com.mapbox.services.android.testapp.directions;
2+
3+
import android.graphics.Color;
4+
import android.graphics.drawable.Drawable;
5+
import android.os.Bundle;
6+
import android.support.annotation.NonNull;
7+
import android.support.design.widget.FloatingActionButton;
8+
import android.support.design.widget.Snackbar;
9+
import android.support.v4.content.ContextCompat;
10+
import android.support.v7.app.AppCompatActivity;
11+
import android.support.v7.widget.Toolbar;
12+
import android.util.Log;
13+
import android.view.View;
14+
import android.widget.Toast;
15+
16+
import com.mapbox.mapboxsdk.annotations.Icon;
17+
import com.mapbox.mapboxsdk.annotations.IconFactory;
18+
import com.mapbox.mapboxsdk.annotations.Marker;
19+
import com.mapbox.mapboxsdk.annotations.MarkerOptions;
20+
import com.mapbox.mapboxsdk.annotations.MarkerViewOptions;
21+
import com.mapbox.mapboxsdk.annotations.Polyline;
22+
import com.mapbox.mapboxsdk.annotations.PolylineOptions;
23+
import com.mapbox.mapboxsdk.camera.CameraPosition;
24+
import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
25+
import com.mapbox.mapboxsdk.constants.Style;
26+
import com.mapbox.mapboxsdk.geometry.LatLng;
27+
import com.mapbox.mapboxsdk.maps.MapView;
28+
import com.mapbox.mapboxsdk.maps.MapboxMap;
29+
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
30+
import com.mapbox.services.Constants;
31+
import com.mapbox.services.android.testapp.MainActivity;
32+
import com.mapbox.services.android.testapp.R;
33+
import com.mapbox.services.android.testapp.Utils;
34+
import com.mapbox.services.commons.ServicesException;
35+
import com.mapbox.services.commons.geojson.LineString;
36+
import com.mapbox.services.commons.models.Position;
37+
import com.mapbox.services.commons.turf.TurfException;
38+
import com.mapbox.services.commons.utils.PolylineUtils;
39+
import com.mapbox.services.directions.v5.DirectionsCriteria;
40+
import com.mapbox.services.directions.v5.MapboxDirections;
41+
import com.mapbox.services.directions.v5.models.DirectionsResponse;
42+
import com.mapbox.services.directions.v5.models.DirectionsRoute;
43+
import com.mapbox.services.directions.v5.models.LegStep;
44+
import com.mapbox.services.directions.v5.models.RouteLeg;
45+
import com.mapbox.services.navigation.v5.RouteUtils;
46+
47+
import java.util.ArrayList;
48+
import java.util.List;
49+
import java.util.Locale;
50+
51+
import retrofit2.Call;
52+
import retrofit2.Callback;
53+
import retrofit2.Response;
54+
55+
public class RouteUtilsV5Activity extends AppCompatActivity implements OnMapReadyCallback {
56+
57+
private final static String LOG_TAG = "RouteUtilsV5Activity";
58+
59+
private MapView mapView = null;
60+
private MapboxMap mapboxMap = null;
61+
62+
private LatLng from = null;
63+
private LatLng to = null;
64+
private DirectionsRoute currentRoute = null;
65+
66+
private Icon tapIcon;
67+
private Marker userTap = null;
68+
private List<Polyline> snapLines = null;
69+
70+
@Override
71+
protected void onCreate(Bundle savedInstanceState) {
72+
super.onCreate(savedInstanceState);
73+
setContentView(R.layout.activity_route_utils_v5);
74+
75+
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
76+
setSupportActionBar(toolbar);
77+
78+
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
79+
80+
// Create an Icon object for the marker to use
81+
IconFactory iconFactory = IconFactory.getInstance(this);
82+
Drawable iconDrawable = ContextCompat.getDrawable(this, R.drawable.ic_my_location_black_24dp);
83+
tapIcon = iconFactory.fromDrawable(iconDrawable);
84+
85+
// Set up a standard Mapbox map
86+
mapView = (MapView) findViewById(R.id.mapview);
87+
mapView.onCreate(savedInstanceState);
88+
mapView.getMapAsync(this);
89+
}
90+
91+
@Override
92+
public void onMapReady(MapboxMap mapboxMap) {
93+
this.mapboxMap = mapboxMap;
94+
95+
mapboxMap.setStyleUrl(Style.MAPBOX_STREETS);
96+
97+
// Dupont Circle
98+
LatLng target = new LatLng(38.90962, -77.04341);
99+
100+
// Move map
101+
CameraPosition cameraPosition = new CameraPosition.Builder()
102+
.target(target)
103+
.zoom(14)
104+
.build();
105+
mapboxMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
106+
107+
mapboxMap.setOnMapClickListener(new MapboxMap.OnMapClickListener() {
108+
@Override
109+
public void onMapClick(@NonNull LatLng point) {
110+
if (from == null) {
111+
setFrom(point);
112+
} else if (to == null) {
113+
setTo(point);
114+
} else {
115+
try {
116+
doUtils(point);
117+
} catch (ServicesException e) {
118+
Log.e(LOG_TAG, "Services exception: " + e.getMessage());
119+
e.printStackTrace();
120+
} catch (TurfException e) {
121+
Log.e(LOG_TAG, "Turf exception: " + e.getMessage());
122+
e.printStackTrace();
123+
}
124+
}
125+
}
126+
});
127+
}
128+
129+
private void setFrom(LatLng point) {
130+
from = point;
131+
mapboxMap.addMarker(new MarkerOptions()
132+
.position(point)
133+
.title("From"));
134+
}
135+
136+
private void setTo(LatLng point) {
137+
to = point;
138+
mapboxMap.addMarker(new MarkerOptions()
139+
.position(point)
140+
.title("To"));
141+
142+
try {
143+
getRoute(Position.fromCoordinates(from.getLongitude(), from.getLatitude()), Position.fromCoordinates(to.getLongitude(), to.getLatitude()));
144+
} catch (ServicesException e) {
145+
showMessage(e.getMessage());
146+
e.printStackTrace();
147+
}
148+
}
149+
150+
private void doUtils(LatLng point) throws ServicesException, TurfException {
151+
// Remove previous
152+
if (userTap != null) {
153+
mapboxMap.removeMarker(userTap);
154+
}
155+
156+
userTap = mapboxMap.addMarker(new MarkerOptions().position(point).setIcon(tapIcon));
157+
158+
RouteUtils routeUtils = new RouteUtils();
159+
RouteLeg route = currentRoute.getLegs().get(0);
160+
Position position = Position.fromCoordinates(point.getLongitude(), point.getLatitude());
161+
162+
// General situational message
163+
String message = String.format(Locale.US, "You're closest to step %d/%d (%s)",
164+
routeUtils.getClosestStep(position, route) + 1,
165+
route.getSteps().size(),
166+
routeUtils.isOffRoute(position, route) ? "off-route" : "not off-route");
167+
showMessage(message);
168+
169+
// Remove previous lines
170+
if (snapLines != null && snapLines.size() > 0) {
171+
for (Polyline snapLine: snapLines) {
172+
mapboxMap.removePolyline(snapLine);
173+
}
174+
}
175+
176+
// Draw snap to route lines
177+
snapLines = new ArrayList<>();
178+
for (int stepIndex = 0; stepIndex < route.getSteps().size(); stepIndex++) {
179+
Position snapPoint = routeUtils.getSnapToRoute(position, route, stepIndex);
180+
LatLng[] points = new LatLng[] {
181+
point,
182+
new LatLng(snapPoint.getLatitude(), snapPoint.getLongitude())};
183+
snapLines.add(mapboxMap.addPolyline(new PolylineOptions()
184+
.add(points)
185+
.color(Color.parseColor("#f9886c"))
186+
.width(2)));
187+
}
188+
189+
// Log some extra info
190+
for (int stepIndex = 0; stepIndex < route.getSteps().size(); stepIndex++) {
191+
Log.d(LOG_TAG, String.format("Step %d: in step = %b, distance = %.1fkm",
192+
stepIndex + 1,
193+
routeUtils.isInStep(position, route, stepIndex),
194+
routeUtils.getDistanceToStep(position, route, stepIndex)));
195+
}
196+
}
197+
198+
private void getRoute(Position origin, Position destination) throws ServicesException {
199+
ArrayList<Position> positions = new ArrayList<>();
200+
positions.add(origin);
201+
positions.add(destination);
202+
203+
MapboxDirections client = new MapboxDirections.Builder()
204+
.setAccessToken(Utils.getMapboxAccessToken(this))
205+
.setCoordinates(positions)
206+
.setProfile(DirectionsCriteria.PROFILE_DRIVING)
207+
.setSteps(true)
208+
.setOverview(DirectionsCriteria.OVERVIEW_FULL)
209+
.build();
210+
211+
client.enqueueCall(new Callback<DirectionsResponse>() {
212+
@Override
213+
public void onResponse(Call<DirectionsResponse> call, Response<DirectionsResponse> response) {
214+
// You can get generic HTTP info about the response
215+
Log.d(LOG_TAG, "Response code: " + response.code());
216+
if (response.body() == null) {
217+
Log.e(LOG_TAG, "No routes found, make sure you set the right user and access token.");
218+
return;
219+
}
220+
221+
// Print some info about the route
222+
currentRoute = response.body().getRoutes().get(0);
223+
Log.d(LOG_TAG, "Distance: " + currentRoute.getDistance());
224+
showMessage(String.format(Locale.US, "Route has %d steps and it's %.1f meters long.",
225+
currentRoute.getLegs().get(0).getSteps().size(),
226+
currentRoute.getDistance()));
227+
228+
// Draw the route on the map
229+
drawRoute(currentRoute);
230+
}
231+
232+
@Override
233+
public void onFailure(Call<DirectionsResponse> call, Throwable t) {
234+
Log.e(LOG_TAG, "Error: " + t.getMessage());
235+
showMessage("Error: " + t.getMessage());
236+
}
237+
});
238+
}
239+
240+
private void drawRoute(DirectionsRoute route) {
241+
// We're gonna draw each step in an alternating color
242+
String[] colors = new String[] {"#3887be", "#56b881"}; // Blue, green
243+
244+
List<Position> coordinates;
245+
LatLng[] points;
246+
int colorIndex = 0;
247+
for (int i = 0; i < route.getLegs().get(0).getSteps().size(); i++) {
248+
LegStep step = route.getLegs().get(0).getSteps().get(i);
249+
coordinates = PolylineUtils.decode(step.getGeometry(), Constants.OSRM_PRECISION_V5);
250+
points = new LatLng[coordinates.size()];
251+
for (int j = 0; j < coordinates.size(); j++) {
252+
points[j] = new LatLng(
253+
coordinates.get(j).getLatitude(),
254+
coordinates.get(j).getLongitude());
255+
}
256+
257+
colorIndex ^= 1;
258+
mapboxMap.addPolyline(new PolylineOptions()
259+
.add(points)
260+
.color(Color.parseColor(colors[colorIndex]))
261+
.width(5));
262+
}
263+
}
264+
265+
private void showMessage(String message) {
266+
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
267+
}
268+
269+
@Override
270+
public void onResume() {
271+
super.onResume();
272+
mapView.onResume();
273+
}
274+
275+
@Override
276+
public void onPause() {
277+
super.onPause();
278+
mapView.onPause();
279+
}
280+
281+
@Override
282+
protected void onSaveInstanceState(Bundle outState) {
283+
super.onSaveInstanceState(outState);
284+
mapView.onSaveInstanceState(outState);
285+
}
286+
287+
@Override
288+
protected void onDestroy() {
289+
super.onDestroy();
290+
mapView.onDestroy();
291+
}
292+
293+
@Override
294+
public void onLowMemory() {
295+
super.onLowMemory();
296+
mapView.onLowMemory();
297+
}
298+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
2+
android:width="24dp"
3+
android:height="24dp"
4+
android:viewportWidth="24.0"
5+
android:viewportHeight="24.0">
6+
<path
7+
android:fillColor="#FF000000"
8+
android:pathData="M12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94L13,1h-2v2.06C6.83,3.52 3.52,6.83 3.06,11L1,11v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94L11,23h2v-2.06c4.17,-0.46 7.48,-3.77 7.94,-7.94L23,13v-2h-2.06zM12,19c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
9+
</vector>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:app="http://schemas.android.com/apk/res-auto"
4+
xmlns:tools="http://schemas.android.com/tools"
5+
android:layout_width="match_parent"
6+
android:layout_height="match_parent"
7+
android:fitsSystemWindows="true"
8+
tools:context="com.mapbox.services.android.testapp.directions.RouteUtilsV5Activity">
9+
10+
<android.support.design.widget.AppBarLayout
11+
android:layout_width="match_parent"
12+
android:layout_height="wrap_content"
13+
android:theme="@style/AppTheme.AppBarOverlay">
14+
15+
<android.support.v7.widget.Toolbar
16+
android:id="@+id/toolbar"
17+
android:layout_width="match_parent"
18+
android:layout_height="?attr/actionBarSize"
19+
android:background="?attr/colorPrimary"
20+
app:popupTheme="@style/AppTheme.PopupOverlay" />
21+
22+
</android.support.design.widget.AppBarLayout>
23+
24+
<include layout="@layout/content_route_utils_v5" />
25+
26+
</android.support.design.widget.CoordinatorLayout>

0 commit comments

Comments
 (0)