package com.osmnavigator;
import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.location.Address;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.res.ResourcesCompat;
import android.text.InputType;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import org.mapsforge.map.android.graphics.AndroidGraphicFactory;
import org.mapsforge.map.rendertheme.ExternalRenderTheme;
import org.mapsforge.map.rendertheme.XmlRenderTheme;
import org.osmdroid.api.IMapController;
import org.osmdroid.bonuspack.clustering.RadiusMarkerClusterer;
import org.osmdroid.bonuspack.kml.KmlDocument;
import org.osmdroid.bonuspack.kml.KmlFeature;
import org.osmdroid.bonuspack.kml.KmlFolder;
import org.osmdroid.bonuspack.kml.KmlPlacemark;
import org.osmdroid.bonuspack.kml.KmlPoint;
import org.osmdroid.bonuspack.kml.KmlTrack;
import org.osmdroid.bonuspack.kml.LineStyle;
import org.osmdroid.bonuspack.kml.Style;
import org.osmdroid.bonuspack.location.FlickrPOIProvider;
import org.osmdroid.bonuspack.location.GeoNamesPOIProvider;
import org.osmdroid.bonuspack.location.GeocoderNominatim;
import org.osmdroid.bonuspack.location.OverpassAPIProvider;
import org.osmdroid.bonuspack.location.POI;
import org.osmdroid.bonuspack.location.PicasaPOIProvider;
import org.osmdroid.bonuspack.routing.GoogleRoadManager;
import org.osmdroid.bonuspack.routing.GraphHopperRoadManager;
import org.osmdroid.bonuspack.routing.OSRMRoadManager;
import org.osmdroid.bonuspack.routing.Road;
import org.osmdroid.bonuspack.routing.RoadManager;
import org.osmdroid.bonuspack.routing.RoadNode;
import org.osmdroid.bonuspack.utils.BonusPackHelper;
import org.osmdroid.config.Configuration;
import org.osmdroid.events.MapEventsReceiver;
import org.osmdroid.mapsforge.MapsForgeTileProvider;
import org.osmdroid.mapsforge.MapsForgeTileSource;
import org.osmdroid.tileprovider.MapTileProviderBase;
import org.osmdroid.tileprovider.MapTileProviderBasic;
import org.osmdroid.tileprovider.cachemanager.CacheManager;
import org.osmdroid.tileprovider.tilesource.ITileSource;
import org.osmdroid.tileprovider.tilesource.MapBoxTileSource;
import org.osmdroid.tileprovider.tilesource.OnlineTileSourceBase;
import org.osmdroid.tileprovider.tilesource.TileSourceFactory;
import org.osmdroid.tileprovider.util.ManifestUtil;
import org.osmdroid.tileprovider.util.SimpleRegisterReceiver;
import org.osmdroid.util.BoundingBox;
import org.osmdroid.util.GeoPoint;
import org.osmdroid.util.NetworkLocationIgnorer;
import org.osmdroid.views.MapView;
import org.osmdroid.views.overlay.FolderOverlay;
import org.osmdroid.views.overlay.MapEventsOverlay;
import org.osmdroid.views.overlay.Marker;
import org.osmdroid.views.overlay.Marker.OnMarkerDragListener;
import org.osmdroid.views.overlay.Overlay;
import org.osmdroid.views.overlay.Polygon;
import org.osmdroid.views.overlay.Polyline;
import org.osmdroid.views.overlay.ScaleBarOverlay;
import org.osmdroid.views.overlay.TilesOverlay;
import org.osmdroid.views.overlay.infowindow.BasicInfoWindow;
import org.osmdroid.views.overlay.infowindow.InfoWindow;
import org.osmdroid.views.overlay.infowindow.MarkerInfoWindow;
import org.osmdroid.views.overlay.mylocation.DirectedLocationOverlay;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Stack;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Simple and general-purpose map/navigation Android application, including a KML viewer and editor.
* It is based on osmdroid and OSMBonusPack
* @see <a href="https://github.com/MKergall/osmbonuspack">OSMBonusPack</a>
* @author M.Kergall
*
*/
public class MapActivity extends Activity implements MapEventsReceiver, LocationListener, SensorEventListener, MapView.OnFirstLayoutListener {
protected MapView map;
protected GeoPoint startPoint, destinationPoint;
protected ArrayList<GeoPoint> viaPoints;
protected static int START_INDEX=-2, DEST_INDEX=-1;
protected FolderOverlay mItineraryMarkers;
//for departure, destination and viapoints
protected Marker markerStart, markerDestination;
protected ViaPointInfoWindow mViaPointInfoWindow;
protected DirectedLocationOverlay myLocationOverlay;
//MyLocationNewOverlay myLocationNewOverlay;
protected LocationManager mLocationManager;
//protected SensorManager mSensorManager;
//protected Sensor mOrientation;
protected boolean mTrackingMode;
Button mTrackingModeButton;
float mAzimuthAngleSpeed = 0.0f;
protected Polygon mDestinationPolygon; //enclosing polygon of destination location
public static Road[] mRoads; //made static to pass between activities
protected int mSelectedRoad;
protected Polyline[] mRoadOverlays;
protected FolderOverlay mRoadNodeMarkers;
protected static final int ROUTE_REQUEST = 1;
static final int OSRM=0, GRAPHHOPPER_FASTEST=1, GRAPHHOPPER_BICYCLE=2, GRAPHHOPPER_PEDESTRIAN=3, GOOGLE_FASTEST=4;
int mWhichRouteProvider;
public static ArrayList<POI> mPOIs; //made static to pass between activities
RadiusMarkerClusterer mPoiMarkers;
AutoCompleteTextView poiTagText;
protected static final int POIS_REQUEST = 2;
protected FolderOverlay mKmlOverlay; //root container of overlays from KML reading
public static KmlDocument mKmlDocument; //made static to pass between activities
public static Stack<KmlFeature> mKmlStack; //passed between activities, top is the current KmlFeature to edit.
public static KmlFolder mKmlClipboard; //passed between activities. Folder for multiple items selection.
boolean mIsRecordingTrack;
FriendsManager mFriendsManager;
static String SHARED_PREFS_APPKEY = "OSMNavigator";
static String PREF_LOCATIONS_KEY = "PREF_LOCATIONS";
OnlineTileSourceBase MAPBOXSATELLITELABELLED;
boolean mNightMode;
static final String userAgent = "OsmNavigator/1.0";
static String graphHopperApiKey;
static String mapQuestApiKey;
static String flickrApiKey;
static String geonamesAccount;
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Configuration.getInstance().setOsmdroidBasePath(new File(Environment.getExternalStorageDirectory(), "osmdroid"));
Configuration.getInstance().setOsmdroidTileCache(new File(Environment.getExternalStorageDirectory(), "osmdroid/tiles"));
LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflater.inflate(R.layout.main, null);
setContentView(v);
SharedPreferences prefs = getSharedPreferences("OSMNAVIGATOR", MODE_PRIVATE);
MAPBOXSATELLITELABELLED = new MapBoxTileSource("MapBoxSatelliteLabelled", 1, 19, 256, ".png");
((MapBoxTileSource) MAPBOXSATELLITELABELLED).retrieveAccessToken(this);
((MapBoxTileSource) MAPBOXSATELLITELABELLED).retrieveMapBoxMapId(this);
TileSourceFactory.addTileSource(MAPBOXSATELLITELABELLED);
graphHopperApiKey = ManifestUtil.retrieveKey(this, "GRAPHHOPPER_API_KEY");
mapQuestApiKey = ManifestUtil.retrieveKey(this, "MAPQUEST_API_KEY");
flickrApiKey = ManifestUtil.retrieveKey(this, "FLICKR_API_KEY");
geonamesAccount = ManifestUtil.retrieveKey(this, "GEONAMES_ACCOUNT");
map = (MapView) v.findViewById(R.id.map);
String tileProviderName = prefs.getString("TILE_PROVIDER", "Mapnik");
mNightMode = prefs.getBoolean("NIGHT_MODE", false);
try {
ITileSource tileSource = TileSourceFactory.getTileSource(tileProviderName);
map.setTileSource(tileSource);
} catch (IllegalArgumentException e) {
map.setTileSource(TileSourceFactory.MAPNIK);
}
if (mNightMode)
map.getOverlayManager().getTilesOverlay().setColorFilter(TilesOverlay.INVERT_COLORS);
map.setTilesScaledToDpi(true);
map.setBuiltInZoomControls(true);
map.setMultiTouchControls(true);
IMapController mapController = map.getController();
//To use MapEventsReceiver methods, we add a MapEventsOverlay:
MapEventsOverlay overlay = new MapEventsOverlay(this);
map.getOverlays().add(overlay);
mLocationManager = (LocationManager)getSystemService(LOCATION_SERVICE);
//mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
//mOrientation = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
//map prefs:
mapController.setZoom(prefs.getInt("MAP_ZOOM_LEVEL", 5));
mapController.setCenter(new GeoPoint((double) prefs.getFloat("MAP_CENTER_LAT", 48.5f),
(double)prefs.getFloat("MAP_CENTER_LON", 2.5f)));
myLocationOverlay = new DirectedLocationOverlay(this);
map.getOverlays().add(myLocationOverlay);
if (savedInstanceState == null){
Location location = null;
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
location = mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if (location == null)
location = mLocationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
}
if (location != null) {
//location known:
onLocationChanged(location);
} else {
//no location known: hide myLocationOverlay
myLocationOverlay.setEnabled(false);
}
startPoint = null;
destinationPoint = null;
viaPoints = new ArrayList<GeoPoint>();
} else {
myLocationOverlay.setLocation((GeoPoint)savedInstanceState.getParcelable("location"));
//TODO: restore other aspects of myLocationOverlay...
startPoint = savedInstanceState.getParcelable("start");
destinationPoint = savedInstanceState.getParcelable("destination");
viaPoints = savedInstanceState.getParcelableArrayList("viapoints");
}
ScaleBarOverlay scaleBarOverlay = new ScaleBarOverlay(map);
map.getOverlays().add(scaleBarOverlay);
// Itinerary markers:
mItineraryMarkers = new FolderOverlay();
mItineraryMarkers.setName(getString(R.string.itinerary_markers_title));
map.getOverlays().add(mItineraryMarkers);
mViaPointInfoWindow = new ViaPointInfoWindow(R.layout.itinerary_bubble, map);
updateUIWithItineraryMarkers();
//Tracking system:
mTrackingModeButton = (Button)findViewById(R.id.buttonTrackingMode);
mTrackingModeButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
mTrackingMode = !mTrackingMode;
updateUIWithTrackingMode();
}
});
if (savedInstanceState != null){
mTrackingMode = savedInstanceState.getBoolean("tracking_mode");
updateUIWithTrackingMode();
} else
mTrackingMode = false;
mIsRecordingTrack = false; //TODO restore state
AutoCompleteOnPreferences departureText = (AutoCompleteOnPreferences) findViewById(R.id.editDeparture);
departureText.setPrefKeys(SHARED_PREFS_APPKEY, PREF_LOCATIONS_KEY);
Button searchDepButton = (Button)findViewById(R.id.buttonSearchDep);
searchDepButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
handleSearchButton(START_INDEX, R.id.editDeparture);
}
});
AutoCompleteOnPreferences destinationText = (AutoCompleteOnPreferences) findViewById(R.id.editDestination);
destinationText.setPrefKeys(SHARED_PREFS_APPKEY, PREF_LOCATIONS_KEY);
Button searchDestButton = (Button)findViewById(R.id.buttonSearchDest);
searchDestButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
handleSearchButton(DEST_INDEX, R.id.editDestination);
}
});
View expander = findViewById(R.id.expander);
expander.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
View searchPanel = findViewById(R.id.search_panel);
if (searchPanel.getVisibility() == View.VISIBLE){
searchPanel.setVisibility(View.GONE);
} else {
searchPanel.setVisibility(View.VISIBLE);
}
}
});
View searchPanel = findViewById(R.id.search_panel);
searchPanel.setVisibility(prefs.getInt("PANEL_VISIBILITY", View.VISIBLE));
registerForContextMenu(searchDestButton);
//context menu for clicking on the map is registered on this button.
//(a little bit strange, but if we register it on mapView, it will catch map drag events)
//Route and Directions
mWhichRouteProvider = prefs.getInt("ROUTE_PROVIDER", OSRM);
mRoadNodeMarkers = new FolderOverlay();
mRoadNodeMarkers.setName("Route Steps");
map.getOverlays().add(mRoadNodeMarkers);
if (savedInstanceState != null){
//STATIC mRoad = savedInstanceState.getParcelable("road");
updateUIWithRoads(mRoads);
}
//POIs:
//POI search interface:
String[] poiTags = getResources().getStringArray(R.array.poi_tags);
poiTagText = (AutoCompleteTextView) findViewById(R.id.poiTag);
ArrayAdapter<String> poiAdapter = new ArrayAdapter<String>(this,
android.R.layout.simple_dropdown_item_1line, poiTags);
poiTagText.setAdapter(poiAdapter);
Button setPOITagButton = (Button) findViewById(R.id.buttonSetPOITag);
setPOITagButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
//Hide the soft keyboard:
InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(poiTagText.getWindowToken(), 0);
//Start search:
String feature = poiTagText.getText().toString();
if (!feature.equals(""))
Toast.makeText(v.getContext(), "Searching:\n"+feature, Toast.LENGTH_LONG).show();
getPOIAsync(feature);
}
});
//POI markers:
mPoiMarkers = new RadiusMarkerClusterer(this);
Drawable clusterIconD = ResourcesCompat.getDrawable(getResources(), R.drawable.marker_poi_cluster, null);
Bitmap clusterIcon = ((BitmapDrawable)clusterIconD).getBitmap();
mPoiMarkers.setIcon(clusterIcon);
mPoiMarkers.mAnchorV = Marker.ANCHOR_BOTTOM;
mPoiMarkers.mTextAnchorU = 0.70f;
mPoiMarkers.mTextAnchorV = 0.27f;
mPoiMarkers.getTextPaint().setTextSize(12 * getResources().getDisplayMetrics().density);
map.getOverlays().add(mPoiMarkers);
if (savedInstanceState != null){
//STATIC - mPOIs = savedInstanceState.getParcelableArrayList("poi");
updateUIWithPOI(mPOIs, "");
}
//KML handling:
mKmlOverlay = null;
if (savedInstanceState != null){
//STATIC - mKmlDocument = savedInstanceState.getParcelable("kml");
updateUIWithKml();
} else { //first launch:
mKmlDocument = new KmlDocument();
mKmlStack = new Stack<KmlFeature>();
mKmlClipboard = new KmlFolder();
//check if intent has been passed with a kml URI to load (url or file)
Intent onCreateIntent = getIntent();
if (onCreateIntent.getAction().equals(Intent.ACTION_VIEW)){
String uri = onCreateIntent.getDataString();
openFile(uri, true, false);
}
}
//Sharing
mFriendsManager = new FriendsManager(this, map);
mFriendsManager.onCreate(savedInstanceState);
checkPermissions();
Button menuButton = (Button) findViewById(R.id.buttonMenu);
menuButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
openOptionsMenu();
}
});
}
final private int REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = 124;
void checkPermissions() {
List<String> permissions = new ArrayList<>();
String message = "Application permissions:";
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
permissions.add(Manifest.permission.ACCESS_FINE_LOCATION);
message += "\nLocation to show user location.";
}
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
message += "\nStorage access to store map tiles.";
}
if (!permissions.isEmpty()) {
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
String[] params = permissions.toArray(new String[permissions.size()]);
ActivityCompat.requestPermissions(this, params, REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
} // else: We already have permissions, so handle as normal
}
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS: {
Map<String, Integer> perms = new HashMap<>();
// Initial
perms.put(Manifest.permission.WRITE_EXTERNAL_STORAGE, PackageManager.PERMISSION_GRANTED);
// Fill with results
for (int i = 0; i < permissions.length; i++)
perms.put(permissions[i], grantResults[i]);
// Check for WRITE_EXTERNAL_STORAGE
Boolean storage = perms.get(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
if (!storage) {
// Permission Denied
Toast.makeText(this, "Storage permission is required to store map tiles to reduce data usage and for offline usage.", Toast.LENGTH_LONG).show();
} // else: permission was granted, yay!
}
}
}
void setViewOn(BoundingBox bb){
if (bb != null){
map.zoomToBoundingBox(bb, true);
}
}
//--- Stuff for setting the mapview on a box at startup:
BoundingBox mInitialBoundingBox = null;
void setInitialViewOn(BoundingBox bb) {
if (map.getScreenRect(null).height() == 0) {
mInitialBoundingBox = bb;
map.addOnFirstLayoutListener(this);
} else
map.zoomToBoundingBox(bb, false);
}
@Override
public void onFirstLayout(View v, int left, int top, int right, int bottom) {
if (mInitialBoundingBox != null)
map.zoomToBoundingBox(mInitialBoundingBox, false);
}
//---
void savePrefs(){
SharedPreferences prefs = getSharedPreferences("OSMNAVIGATOR", MODE_PRIVATE);
SharedPreferences.Editor ed = prefs.edit();
ed.putInt("MAP_ZOOM_LEVEL", map.getZoomLevel());
GeoPoint c = (GeoPoint) map.getMapCenter();
ed.putFloat("MAP_CENTER_LAT", (float)c.getLatitude());
ed.putFloat("MAP_CENTER_LON", (float)c.getLongitude());
View searchPanel = findViewById(R.id.search_panel);
ed.putInt("PANEL_VISIBILITY", searchPanel.getVisibility());
MapTileProviderBase tileProvider = map.getTileProvider();
String tileProviderName = tileProvider.getTileSource().name();
ed.putString("TILE_PROVIDER", tileProviderName);
ed.putBoolean("NIGHT_MODE", mNightMode);
ed.putInt("ROUTE_PROVIDER", mWhichRouteProvider);
ed.apply();
}
/**
* callback to store activity status before a restart (orientation change for instance)
*/
@Override protected void onSaveInstanceState (Bundle outState){
outState.putParcelable("location", myLocationOverlay.getLocation());
outState.putBoolean("tracking_mode", mTrackingMode);
outState.putParcelable("start", startPoint);
outState.putParcelable("destination", destinationPoint);
outState.putParcelableArrayList("viapoints", viaPoints);
//STATIC - outState.putParcelable("road", mRoad);
//STATIC - outState.putParcelableArrayList("poi", mPOIs);
//STATIC - outState.putParcelable("kml", mKmlDocument);
//STATIC - outState.putParcelable("friends", mFriends);
mFriendsManager.onSaveInstanceState(outState);
savePrefs();
}
@Override protected void onActivityResult (int requestCode, int resultCode, Intent intent) {
switch (requestCode) {
case FriendsManager.START_SHARING_REQUEST:
case FriendsManager.FRIENDS_REQUEST:
mFriendsManager.onActivityResult(requestCode, resultCode, intent);
break;
case ROUTE_REQUEST:
if (resultCode == RESULT_OK) {
int nodeId = intent.getIntExtra("NODE_ID", 0);
map.getController().setCenter(mRoads[mSelectedRoad].mNodes.get(nodeId).mLocation);
Marker roadMarker = (Marker) mRoadNodeMarkers.getItems().get(nodeId);
roadMarker.showInfoWindow();
}
break;
case POIS_REQUEST:
if (resultCode == RESULT_OK) {
int id = intent.getIntExtra("ID", 0);
map.getController().setCenter(mPOIs.get(id).mLocation);
Marker poiMarker = mPoiMarkers.getItem(id);
poiMarker.showInfoWindow();
}
break;
case KmlTreeActivity.KML_TREE_REQUEST:
mKmlStack.pop();
updateUIWithKml();
if (intent == null)
break;
KmlFeature selectedFeature = intent.getParcelableExtra("KML_FEATURE");
if (selectedFeature == null)
break;
BoundingBox bb = selectedFeature.getBoundingBox();
setViewOn(bb);
break;
case KmlStylesActivity.KML_STYLES_REQUEST:
updateUIWithKml();
break;
default:
break;
}
}
/* String getBestProvider(){
String bestProvider = null;
//bestProvider = locationManager.getBestProvider(new Criteria(), true); // => returns "Network Provider"!
if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER))
bestProvider = LocationManager.GPS_PROVIDER;
else if (locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER))
bestProvider = LocationManager.NETWORK_PROVIDER;
return bestProvider;
} */
boolean startLocationUpdates(){
boolean result = false;
for (final String provider : mLocationManager.getProviders(true)) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
mLocationManager.requestLocationUpdates(provider, 2 * 1000, 0.0f, this);
result = true;
}
}
return result;
}
@Override protected void onResume() {
super.onResume();
boolean isOneProviderEnabled = startLocationUpdates();
myLocationOverlay.setEnabled(isOneProviderEnabled);
//TODO: not used currently
//mSensorManager.registerListener(this, mOrientation, SensorManager.SENSOR_DELAY_NORMAL);
//sensor listener is causing a high CPU consumption...
mFriendsManager.onResume();
}
@Override protected void onPause() {
super.onPause();
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
mLocationManager.removeUpdates(this);
}
//TODO: mSensorManager.unregisterListener(this);
mFriendsManager.onPause();
savePrefs();
}
void updateUIWithTrackingMode(){
if (mTrackingMode){
mTrackingModeButton.setBackgroundResource(R.drawable.btn_tracking_on);
if (myLocationOverlay.isEnabled()&& myLocationOverlay.getLocation() != null){
map.getController().animateTo(myLocationOverlay.getLocation());
}
map.setMapOrientation(-mAzimuthAngleSpeed);
mTrackingModeButton.setKeepScreenOn(true);
} else {
mTrackingModeButton.setBackgroundResource(R.drawable.btn_tracking_off);
map.setMapOrientation(0.0f);
mTrackingModeButton.setKeepScreenOn(false);
}
}
//------------- Geocoding and Reverse Geocoding
/**
* Reverse Geocoding
*/
public String getAddress(GeoPoint p){
GeocoderNominatim geocoder = new GeocoderNominatim(userAgent);
String theAddress;
try {
double dLatitude = p.getLatitude();
double dLongitude = p.getLongitude();
List<Address> addresses = geocoder.getFromLocation(dLatitude, dLongitude, 1);
StringBuilder sb = new StringBuilder();
if (addresses.size() > 0) {
Address address = addresses.get(0);
int n = address.getMaxAddressLineIndex();
for (int i=0; i<=n; i++) {
if (i!=0)
sb.append(", ");
sb.append(address.getAddressLine(i));
}
theAddress = sb.toString();
} else {
theAddress = null;
}
} catch (IOException e) {
theAddress = null;
}
if (theAddress != null) {
return theAddress;
} else {
return "";
}
}
private class GeocodingTask extends AsyncTask<Object, Void, List<Address>> {
int mIndex;
protected List<Address> doInBackground(Object... params) {
String locationAddress = (String)params[0];
mIndex = (Integer)params[1];
GeocoderNominatim geocoder = new GeocoderNominatim(userAgent);
geocoder.setOptions(true); //ask for enclosing polygon (if any)
try {
BoundingBox viewbox = map.getBoundingBox();
List<Address> foundAdresses = geocoder.getFromLocationName(locationAddress, 1,
viewbox.getLatSouth(), viewbox.getLonEast(),
viewbox.getLatNorth(), viewbox.getLonWest(), false);
return foundAdresses;
} catch (Exception e) {
return null;
}
}
protected void onPostExecute(List<Address> foundAdresses) {
if (foundAdresses == null) {
Toast.makeText(getApplicationContext(), "Geocoding error", Toast.LENGTH_SHORT).show();
} else if (foundAdresses.size() == 0) { //if no address found, display an error
Toast.makeText(getApplicationContext(), "Address not found.", Toast.LENGTH_SHORT).show();
} else {
Address address = foundAdresses.get(0); //get first address
String addressDisplayName = address.getExtras().getString("display_name");
if (mIndex == START_INDEX){
startPoint = new GeoPoint(address.getLatitude(), address.getLongitude());
markerStart = updateItineraryMarker(markerStart, startPoint, START_INDEX,
R.string.departure, R.drawable.marker_departure, -1, addressDisplayName);
map.getController().setCenter(startPoint);
} else if (mIndex == DEST_INDEX){
destinationPoint = new GeoPoint(address.getLatitude(), address.getLongitude());
markerDestination = updateItineraryMarker(markerDestination, destinationPoint, DEST_INDEX,
R.string.destination, R.drawable.marker_destination, -1, addressDisplayName);
map.getController().setCenter(destinationPoint);
}
getRoadAsync();
//get and display enclosing polygon:
Bundle extras = address.getExtras();
if (extras != null && extras.containsKey("polygonpoints")){
ArrayList<GeoPoint> polygon = extras.getParcelableArrayList("polygonpoints");
//Log.d("DEBUG", "polygon:"+polygon.size());
updateUIWithPolygon(polygon, addressDisplayName);
} else {
updateUIWithPolygon(null, "");
}
}
}
}
/**
* Geocoding of the departure or destination address
*/
public void handleSearchButton(int index, int editResId){
EditText locationEdit = (EditText)findViewById(editResId);
//Hide the soft keyboard:
InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(locationEdit.getWindowToken(), 0);
String locationAddress = locationEdit.getText().toString();
if (locationAddress.equals("")){
removePoint(index);
map.invalidate();
return;
}
Toast.makeText(this, "Searching:\n"+locationAddress, Toast.LENGTH_LONG).show();
AutoCompleteOnPreferences.storePreference(this, locationAddress, SHARED_PREFS_APPKEY, PREF_LOCATIONS_KEY);
new GeocodingTask().execute(locationAddress, index);
}
//add or replace the polygon overlay
public void updateUIWithPolygon(ArrayList<GeoPoint> polygon, String name){
List<Overlay> mapOverlays = map.getOverlays();
int location = -1;
if (mDestinationPolygon != null)
location = mapOverlays.indexOf(mDestinationPolygon);
mDestinationPolygon = new Polygon();
mDestinationPolygon.setFillColor(0x15FF0080);
mDestinationPolygon.setStrokeColor(0x800000FF);
mDestinationPolygon.setStrokeWidth(5.0f);
mDestinationPolygon.setTitle(name);
BoundingBox bb = null;
if (polygon != null){
mDestinationPolygon.setPoints(polygon);
bb = BoundingBox.fromGeoPoints(polygon);
}
if (location != -1)
mapOverlays.set(location, mDestinationPolygon);
else
mapOverlays.add(1, mDestinationPolygon); //insert just above the MapEventsOverlay.
setViewOn(bb);
map.invalidate();
}
//Async task to reverse-geocode the marker position in a separate thread:
private class ReverseGeocodingTask extends AsyncTask<Object, Void, String> {
Marker marker;
protected String doInBackground(Object... params) {
marker = (Marker)params[0];
return getAddress(marker.getPosition());
}
protected void onPostExecute(String result) {
marker.setSnippet(result);
marker.showInfoWindow();
}
}
//------------ Itinerary markers
class OnItineraryMarkerDragListener implements OnMarkerDragListener {
@Override public void onMarkerDrag(Marker marker) {}
@Override public void onMarkerDragEnd(Marker marker) {
int index = (Integer)marker.getRelatedObject();
if (index == START_INDEX)
startPoint = marker.getPosition();
else if (index == DEST_INDEX)
destinationPoint = marker.getPosition();
else
viaPoints.set(index, marker.getPosition());
//update location:
new ReverseGeocodingTask().execute(marker);
//update route:
getRoadAsync();
}
@Override
public void onMarkerDragStart(Marker marker) {
}
}
final OnItineraryMarkerDragListener mItineraryListener = new OnItineraryMarkerDragListener();
/** Update (or create if null) a marker in itineraryMarkers. */
public Marker updateItineraryMarker(Marker marker, GeoPoint p, int index,
int titleResId, int markerResId, int imageResId, String address) {
Drawable icon = ResourcesCompat.getDrawable(getResources(), markerResId, null);
String title = getResources().getString(titleResId);
if (marker == null){
marker = new Marker(map);
marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM);
marker.setInfoWindow(mViaPointInfoWindow);
marker.setDraggable(true);
marker.setOnMarkerDragListener(mItineraryListener);
mItineraryMarkers.add(marker);
}
marker.setTitle(title);
marker.setPosition(p);
marker.setIcon(icon);
if (imageResId != -1)
marker.setImage(ResourcesCompat.getDrawable(getResources(), imageResId, null));
marker.setRelatedObject(index);
map.invalidate();
if (address != null)
marker.setSnippet(address);
else
//Start geocoding task to get the address and update the Marker description:
new ReverseGeocodingTask().execute(marker);
return marker;
}
public void addViaPoint(GeoPoint p){
viaPoints.add(p);
updateItineraryMarker(null, p, viaPoints.size() - 1,
R.string.viapoint, R.drawable.marker_via, -1, null);
}
public void removePoint(int index){
if (index == START_INDEX){
startPoint = null;
if (markerStart != null){
markerStart.closeInfoWindow();
mItineraryMarkers.remove(markerStart);
markerStart = null;
}
} else if (index == DEST_INDEX){
destinationPoint = null;
if (markerDestination != null){
markerDestination.closeInfoWindow();
mItineraryMarkers.remove(markerDestination);
markerDestination = null;
}
} else {
viaPoints.remove(index);
updateUIWithItineraryMarkers();
}
getRoadAsync();
}
public void updateUIWithItineraryMarkers(){
mItineraryMarkers.closeAllInfoWindows();
mItineraryMarkers.getItems().clear();
//Start marker:
if (startPoint != null){
markerStart = updateItineraryMarker(null, startPoint, START_INDEX,
R.string.departure, R.drawable.marker_departure, -1, null);
}
//Via-points markers if any:
for (int index=0; index<viaPoints.size(); index++){
updateItineraryMarker(null, viaPoints.get(index), index,
R.string.viapoint, R.drawable.marker_via, -1, null);
}
//Destination marker if any:
if (destinationPoint != null){
markerDestination = updateItineraryMarker(null, destinationPoint, DEST_INDEX,
R.string.destination, R.drawable.marker_destination, -1, null);
}
}
//------------ Route and Directions
private void putRoadNodes(Road road){
mRoadNodeMarkers.getItems().clear();
Drawable icon = ResourcesCompat.getDrawable(getResources(), R.drawable.marker_node, null);
int n = road.mNodes.size();
MarkerInfoWindow infoWindow = new MarkerInfoWindow(org.osmdroid.bonuspack.R.layout.bonuspack_bubble, map);
TypedArray iconIds = getResources().obtainTypedArray(R.array.direction_icons);
for (int i=0; i<n; i++){
RoadNode node = road.mNodes.get(i);
String instructions = (node.mInstructions==null ? "" : node.mInstructions);
Marker nodeMarker = new Marker(map);
nodeMarker.setTitle(getString(R.string.step)+ " " + (i+1));
nodeMarker.setSnippet(instructions);
nodeMarker.setSubDescription(Road.getLengthDurationText(this, node.mLength, node.mDuration));
nodeMarker.setPosition(node.mLocation);
nodeMarker.setIcon(icon);
nodeMarker.setInfoWindow(infoWindow); //use a shared infowindow.
int iconId = iconIds.getResourceId(node.mManeuverType, R.drawable.ic_empty);
if (iconId != R.drawable.ic_empty){
Drawable image = ResourcesCompat.getDrawable(getResources(), iconId, null);
nodeMarker.setImage(image);
}
mRoadNodeMarkers.add(nodeMarker);
}
iconIds.recycle();
}
void selectRoad(int roadIndex){
mSelectedRoad = roadIndex;
putRoadNodes(mRoads[roadIndex]);
//Set route info in the text view:
TextView textView = (TextView)findViewById(R.id.routeInfo);
textView.setText(mRoads[roadIndex].getLengthDurationText(this, -1));
for (int i=0; i<mRoadOverlays.length; i++){
Paint p = mRoadOverlays[i].getPaint();
if (i == roadIndex)
p.setColor(0x800000FF); //blue
else
p.setColor(0x90666666); //grey
}
map.invalidate();
}
class RoadOnClickListener implements Polyline.OnClickListener{
@Override public boolean onClick(Polyline polyline, MapView mapView, GeoPoint eventPos){
int selectedRoad = (Integer)polyline.getRelatedObject();
selectRoad(selectedRoad);
polyline.showInfoWindow(eventPos);
return true;
}
}
void updateUIWithRoads(Road[] roads){
mRoadNodeMarkers.getItems().clear();
TextView textView = (TextView)findViewById(R.id.routeInfo);
textView.setText("");
List<Overlay> mapOverlays = map.getOverlays();
if (mRoadOverlays != null){
for (int i=0; i<mRoadOverlays.length; i++)
mapOverlays.remove(mRoadOverlays[i]);
mRoadOverlays = null;
}
if (roads == null)
return;
if (roads[0].mStatus == Road.STATUS_TECHNICAL_ISSUE)
Toast.makeText(map.getContext(), "Technical issue when getting the route", Toast.LENGTH_SHORT).show();
else if (roads[0].mStatus > Road.STATUS_TECHNICAL_ISSUE) //functional issues
Toast.makeText(map.getContext(), "No possible route here", Toast.LENGTH_SHORT).show();
mRoadOverlays = new Polyline[roads.length];
for (int i=0; i<roads.length; i++) {
Polyline roadPolyline = RoadManager.buildRoadOverlay(roads[i]);
mRoadOverlays[i] = roadPolyline;
if (mWhichRouteProvider == GRAPHHOPPER_BICYCLE || mWhichRouteProvider == GRAPHHOPPER_PEDESTRIAN) {
Paint p = roadPolyline.getPaint();
p.setPathEffect(new DashPathEffect(new float[]{10, 5}, 0));
}
String routeDesc = roads[i].getLengthDurationText(this, -1);
roadPolyline.setTitle(getString(R.string.route) + " - " + routeDesc);
roadPolyline.setInfoWindow(new BasicInfoWindow(org.osmdroid.bonuspack.R.layout.bonuspack_bubble, map));
roadPolyline.setRelatedObject(i);
roadPolyline.setOnClickListener(new RoadOnClickListener());
mapOverlays.add(1, roadPolyline);
//we insert the road overlays at the "bottom", just above the MapEventsOverlay,
//to avoid covering the other overlays.
}
selectRoad(0);
}
/**
* Async task to get the road in a separate thread.
*/
private class UpdateRoadTask extends AsyncTask<Object, Void, Road[]> {
private final Context mContext;
public UpdateRoadTask(Context context) {
this.mContext = context;
}
protected Road[] doInBackground(Object... params) {
@SuppressWarnings("unchecked")
ArrayList<GeoPoint> waypoints = (ArrayList<GeoPoint>)params[0];
RoadManager roadManager;
Locale locale = Locale.getDefault();
switch (mWhichRouteProvider){
case OSRM:
roadManager = new OSRMRoadManager(mContext);
break;
case GRAPHHOPPER_FASTEST:
roadManager = new GraphHopperRoadManager(graphHopperApiKey, false);
roadManager.addRequestOption("locale="+locale.getLanguage());
//roadManager = new MapQuestRoadManager(mapQuestApiKey);
//roadManager.addRequestOption("locale="+locale.getLanguage()+"_"+locale.getCountry());
break;
case GRAPHHOPPER_BICYCLE:
roadManager = new GraphHopperRoadManager(graphHopperApiKey, false);
roadManager.addRequestOption("locale="+locale.getLanguage());
roadManager.addRequestOption("vehicle=bike");
//((GraphHopperRoadManager)roadManager).setElevation(true);
break;
case GRAPHHOPPER_PEDESTRIAN:
roadManager = new GraphHopperRoadManager(graphHopperApiKey, false);
roadManager.addRequestOption("locale="+locale.getLanguage());
roadManager.addRequestOption("vehicle=foot");
//((GraphHopperRoadManager)roadManager).setElevation(true);
break;
case GOOGLE_FASTEST:
roadManager = new GoogleRoadManager();
break;
default:
return null;
}
return roadManager.getRoads(waypoints);
}
protected void onPostExecute(Road[] result) {
mRoads = result;
updateUIWithRoads(result);
getPOIAsync(poiTagText.getText().toString());
}
}
public void getRoadAsync(){
mRoads = null;
GeoPoint roadStartPoint = null;
if (startPoint != null){
roadStartPoint = startPoint;
} else if (myLocationOverlay.isEnabled() && myLocationOverlay.getLocation() != null){
//use my current location as itinerary start point:
roadStartPoint = myLocationOverlay.getLocation();
}
if (roadStartPoint == null || destinationPoint == null){
updateUIWithRoads(mRoads);
return;
}
ArrayList<GeoPoint> waypoints = new ArrayList<GeoPoint>(2);
waypoints.add(roadStartPoint);
//add intermediate via points:
for (GeoPoint p:viaPoints){
waypoints.add(p);
}
waypoints.add(destinationPoint);
new UpdateRoadTask(this).execute(waypoints);
}
//----------------- POIs
void updateUIWithPOI(ArrayList<POI> pois, String featureTag){
if (pois != null){
POIInfoWindow poiInfoWindow = new POIInfoWindow(map);
for (POI poi:pois){
Marker poiMarker = new Marker(map);
poiMarker.setTitle(poi.mType);
poiMarker.setSnippet(poi.mDescription);
poiMarker.setPosition(poi.mLocation);
Drawable icon = null;
if (poi.mServiceId == POI.POI_SERVICE_NOMINATIM || poi.mServiceId == POI.POI_SERVICE_OVERPASS_API){
icon = ResourcesCompat.getDrawable(getResources(), R.drawable.marker_poi, null);
poiMarker.setAnchor(Marker.ANCHOR_CENTER, 1.0f);
} else if (poi.mServiceId == POI.POI_SERVICE_GEONAMES_WIKIPEDIA){
if (poi.mRank < 90)
icon = ResourcesCompat.getDrawable(getResources(), R.drawable.marker_poi_wikipedia_16, null);
else
icon = ResourcesCompat.getDrawable(getResources(), R.drawable.marker_poi_wikipedia_32, null);
} else if (poi.mServiceId == POI.POI_SERVICE_FLICKR){
icon = ResourcesCompat.getDrawable(getResources(), R.drawable.marker_poi_flickr, null);
} else if (poi.mServiceId == POI.POI_SERVICE_PICASA){
icon = ResourcesCompat.getDrawable(getResources(), R.drawable.marker_poi_picasa_24, null);
poiMarker.setSubDescription(poi.mCategory);
}
poiMarker.setIcon(icon);
poiMarker.setRelatedObject(poi);
poiMarker.setInfoWindow(poiInfoWindow);
//thumbnail loading moved in async task for better performances.
mPoiMarkers.add(poiMarker);
}
}
mPoiMarkers.setName(featureTag);
mPoiMarkers.invalidate();
map.invalidate();
}
void setMarkerIconAsPhoto(Marker marker, Bitmap thumbnail){
int borderSize = 2;
thumbnail = Bitmap.createScaledBitmap(thumbnail, 48, 48, true);
Bitmap withBorder = Bitmap.createBitmap(thumbnail.getWidth() + borderSize * 2, thumbnail.getHeight() + borderSize * 2, thumbnail.getConfig());
Canvas canvas = new Canvas(withBorder);
canvas.drawColor(Color.WHITE);
canvas.drawBitmap(thumbnail, borderSize, borderSize, null);
BitmapDrawable icon = new BitmapDrawable(getResources(), withBorder);
marker.setIcon(icon);
}
ExecutorService mThreadPool = Executors.newFixedThreadPool(3);
class ThumbnailLoaderTask implements Runnable {
POI mPoi; Marker mMarker;
ThumbnailLoaderTask(POI poi, Marker marker){
mPoi = poi; mMarker = marker;
}
@Override public void run(){
Bitmap thumbnail = mPoi.getThumbnail();
if (thumbnail != null){
setMarkerIconAsPhoto(mMarker, thumbnail);
}
}
}
/** Loads all thumbnails in background */
void startAsyncThumbnailsLoading(ArrayList<POI> pois){
if (pois == null)
return;
//Try to stop existing threads:
mThreadPool.shutdownNow();
mThreadPool = Executors.newFixedThreadPool(3);
for (int i=0; i<pois.size(); i++){
final POI poi = pois.get(i);
final Marker marker = mPoiMarkers.getItem(i);
mThreadPool.submit(new ThumbnailLoaderTask(poi, marker));
}
}
/**
* Convert human readable feature to an OSM tag.
* @param humanReadableFeature
* @return OSM tag string: "k=v"
*/
String getOSMTag(String humanReadableFeature){
HashMap<String,String> map = BonusPackHelper.parseStringMapResource(getApplicationContext(), R.array.osm_poi_tags);
return map.get(humanReadableFeature.toLowerCase(Locale.getDefault()));
}
private class POILoadingTask extends AsyncTask<Object, Void, ArrayList<POI>> {
String mFeatureTag;
String message;
protected ArrayList<POI> doInBackground(Object... params) {
mFeatureTag = (String)params[0];
BoundingBox bb = map.getBoundingBox();
if (mFeatureTag == null || mFeatureTag.equals("")){
return null;
} else if (mFeatureTag.equals("wikipedia")){
GeoNamesPOIProvider poiProvider = new GeoNamesPOIProvider(geonamesAccount);
//Get POI inside the bounding box of the current map view:
ArrayList<POI> pois = poiProvider.getPOIInside(bb, 30);
return pois;
} else if (mFeatureTag.equals("flickr")){
FlickrPOIProvider poiProvider = new FlickrPOIProvider(flickrApiKey);
ArrayList<POI> pois = poiProvider.getPOIInside(bb, 30);
return pois;
} else if (mFeatureTag.startsWith("picasa")){
PicasaPOIProvider poiProvider = new PicasaPOIProvider(null);
//allow to search for keywords among picasa photos:
String q = mFeatureTag.substring("picasa".length());
ArrayList<POI> pois = poiProvider.getPOIInside(bb, 50, q);
return pois;
} else {
/*
NominatimPOIProvider poiProvider = new NominatimPOIProvider();
ArrayList<POI> pois;
if (mRoad == null){
pois = poiProvider.getPOIInside(map.getBoundingBox(), mFeatureTag, 100);
} else {
pois = poiProvider.getPOIAlong(mRoad.getRouteLow(), mFeatureTag, 100, 2.0);
}
*/
OverpassAPIProvider overpassProvider = new OverpassAPIProvider();
String osmTag = getOSMTag(mFeatureTag);
if (osmTag == null){
message = mFeatureTag + " is not a valid feature.";
return null;
}
String oUrl = overpassProvider.urlForPOISearch(osmTag, bb, 100, 10);
ArrayList<POI> pois = overpassProvider.getPOIsFromUrl(oUrl);
return pois;
}
}
protected void onPostExecute(ArrayList<POI> pois) {
mPOIs = pois;
if (mFeatureTag == null || mFeatureTag.equals("")){
//no search, no message
} else if (mPOIs == null){
if (message != null)
Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
else
Toast.makeText(getApplicationContext(), "Technical issue when getting "+mFeatureTag+ " POI.", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(getApplicationContext(), mFeatureTag+ " found:"+mPOIs.size(), Toast.LENGTH_LONG).show();
}
updateUIWithPOI(mPOIs, mFeatureTag);
if (mFeatureTag.equals("flickr")||mFeatureTag.startsWith("picasa")||mFeatureTag.equals("wikipedia"))
startAsyncThumbnailsLoading(mPOIs);
}
}
void getPOIAsync(String tag){
mPoiMarkers.getItems().clear();
new POILoadingTask().execute(tag);
}
//------------ KML handling
boolean mDialogForOpen;
void openLocalFileDialog(boolean open){
mDialogForOpen = open;
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.file_kml_open));
builder.setMessage("" + mKmlDocument.getDefaultPathForAndroid(""));
final EditText input = new EditText(this);
input.setInputType(InputType.TYPE_CLASS_TEXT);
String localFileName = getSharedPreferences("OSMNAVIGATOR", MODE_PRIVATE).getString("KML_LOCAL_FILE", "current.kml");
input.setText(localFileName);
builder.setView(input);
builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
@Override public void onClick(DialogInterface dialog, int which) {
String localFileName = input.getText().toString();
SharedPreferences prefs = getSharedPreferences("OSMNAVIGATOR", MODE_PRIVATE);
prefs.edit().putString("KML_LOCAL_FILE", localFileName).apply();
dialog.cancel();
if (mDialogForOpen){
File file = mKmlDocument.getDefaultPathForAndroid(localFileName);
openFile("file:/"+file.toString(), false, false);
} else
saveFile(localFileName);
}
});
builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
@Override public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
builder.show();
}
void openUrlDialog(){
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.url_kml_open));
final EditText input = new EditText(this);
input.setInputType(InputType.TYPE_CLASS_TEXT);
String defaultUri = "https://raw.githubusercontent.com/googlemaps/kml-samples/gh-pages/a/h.kml";
String uri = getSharedPreferences("OSMNAVIGATOR", MODE_PRIVATE).getString("KML_URI", defaultUri);
input.setText(uri);
builder.setView(input);
builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
@Override public void onClick(DialogInterface dialog, int which) {
String uri = input.getText().toString();
SharedPreferences prefs = getSharedPreferences("OSMNAVIGATOR", MODE_PRIVATE);
prefs.edit().putString("KML_URI", uri).apply();
dialog.cancel();
openFile(uri, false, false);
}
});
builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
@Override public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
builder.show();
}
void openOverpassAPIWizard(){
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Overpass API Wizard");
final EditText input = new EditText(this);
input.setInputType(InputType.TYPE_CLASS_TEXT);
String query = getSharedPreferences("OSMNAVIGATOR", MODE_PRIVATE).getString("OVERPASS_QUERY", "amenity=cinema");
input.setText(query);
builder.setView(input);
builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String query = input.getText().toString();
SharedPreferences prefs = getSharedPreferences("OSMNAVIGATOR", MODE_PRIVATE);
prefs.edit().putString("OVERPASS_QUERY", query).apply();
dialog.cancel();
openFile(query, false, true);
}
});
builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
builder.show();
}
boolean getKMLFromOverpass(String query){
OverpassAPIProvider overpassProvider = new OverpassAPIProvider();
String oUrl = overpassProvider.urlForTagSearchKml(query, map.getBoundingBox(), 500, 30);
return overpassProvider.addInKmlFolder(mKmlDocument.mKmlRoot, oUrl);
}
ProgressDialog createSpinningDialog(String title){
ProgressDialog pd = new ProgressDialog(map.getContext());
pd.setTitle(title);
pd.setMessage(getString(R.string.wait));
pd.setCancelable(false);
pd.setIndeterminate(true);
return pd;
}
class KmlLoadingTask extends AsyncTask<Object, Void, Boolean>{
String mUri;
boolean mOnCreate;
ProgressDialog mPD;
String mMessage;
KmlLoadingTask(String message){
super();
mMessage = message;
}
@Override protected void onPreExecute() {
mPD = createSpinningDialog(mMessage);
mPD.show();
}
@Override protected Boolean doInBackground(Object... params) {
mUri = (String)params[0];
mOnCreate = (Boolean)params[1];
boolean isOverpassRequest = (Boolean)params[2];
mKmlDocument = new KmlDocument();
boolean ok = false;
if (isOverpassRequest){
//mUri contains the query
ok = getKMLFromOverpass(mUri);
} else if (mUri.startsWith("file:/")){
mUri = mUri.substring("file:/".length());
File file = new File(mUri);
if (mUri.endsWith(".json"))
ok = mKmlDocument.parseGeoJSON(file);
else if (mUri.endsWith(".kmz"))
ok = mKmlDocument.parseKMZFile(file);
else //assume KML
ok = mKmlDocument.parseKMLFile(file);
} else if (mUri.startsWith("http")) {
ok = mKmlDocument.parseKMLUrl(mUri);
}
return ok;
}
@Override protected void onPostExecute(Boolean ok) {
if (mPD != null)
mPD.dismiss();
if (!ok)
Toast.makeText(getApplicationContext(), "Sorry, unable to read "+mUri, Toast.LENGTH_SHORT).show();
updateUIWithKml();
if (ok){
BoundingBox bb = mKmlDocument.mKmlRoot.getBoundingBox();
if (bb != null){
if (!mOnCreate)
setViewOn(bb);
else //KO in onCreate (osmdroid bug) - Workaround:
setInitialViewOn(bb);
}
}
}
}
void openFile(String uri, boolean onCreate, boolean isOverpassRequest){
//Toast.makeText(this, "Loading "+uri, Toast.LENGTH_SHORT).show();
new KmlLoadingTask(getString(R.string.loading)+" "+uri).execute(uri, onCreate, isOverpassRequest);
}
/** save fileName locally, as KML or GeoJSON depending on the extension */
void saveFile(String fileName){
boolean result;
File file = mKmlDocument.getDefaultPathForAndroid(fileName);
if (fileName.endsWith(".json"))
result = mKmlDocument.saveAsGeoJSON(file);
else
result = mKmlDocument.saveAsKML(file);
if (result)
Toast.makeText(this, fileName + " saved", Toast.LENGTH_SHORT).show();
else
Toast.makeText(this, "Unable to save "+fileName, Toast.LENGTH_SHORT).show();
}
Style buildDefaultStyle(){
Drawable defaultKmlMarker = ResourcesCompat.getDrawable(getResources(), R.drawable.marker_kml_point, null);
Bitmap bitmap = ((BitmapDrawable)defaultKmlMarker).getBitmap();
return new Style(bitmap, 0x901010AA, 3.0f, 0x20AA1010);
}
void updateUIWithKml(){
if (mKmlOverlay != null){
mKmlOverlay.closeAllInfoWindows();
map.getOverlays().remove(mKmlOverlay);
}
mKmlOverlay = (FolderOverlay)mKmlDocument.mKmlRoot.buildOverlay(map, buildDefaultStyle(), null, mKmlDocument);
map.getOverlays().add(mKmlOverlay);
map.invalidate();
}
void insertOverlaysInKml(){
KmlFolder root = mKmlDocument.mKmlRoot;
//Insert relevant overlays inside:
if (mItineraryMarkers.getItems().size()>0)
root.addOverlay(mItineraryMarkers, mKmlDocument);
if (mRoadOverlays != null){
for (int i=0; i<mRoadOverlays.length; i++)
root.addOverlay(mRoadOverlays[i], mKmlDocument);
}
if (mRoadNodeMarkers.getItems().size()>0)
root.addOverlay(mRoadNodeMarkers, mKmlDocument);
root.addOverlay(mDestinationPolygon, mKmlDocument);
if (mPoiMarkers.getItems().size()>0){
root.addOverlay(mPoiMarkers, mKmlDocument);
}
}
//Async task to reverse-geocode the KML point in a separate thread:
private class KMLGeocodingTask extends AsyncTask<Object, Void, String> {
KmlPlacemark kmlPoint;
protected String doInBackground(Object... params) {
kmlPoint = (KmlPlacemark)params[0];
return getAddress(((KmlPoint) kmlPoint.mGeometry).getPosition());
}
protected void onPostExecute(String result) {
kmlPoint.mName = result;
updateUIWithKml();
// marker.showInfoWindow();
}
}
void addKmlPoint(GeoPoint position){
KmlFeature kmlPoint = new KmlPlacemark(position);
mKmlDocument.mKmlRoot.add(kmlPoint);
new KMLGeocodingTask().execute(kmlPoint);
updateUIWithKml();
}
//------------ MapEventsReceiver implementation
GeoPoint mClickedGeoPoint; //any other way to pass the position to the menu ???
@Override public boolean longPressHelper(GeoPoint p) {
mClickedGeoPoint = p;
Button searchButton = (Button)findViewById(R.id.buttonSearchDest);
openContextMenu(searchButton);
//menu is hooked on the "Search Destination" button, as it must be hooked somewhere.
return true;
}
@Override public boolean singleTapConfirmedHelper(GeoPoint p) {
InfoWindow.closeAllInfoWindowsOn(map);
return true;
}
//----------- Context Menu when clicking on the map
@Override public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.map_menu, menu);
}
@Override public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_departure:
startPoint = new GeoPoint(mClickedGeoPoint);
markerStart = updateItineraryMarker(markerStart, startPoint, START_INDEX,
R.string.departure, R.drawable.marker_departure, -1, null);
getRoadAsync();
return true;
case R.id.menu_destination:
destinationPoint = new GeoPoint(mClickedGeoPoint);
markerDestination = updateItineraryMarker(markerDestination, destinationPoint, DEST_INDEX,
R.string.destination, R.drawable.marker_destination, -1, null);
getRoadAsync();
return true;
case R.id.menu_viapoint:
GeoPoint viaPoint = new GeoPoint(mClickedGeoPoint);
addViaPoint(viaPoint);
getRoadAsync();
return true;
case R.id.menu_kmlpoint:
GeoPoint kmlPoint = new GeoPoint(mClickedGeoPoint);
addKmlPoint(kmlPoint);
return true;
default:
return super.onContextItemSelected(item);
}
}
//------------ Option Menu implementation
@Override public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.option_menu, menu);
switch (mWhichRouteProvider){
case OSRM:
menu.findItem(R.id.menu_route_osrm).setChecked(true);
break;
case GRAPHHOPPER_FASTEST:
menu.findItem(R.id.menu_route_graphhopper_fastest).setChecked(true);
break;
case GRAPHHOPPER_BICYCLE:
menu.findItem(R.id.menu_route_graphhopper_bicycle).setChecked(true);
break;
case GRAPHHOPPER_PEDESTRIAN:
menu.findItem(R.id.menu_route_graphhopper_pedestrian).setChecked(true);
break;
case GOOGLE_FASTEST:
menu.findItem(R.id.menu_route_google).setChecked(true);
break;
}
if (map.getTileProvider().getTileSource() == TileSourceFactory.MAPNIK) {
if (!mNightMode)
menu.findItem(R.id.menu_tile_mapnik).setChecked(true);
else
menu.findItem(R.id.menu_tile_mapnik_by_night).setChecked(true);
}
else if (map.getTileProvider().getTileSource() == MAPBOXSATELLITELABELLED)
menu.findItem(R.id.menu_tile_mapbox_satellite).setChecked(true);
return true;
}
@Override public boolean onPrepareOptionsMenu(Menu menu) {
mFriendsManager.onPrepareOptionsMenu(menu);
if (mRoads != null && mRoads[mSelectedRoad].mNodes.size()>0)
menu.findItem(R.id.menu_itinerary).setEnabled(true);
else
menu.findItem(R.id.menu_itinerary).setEnabled(false);
if (mPOIs != null && mPOIs.size()>0)
menu.findItem(R.id.menu_pois).setEnabled(true);
else
menu.findItem(R.id.menu_pois).setEnabled(false);
return true;
}
/** return the index of the first Marker having its bubble opened, -1 if none */
static int getIndexOfBubbledMarker(List<? extends Overlay> list) {
for (int i=0; i<list.size(); i++){
Overlay item = list.get(i);
if (item instanceof Marker){
Marker marker = (Marker)item;
if (marker.isInfoWindowShown())
return i;
}
}
return -1;
}
void setStdTileProvider(){
if (!(map.getTileProvider() instanceof MapTileProviderBasic)){
MapTileProviderBasic bitmapProvider = new MapTileProviderBasic(this);
map.setTileProvider(bitmapProvider);
}
}
boolean setMapsForgeTileProvider(){
String path = Environment.getExternalStorageDirectory().getPath()+"/mapsforge/";
Toast.makeText(this, "Loading MapsForge .map files and rendering theme from " + path, Toast.LENGTH_LONG).show();
File folder = new File(path);
File[] listOfFiles = folder.listFiles();
if (listOfFiles == null)
return false;
//Build a list with only .map files; get rendering config file if any:
File renderingFile = null;
ArrayList<File> listOfMapFiles = new ArrayList<>(listOfFiles.length);
for (File file:listOfFiles){
if (file.isFile() && file.getName().endsWith(".map")){
listOfMapFiles.add(file);
} else if (file.isFile() && file.getName().endsWith(".xml")) {
renderingFile = file;
}
}
listOfFiles = new File[listOfMapFiles.size()];
listOfFiles = listOfMapFiles.toArray(listOfFiles);
//Use rendering file if any
XmlRenderTheme theme = null;
try {
if (renderingFile != null)
theme = new ExternalRenderTheme(renderingFile);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
if (AndroidGraphicFactory.INSTANCE == null)
AndroidGraphicFactory.createInstance(this.getApplication());
MapsForgeTileSource source = MapsForgeTileSource.createFromFiles(listOfFiles, theme, "rendertheme-v4");
MapsForgeTileProvider mfProvider = new MapsForgeTileProvider(new SimpleRegisterReceiver(this), source);
map.setTileProvider(mfProvider);
return true;
}
@Override public boolean onOptionsItemSelected(MenuItem item) {
Intent myIntent;
switch (item.getItemId()) {
case R.id.menu_sharing:
return mFriendsManager.onOptionsItemSelected(item);
case R.id.menu_itinerary:
myIntent = new Intent(this, RouteActivity.class);
int currentNodeId = getIndexOfBubbledMarker(mRoadNodeMarkers.getItems());
myIntent.putExtra("SELECTED_ROAD", mSelectedRoad);
myIntent.putExtra("NODE_ID", currentNodeId);
startActivityForResult(myIntent, ROUTE_REQUEST);
return true;
case R.id.menu_pois:
myIntent = new Intent(this, POIActivity.class);
myIntent.putExtra("ID", getIndexOfBubbledMarker(mPoiMarkers.getItems()));
startActivityForResult(myIntent, POIS_REQUEST);
return true;
case R.id.menu_kml_url:
openUrlDialog();
return true;
case R.id.menu_open_file:
openLocalFileDialog(true);
return true;
case R.id.menu_overpass_api:
openOverpassAPIWizard();
return true;
case R.id.menu_kml_record_track:
mIsRecordingTrack = !mIsRecordingTrack;
mFriendsManager.setTracksRecording(mIsRecordingTrack);
if (mIsRecordingTrack)
item.setTitle(R.string.menu_kml_stop_record_tracks);
else
item.setTitle(R.string.menu_kml_record_tracks);
return true;
case R.id.menu_kml_get_overlays:
insertOverlaysInKml();
updateUIWithKml();
return true;
case R.id.menu_kml_tree:
myIntent = new Intent(this, KmlTreeActivity.class);
//myIntent.putExtra("KML", mKmlDocument.kmlRoot);
mKmlStack.push(mKmlDocument.mKmlRoot);
startActivityForResult(myIntent, KmlTreeActivity.KML_TREE_REQUEST);
return true;
case R.id.menu_kml_styles:
myIntent = new Intent(this, KmlStylesActivity.class);
startActivityForResult(myIntent, KmlStylesActivity.KML_STYLES_REQUEST);
return true;
case R.id.menu_save_file:
openLocalFileDialog(false);
return true;
case R.id.menu_kml_clear:
mKmlDocument = new KmlDocument();
updateUIWithKml();
return true;
case R.id.menu_route_osrm:
mWhichRouteProvider = OSRM;
item.setChecked(true);
getRoadAsync();
return true;
case R.id.menu_route_graphhopper_fastest:
mWhichRouteProvider = GRAPHHOPPER_FASTEST;
item.setChecked(true);
getRoadAsync();
return true;
case R.id.menu_route_graphhopper_bicycle:
mWhichRouteProvider = GRAPHHOPPER_BICYCLE;
item.setChecked(true);
getRoadAsync();
return true;
case R.id.menu_route_graphhopper_pedestrian:
mWhichRouteProvider = GRAPHHOPPER_PEDESTRIAN;
item.setChecked(true);
getRoadAsync();
return true;
case R.id.menu_route_google:
mWhichRouteProvider = GOOGLE_FASTEST;
item.setChecked(true);
getRoadAsync();
return true;
case R.id.menu_tile_mapnik:
setStdTileProvider();
map.setTileSource(TileSourceFactory.MAPNIK);
map.getOverlayManager().getTilesOverlay().setColorFilter(null);
mNightMode = false;
item.setChecked(true);
return true;
case R.id.menu_tile_mapnik_by_night:
setStdTileProvider();
map.setTileSource(TileSourceFactory.MAPNIK);
map.getOverlayManager().getTilesOverlay().setColorFilter(TilesOverlay.INVERT_COLORS);
mNightMode = true;
item.setChecked(true);
return true;
case R.id.menu_tile_mapbox_satellite:
setStdTileProvider();
map.setTileSource(MAPBOXSATELLITELABELLED);
map.getOverlayManager().getTilesOverlay().setColorFilter(null);
item.setChecked(true);
return true;
case R.id.menu_tile_mapsforge:
boolean result = setMapsForgeTileProvider();
if (result)
item.setChecked(true);
else
Toast.makeText(this, "No MapsForge map found", Toast.LENGTH_SHORT).show();
return true;
case R.id.menu_download_view_area:{
CacheManager cacheManager = new CacheManager(map);
int zoomMin = map.getZoomLevel();
int zoomMax = map.getZoomLevel()+4;
cacheManager.downloadAreaAsync(this, map.getBoundingBox(), zoomMin, zoomMax);
return true;
}
case R.id.menu_clear_view_area:{
CacheManager cacheManager = new CacheManager(map);
int zoomMin = map.getZoomLevel();
int zoomMax = map.getZoomLevel()+7;
cacheManager.cleanAreaAsync(this, map.getBoundingBox(), zoomMin, zoomMax);
return true;
}
case R.id.menu_cache_usage:{
CacheManager cacheManager = new CacheManager(map);
long cacheUsage = cacheManager.currentCacheUsage()/(1024*1024);
long cacheCapacity = cacheManager.cacheCapacity()/(1024*1024);
float percent = 100.0f*cacheUsage/cacheCapacity;
String message = "Cache usage:\n"+cacheUsage+" Mo / "+cacheCapacity+" Mo = "+(int)percent + "%";
Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
return true;
}
default:
return super.onOptionsItemSelected(item);
}
}
//------------ LocationListener implementation
private final NetworkLocationIgnorer mIgnorer = new NetworkLocationIgnorer();
long mLastTime = 0; // milliseconds
double mSpeed = 0.0; // km/h
@Override public void onLocationChanged(final Location pLoc) {
long currentTime = System.currentTimeMillis();
if (mIgnorer.shouldIgnore(pLoc.getProvider(), currentTime))
return;
double dT = currentTime - mLastTime;
if (dT < 100.0){
//Toast.makeText(this, pLoc.getProvider()+" dT="+dT, Toast.LENGTH_SHORT).show();
return;
}
mLastTime = currentTime;
GeoPoint newLocation = new GeoPoint(pLoc);
if (!myLocationOverlay.isEnabled()){
//we get the location for the first time:
myLocationOverlay.setEnabled(true);
map.getController().animateTo(newLocation);
}
GeoPoint prevLocation = myLocationOverlay.getLocation();
myLocationOverlay.setLocation(newLocation);
myLocationOverlay.setAccuracy((int)pLoc.getAccuracy());
if (prevLocation != null && pLoc.getProvider().equals(LocationManager.GPS_PROVIDER)){
mSpeed = pLoc.getSpeed() * 3.6;
long speedInt = Math.round(mSpeed);
TextView speedTxt = (TextView)findViewById(R.id.speed);
speedTxt.setText(speedInt + " km/h");
//TODO: check if speed is not too small
if (mSpeed >= 0.1){
mAzimuthAngleSpeed = pLoc.getBearing();
myLocationOverlay.setBearing(mAzimuthAngleSpeed);
}
}
if (mTrackingMode){
//keep the map view centered on current location:
map.getController().animateTo(newLocation);
map.setMapOrientation(-mAzimuthAngleSpeed);
} else {
//just redraw the location overlay:
map.invalidate();
}
if (mIsRecordingTrack) {
recordCurrentLocationInTrack("my_track", "My Track", newLocation);
}
}
static int[] TrackColor = {
Color.CYAN-0x20000000, Color.BLUE-0x20000000, Color.MAGENTA-0x20000000, Color.RED-0x20000000, Color.YELLOW-0x20000000
};
KmlTrack createTrack(String id, String name) {
KmlTrack t = new KmlTrack();
KmlPlacemark p = new KmlPlacemark();
p.mId = id;
p.mName = name;
p.mGeometry = t;
mKmlDocument.mKmlRoot.add(p);
//set a color to this track by creating a style:
Style s = new Style();
int color;
try {
color = Integer.parseInt(id);
color = color % TrackColor.length;
color = TrackColor[color];
} catch (NumberFormatException e) {
color = Color.GREEN-0x20000000;
}
s.mLineStyle = new LineStyle(color, 8.0f);
String styleId = mKmlDocument.addStyle(s);
p.mStyle = styleId;
return t;
}
void recordCurrentLocationInTrack(String trackId, String trackName, GeoPoint currentLocation) {
//Find the KML track in the current KML structure - and create it if necessary:
KmlTrack t;
KmlFeature f = mKmlDocument.mKmlRoot.findFeatureId(trackId, false);
if (f == null)
t = createTrack(trackId, trackName);
else if (!(f instanceof KmlPlacemark))
//id already defined but is not a PlaceMark
return;
else {
KmlPlacemark p = (KmlPlacemark)f;
if (!(p.mGeometry instanceof KmlTrack))
//id already defined but is not a Track
return;
else
t = (KmlTrack) p.mGeometry;
}
//TODO check if current location is really different from last point of the track
//record in the track the current location at current time:
t.add(currentLocation, new Date());
//refresh KML:
updateUIWithKml();
}
@Override public void onProviderDisabled(String provider) {}
@Override public void onProviderEnabled(String provider) {}
@Override public void onStatusChanged(String provider, int status, Bundle extras) {}
//------------ SensorEventListener implementation
@Override public void onAccuracyChanged(Sensor sensor, int accuracy) {
myLocationOverlay.setAccuracy(accuracy);
map.invalidate();
}
//static float mAzimuthOrientation = 0.0f;
@Override public void onSensorChanged(SensorEvent event) {
switch (event.sensor.getType()){
case Sensor.TYPE_ORIENTATION:
if (mSpeed < 0.1){
/* TODO Filter to implement...
float azimuth = event.values[0];
if (Math.abs(azimuth-mAzimuthOrientation)>2.0f){
mAzimuthOrientation = azimuth;
myLocationOverlay.setBearing(mAzimuthOrientation);
if (mTrackingMode)
map.setMapOrientation(-mAzimuthOrientation);
else
map.invalidate();
}
*/
}
//at higher speed, we use speed vector, not phone orientation.
break;
default:
break;
}
}
}