package com.nutiteq.advancedmap3;
import java.io.File;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import com.nutiteq.core.MapPos;
import com.nutiteq.core.MapRange;
import com.nutiteq.core.MapVec;
import com.nutiteq.datasources.LocalVectorDataSource;
import com.nutiteq.layers.VectorLayer;
import com.nutiteq.packagemanager.NutiteqPackageManager;
import com.nutiteq.packagemanager.PackageAction;
import com.nutiteq.packagemanager.PackageErrorType;
import com.nutiteq.packagemanager.PackageManager;
import com.nutiteq.packagemanager.PackageManagerListener;
import com.nutiteq.packagemanager.PackageStatus;
import com.nutiteq.routing.NutiteqOnlineRoutingService;
import com.nutiteq.routing.PackageManagerRoutingService;
import com.nutiteq.routing.RoutingAction;
import com.nutiteq.routing.RoutingInstruction;
import com.nutiteq.routing.RoutingRequest;
import com.nutiteq.routing.RoutingResult;
import com.nutiteq.routing.RoutingService;
import com.nutiteq.styles.BalloonPopupMargins;
import com.nutiteq.styles.BalloonPopupStyleBuilder;
import com.nutiteq.styles.LineJointType;
import com.nutiteq.styles.LineStyleBuilder;
import com.nutiteq.styles.MarkerStyle;
import com.nutiteq.styles.MarkerStyleBuilder;
import com.nutiteq.ui.ClickType;
import com.nutiteq.ui.MapClickInfo;
import com.nutiteq.ui.MapEventListener;
import com.nutiteq.ui.VectorElementsClickInfo;
import com.nutiteq.utils.AssetUtils;
import com.nutiteq.utils.BitmapUtils;
import com.nutiteq.vectorelements.BalloonPopup;
import com.nutiteq.vectorelements.Line;
import com.nutiteq.vectorelements.Marker;
import com.nutiteq.vectorelements.NMLModel;
import com.nutiteq.wrappedcommons.MapPosVector;
import com.nutiteq.wrappedcommons.RoutingInstructionVector;
/**
* A sample demonstrating how to use Nutiteq routing engine to calculate offline routes.
* First a package is downloaded asynchronously. Until the package is fully downloaded,
* online routing service is used as a fallback. Once the package is available, routing
* works offline.
*/
public class OfflineRoutingActivity extends VectorMapSampleBaseActivity {
// add packages what you want to download
private static String[] PACKAGE_IDS = new String[]{"EE-routing",
"LT-routing"};
private static String ROUTING_PACKAGEMANAGER_SOURCE = "routing:nutiteq.osm.car";
private static String ROUTING_SERVICE_SOURCE = "nutiteq.osm.car";
/**
* This MapListener waits for two clicks on map - first to set routing start point, and then
* second to mark end point and start routing service.
*/
public class RouteMapEventListener extends MapEventListener {
private MapPos startPos;
private MapPos stopPos;
@Override
public void onVectorElementClicked(VectorElementsClickInfo vectorElementsClickInfo) {
}
// Map View manipulation handlers
@Override
public void onMapClicked(MapClickInfo mapClickInfo) {
if (mapClickInfo.getClickType() == ClickType.CLICK_TYPE_LONG) {
MapPos clickPos = mapClickInfo.getClickPos();
MapPos wgs84Clickpos = mapView.getOptions().getBaseProjection().toWgs84(clickPos);
Log.d(Const.LOG_TAG,"onMapClicked " + wgs84Clickpos);
if (startPos == null) {
// set start, or start again
startPos = clickPos;
setStartMarker(clickPos);
} else if (stopPos == null) {
// set stop and calculate
stopPos = clickPos;
setStopMarker(clickPos);
showRoute(startPos, stopPos);
// restart to force new route next time
startPos = null;
stopPos = null;
}
}
}
@Override
public void onMapMoved() {
}
}
/**
* Listener for package manager events. Contains only empty methods.
*/
class PackageListener extends PackageManagerListener {
@Override
public void onPackageListUpdated() {
Log.d(Const.LOG_TAG, "Package list updated");
int downloadedPackages = 0;
for(int i=0; i<PACKAGE_IDS.length;i++){
boolean alreadyDownloaded = getPackageIfNotExists(PACKAGE_IDS[i]);
if(alreadyDownloaded){
downloadedPackages ++;
}
}
// if all downloaded, can start with offline routing
if(downloadedPackages == PACKAGE_IDS.length){
offlinePackageReady = true;
}
}
private boolean getPackageIfNotExists(String packageId) {
PackageStatus status = packageManager.getLocalPackageStatus(packageId, -1);
if (status == null) {
packageManager.startPackageDownload(packageId);
return false;
}else if(status.getCurrentAction() == PackageAction.PACKAGE_ACTION_READY){
Log.d(Const.LOG_TAG, packageId + " is downloaded and ready");
return true;
}
return false;
}
@Override
public void onPackageListFailed() {
Log.e(Const.LOG_TAG, "Package list update failed");
}
@Override
public void onPackageStatusChanged(String id, int version, PackageStatus status) {
}
@Override
public void onPackageCancelled(String id, int version) {
}
@Override
public void onPackageUpdated(String id, int version) {
Log.d(Const.LOG_TAG, "Offline package updated: " + id);
// if last downloaded
if (id.equals(PACKAGE_IDS[PACKAGE_IDS.length-1])) {
offlinePackageReady = true;
}
}
@Override
public void onPackageFailed(String id, int version, PackageErrorType errorType) {
Log.e(Const.LOG_TAG, "Offline package update failed: " + id);
}
}
private RoutingService onlineRoutingService;
private RoutingService offlineRoutingService;
private PackageManager packageManager;
private boolean shortestPathRunning;
private boolean offlinePackageReady;
private Marker startMarker;
private Marker stopMarker;
private MarkerStyle instructionUp;
private MarkerStyle instructionLeft;
private MarkerStyle instructionRight;
private LocalVectorDataSource routeDataSource;
private LocalVectorDataSource routeStartStopDataSource;
private BalloonPopupStyleBuilder balloonPopupStyleBuilder;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// create PackageManager instance for dealing with offline packages
File packageFolder = new File(getApplicationContext().getExternalFilesDir(null), "routingpackages");
if (!(packageFolder.mkdirs() || packageFolder.isDirectory())) {
Log.e(Const.LOG_TAG, "Could not create package folder!");
}
packageManager = new NutiteqPackageManager(ROUTING_PACKAGEMANAGER_SOURCE, packageFolder.getAbsolutePath());
packageManager.setPackageManagerListener(new PackageListener());
packageManager.start();
// fetch list of available packages from server. Note that this is asynchronous operation and listener will be notified via onPackageListUpdated when this succeeds.
packageManager.startPackageListDownload();
// create offline routing service connected to package manager
offlineRoutingService = new PackageManagerRoutingService(packageManager);
// create additional online routing service that will be used when offline package is not yet downloaded or offline routing fails
onlineRoutingService = new NutiteqOnlineRoutingService(ROUTING_SERVICE_SOURCE);
// define layer and datasource for route line and instructions
routeDataSource = new LocalVectorDataSource(baseProjection);
VectorLayer routeLayer = new VectorLayer(routeDataSource);
mapView.getLayers().add(routeLayer);
// define layer and datasource for route start and stop markers
routeStartStopDataSource = new LocalVectorDataSource(baseProjection);
// Initialize a vector layer with the previous data source
VectorLayer vectorLayer = new VectorLayer(routeStartStopDataSource);
// Add the previous vector layer to the map
mapView.getLayers().add(vectorLayer);
// Set visible zoom range for the vector layer
vectorLayer.setVisibleZoomRange(new MapRange(0, 22));
// set route listener
RouteMapEventListener mapListener = new RouteMapEventListener();
mapView.setMapEventListener(mapListener);
// create markers for start & end, and a layer for them
MarkerStyleBuilder markerStyleBuilder = new MarkerStyleBuilder();
markerStyleBuilder.setBitmap(BitmapUtils
.createBitmapFromAndroidBitmap(BitmapFactory.decodeResource(
getResources(), R.drawable.olmarker)));
markerStyleBuilder.setHideIfOverlapped(false);
markerStyleBuilder.setSize(30);
markerStyleBuilder.setColor(new com.nutiteq.graphics.Color(Color.GREEN));
startMarker = new Marker(new MapPos(0, 0), markerStyleBuilder.buildStyle());
startMarker.setVisible(false);
markerStyleBuilder.setColor(new com.nutiteq.graphics.Color(Color.RED));
stopMarker = new Marker(new MapPos(0, 0), markerStyleBuilder.buildStyle());
stopMarker.setVisible(false);
routeStartStopDataSource.add(startMarker);
routeStartStopDataSource.add(stopMarker);
markerStyleBuilder.setColor(new com.nutiteq.graphics.Color(Color.WHITE));
markerStyleBuilder.setBitmap(BitmapUtils
.createBitmapFromAndroidBitmap(BitmapFactory.decodeResource(
getResources(), R.drawable.direction_up)));
instructionUp = markerStyleBuilder.buildStyle();
markerStyleBuilder.setBitmap(BitmapUtils
.createBitmapFromAndroidBitmap(BitmapFactory.decodeResource(
getResources(), R.drawable.direction_upthenleft)));
instructionLeft = markerStyleBuilder.buildStyle();
markerStyleBuilder.setBitmap(BitmapUtils
.createBitmapFromAndroidBitmap(BitmapFactory.decodeResource(
getResources(), R.drawable.direction_upthenright)));
instructionRight = markerStyleBuilder.buildStyle();
// style for instruction balloons
balloonPopupStyleBuilder = new BalloonPopupStyleBuilder();
balloonPopupStyleBuilder.setTitleMargins(new BalloonPopupMargins(4,4,4,4));
// finally animate map to Estonia
mapView.setFocusPos(mapView.getOptions().getBaseProjection().fromWgs84(new MapPos(25.662893, 58.919365)), 0);
mapView.setZoom(7, 0);
Toast.makeText(getApplicationContext(), "Long-press on map to set route start and finish", Toast.LENGTH_LONG).show();
}
public void showRoute(final MapPos startPos, final MapPos stopPos) {
Log.d(Const.LOG_TAG, "calculating path " + startPos + " to " + stopPos);
if (!offlinePackageReady) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), "Offline package is not ready, using online routing", Toast.LENGTH_SHORT).show();
}
});
}
AsyncTask<Void, Void, RoutingResult> task = new AsyncTask<Void, Void, RoutingResult>() {
public long timeStart;
protected RoutingResult doInBackground(Void... v) {
timeStart = System.currentTimeMillis();
MapPosVector poses = new MapPosVector();
poses.add(startPos);
poses.add(stopPos);
RoutingRequest request = new RoutingRequest(mapView.getOptions().getBaseProjection(), poses);
RoutingResult result;
if (offlinePackageReady) {
result = offlineRoutingService.calculateRoute(request);
} else {
result = onlineRoutingService.calculateRoute(request);
}
return result;
}
protected void onPostExecute(RoutingResult result) {
if (result == null) {
Toast.makeText(getApplicationContext(), "Routing failed", Toast.LENGTH_LONG).show();
shortestPathRunning = false;
return;
}
String routeText = "The route is " + (int) (result.getTotalDistance() / 100) / 10f
+ "km (" + secondsToHours((int) result.getTotalTime())
+ ") calculation: " + (System.currentTimeMillis() - timeStart) + " ms";
Log.i(Const.LOG_TAG,routeText);
Toast.makeText(getApplicationContext(),
routeText
, Toast.LENGTH_LONG).show();
routeDataSource.removeAll();
startMarker.setVisible(false);
routeDataSource.add(createPolyline(startMarker.getGeometry()
.getCenterPos(), stopMarker.getGeometry().getCenterPos(), result));
// add instruction markers
RoutingInstructionVector instructions = result.getInstructions();
boolean first = true;
for (int i = 0; i < instructions.size(); i++) {
RoutingInstruction instruction = instructions.get(i);
if (first) {
Log.d(Const.LOG_TAG,"first instruction");
// set car to first instruction position
first = false;
MapPos firstInstructionPos = result.getPoints().get(instruction.getPointIndex());
// rotate car based on first instruction leg azimuth
float azimuth = (float) instruction.getAzimuth();
// zoom and move map to the first position, to make it navigation-like
// mapView.setFocusPos(firstInstructionPos, 1);
// mapView.setZoom(18, 1);
// mapView.setMapRotation(360 - azimuth, firstInstructionPos, 1);
// mapView.setTilt(30, 1);
} else {
// Log.d(Const.LOG_TAG, instruction.toString());
createRoutePoint(result.getPoints().get(instruction.getPointIndex()), instruction.getStreetName(),
instruction.getTime(), instruction.getDistance(), instruction.getAction(), routeDataSource);
}
}
shortestPathRunning = false;
}
};
if (!shortestPathRunning) {
shortestPathRunning = true;
task.execute();
}
}
protected String secondsToHours(int sec){
int hours = sec / 3600,
remainder = sec % 3600,
minutes = remainder / 60,
seconds = remainder % 60;
return ( (hours < 10 ? "0" : "") + hours
+ "h" + (minutes < 10 ? "0" : "") + minutes
+ "m" + (seconds< 10 ? "0" : "") + seconds+"s" );
}
protected void createRoutePoint(MapPos pos, String name,
double time, double distance, RoutingAction action, LocalVectorDataSource ds) {
MarkerStyle style = instructionUp;
String str = "";
switch (action) {
case ROUTING_ACTION_HEAD_ON:
str = "head on";
break;
case ROUTING_ACTION_FINISH:
str = "finish";
break;
case ROUTING_ACTION_TURN_LEFT:
style = instructionLeft;
str = "turn left";
break;
case ROUTING_ACTION_TURN_RIGHT:
style = instructionRight;
str = "turn right";
break;
case ROUTING_ACTION_UTURN:
str = "u turn";
break;
case ROUTING_ACTION_NO_TURN:
case ROUTING_ACTION_GO_STRAIGHT:
// style = instructionUp;
// str = "continue";
break;
case ROUTING_ACTION_REACH_VIA_LOCATION:
style = instructionUp;
str = "stopover";
break;
case ROUTING_ACTION_ENTER_AGAINST_ALLOWED_DIRECTION:
str = "enter against allowed direction";
break;
case ROUTING_ACTION_LEAVE_AGAINST_ALLOWED_DIRECTION:
break;
case ROUTING_ACTION_ENTER_ROUNDABOUT:
str = "enter roundabout";
break;
case ROUTING_ACTION_STAY_ON_ROUNDABOUT:
str = "stay on roundabout";
break;
case ROUTING_ACTION_LEAVE_ROUNDABOUT:
str = "leave roundabout";
break;
case ROUTING_ACTION_START_AT_END_OF_STREET:
str = "start at end of street";
break;
}
if (!str.equals("")){
Marker marker = new Marker(pos, style);
BalloonPopup popup2 = new BalloonPopup(marker, balloonPopupStyleBuilder.buildStyle(),
str, "");
ds.add(popup2);
ds.add(marker);
}
}
// creates Nutiteq line from GraphHopper response
protected Line createPolyline(MapPos start, MapPos end, RoutingResult result) {
LineStyleBuilder lineStyleBuilder = new LineStyleBuilder();
lineStyleBuilder.setColor(new com.nutiteq.graphics.Color(Color.DKGRAY));
lineStyleBuilder.setLineJointType(LineJointType.LINE_JOINT_TYPE_ROUND);
lineStyleBuilder.setStretchFactor(2);
lineStyleBuilder.setWidth(12);
return new Line(result.getPoints(), lineStyleBuilder.buildStyle());
}
public void setStartMarker(MapPos startPos) {
routeDataSource.removeAll();
stopMarker.setVisible(false);
startMarker.setPos(startPos);
//carModel.setPos(startPos);
startMarker.setVisible(true);
}
public void setStopMarker(MapPos pos) {
stopMarker.setPos(pos);
stopMarker.setVisible(true);
}
}