10 Guided navigation
Overview
This tutorial will show you how to calculate a tour with multiple destinations. Parts of the tour can be standard A to B routings, but they also can be guided navigation routings. Guided navigation routings are routings that lead over a list of SVPs (silent via points).
To accomplish this we will develop a simple JSON file format, which can be loaded by the tutorial from an e-mail attachment or from an open file intent.
Base
You can use your results from the previous Tutorial09
as a base for our new project.
Tour calculation
The NavigationSDK offers extended capabilites to calculate tours.
A tour is defined to be a route with one or multiple stations. A tour can contain "regular" stations and SVP stations. Regular stations are stations which are defined solely by a geo-coordinate. SVP stations are defined by a set of coordinates with directions (and other attributes), which describe a preferred way to get to a destination.
To calculate a tour, do the following:
- Create a tour with the start position as parameter pos
- Add as many stations as you like with
addStation
oraddSVPs
- Call
calculateTour
to calculate the tour
Example JSON data format
Our example data format is a simple json array containing regular and/or svp destinations. A regular destination is basically a destination at point x,y in Mercator coordinates. An SVP destination is a trace of points describing the route from the previous destination to the last point of the SVPs. Our default filename ending will be .tour
.
Attention
SVP stations require a course. For this example we assume the course is given with North = 0 degrees and clockwise orientation. Please note that the SDK only accepts the course with East = 0 and counter clockwise orientation. The SDK has a helper function to convert this: [NSDK_Navigation convertGeoHeading]
.
[ { "type" : "svp", "points" : [ {"x":751322,"y":6696841,"course":193}, {"x":753691,"y":6692971,"course":218}, {"x":753590,"y":6692930,"course":257}, {"x":753486,"y":6692925,"course":307}, {"x":753464,"y":6692964,"course":354}, {"x":753479,"y":6693010,"course":38}, {"x":756197,"y":6692896,"course":128} ] }, { "type" : "regular", "point" : { "x": 756107, "y": 6658347 } } ]
Mail attachement and file view intents
To retrieve our tour files, we give the tutorial the ability to open mail attachments, for example from the google mail app. And we also give it the ability to open a file from a file explorer in Android. Basically we need two new intent filters for our MainActivity. These filters are only an example and not meant to handle all possible mail apps or file explorer apps.
<activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:mimeType="application/octet-stream" /> <data android:pathPattern=".*\\.tour" /> <data android:pathPattern=".*..*..*..*..*.tour" /> <data android:pathPattern=".*..*..*..*.tour" /> <data android:pathPattern=".*..*..*.tour" /> <data android:pathPattern=".*..*.tour" /> <data android:pathPattern=".*.tour" /> <data android:host="*" /> <data android:scheme="file" /> </intent-filter> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:mimeType="application/octet-stream" /> <data android:scheme="content" /> <data android:host="*" /> </intent-filter> </activity>
To deal with such intents, we alter our handleIntent method in our MainActivity. Basically we try to extract an InputStream from our intent if the uri scheme is of type "file" or "content". We delegate the stream to a new CalculateTourRunnable, which is responsible for parsing the JSON and feeding the tour to the SDK. The implementation of CalculateTourRunnable is discussed in the next step. Don't forget to reset the intent to null after handling it. This avoids reloading the tour every time you enter the MainAcitivity.
void handleIntent(Intent intent) { Uri uri = getIntent().getData(); if (uri != null) { String scheme = uri.getScheme(); if (scheme.equals("file") || scheme.equals("content")) { try { InputStream inputStream = getContentResolver().openInputStream(uri); CalculateTourRunnable runnable = new CalculateTourRunnable(inputStream, this, calcRouteObserver); SDKJobQueue.getInstance().push(runnable); setIntent(null); return; } catch (Exception e) { e.printStackTrace(); } } } ... }
CalculateTourRunnable
The implementation of our CalculateTourRunnable is straight forward. It parses the destinations from the file and passes them as a new tour to the NavigationSDK. First we call createTour
with our current position. Then we add all the destinations with addStationToTour
and addSVPsToTour
. Finally we call prepareNavigationForTour with the calculateRouteObserver from our MainActivity to calculate the route. If the route calculation fails, prepareNavigationForTour
will most likely throw a PrepareNavigationException where you can check the error code and the section of the tour the error belongs to. For non fatal errors you can obtain this information also from the PrepareNavigationResult of the call.
Attention
SVP stations require a course. For this example we assume the course is given with North = 0 degrees and clockwise orientation. Please note that the SDK only accepts the course with East = 0 and counter clockwise orientation. The SDK has a helper function to convert this: convertGeoHeading
.
package com.ptvag.navigation.tutorial; import android.app.Activity; import android.content.Intent; import android.support.annotation.NonNull; import android.util.Log; import com.ptvag.navigation.sdk.CalculateTourException; import com.ptvag.navigation.sdk.CalculateTourResult; import com.ptvag.navigation.sdk.GPSManager; import com.ptvag.navigation.sdk.NavigationException; import com.ptvag.navigation.sdk.NavigationSDK; import com.ptvag.navigation.sdk.Observer; import com.ptvag.navigation.sdk.SDKErrorCode; import com.ptvag.navigation.sdk.SVPWayPoint; import com.ptvag.navigation.sdk.SVPWayPointArray; import com.ptvag.navigation.sdk.Tour; import com.ptvag.navigation.sdk.WayPoint; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; class CalculateTourRunnable implements Runnable { private final Observer observer; private final InputStream tourStream; private final Activity activity; public CalculateTourRunnable(InputStream tourStream, Activity activity, Observer observer) { this.tourStream = tourStream; this.activity = activity; this.observer = observer; } @Override public void run() { try { String content = readFile(); if (content.isEmpty()) return; Tour tour = new Tour(getStart()); JSONArray array = new JSONArray(content); for (int i = 0; i < array.length(); ++i) { JSONObject point = array.getJSONObject(i); if (point.getString("type").toUpperCase().equals("SVP")) { JSONArray points = point.getJSONArray("points"); SVPWayPointArray wps = new SVPWayPointArray(points.length()); for (int j = 0; j < points.length(); ++j) { wps.set(j, toSVP(points.getJSONObject(j)) ); } tour.addSVPs(wps); } else { WayPoint wp = toWP(point.getJSONObject("point")); tour.addStation(wp); } } try { CalculateTourResult result = NavigationSDK.calculateTour(tour, observer); if (result.getError() != SDKErrorCode.Ok) { Log.v("Error", result.getError().toString()); } else { Intent intent = new Intent(activity, RouteInfoActivity.class); activity.startActivity(intent); } } catch (CalculateTourException ex) { Log.v("Error", "Error: " + ex.getError() + ", TourSection: " + ex.getTourSection()); } } catch (Exception e) { e.printStackTrace(); } } @NonNull private WayPoint getStart() { WayPoint start; try { // set current position as starting point start = new WayPoint(GPSManager.getInstance().getCurrentPosition()); } catch (NavigationException e) { // if it fails use Karlsruhe start = new WayPoint(934177, 6268747); } return start; } private SVPWayPoint toSVP(JSONObject obj) throws JSONException { return new SVPWayPoint(obj.getInt("x"), obj.getInt("y"), NavigationSDK.convertGeoHeading(obj.getInt("course"))); } private WayPoint toWP(JSONObject obj) throws JSONException { return new WayPoint(obj.getInt("x"), obj.getInt("y")); } @NonNull private String readFile() throws IOException { BufferedReader streamReader = new BufferedReader(new InputStreamReader(tourStream, "UTF-8")); StringBuilder responseStrBuilder = new StringBuilder(); String inputStr; while ((inputStr = streamReader.readLine()) != null) responseStrBuilder.append(inputStr); return responseStrBuilder.toString(); } }
Attention
Alternative routes are currently only supported for the first destination of your tour and only if it is a regular destination.
Switch to next destination
Currently you have to create a new tour, add all destinations except the first and call calculateTour
again. This will probably change in a future release.