/*
* Copyright 2010, 2011, 2012 mapsforge.org
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mapsforge.applications.android.advancedmapviewer;
import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.text.DateFormat;
import java.util.Date;
import org.mapsforge.android.AndroidUtils;
import org.mapsforge.android.maps.DebugSettings;
import org.mapsforge.android.maps.MapActivity;
import org.mapsforge.android.maps.MapScaleBar;
import org.mapsforge.android.maps.MapScaleBar.TextField;
import org.mapsforge.android.maps.MapView;
import org.mapsforge.android.maps.MapViewPosition;
import org.mapsforge.android.maps.mapgenerator.TileCache;
import org.mapsforge.android.maps.overlay.Marker;
import org.mapsforge.android.maps.overlay.MyLocationOverlay;
import org.mapsforge.applications.android.advancedmapviewer.filefilter.FilterByFileExtension;
import org.mapsforge.applications.android.advancedmapviewer.filefilter.ValidMapFile;
import org.mapsforge.applications.android.advancedmapviewer.filefilter.ValidRenderTheme;
import org.mapsforge.applications.android.advancedmapviewer.filepicker.FilePicker;
import org.mapsforge.applications.android.advancedmapviewer.preferences.EditPreferences;
import org.mapsforge.core.model.BoundingBox;
import org.mapsforge.core.model.GeoPoint;
import org.mapsforge.core.model.MapPosition;
import org.mapsforge.map.reader.header.MapFileInfo;
import org.mapsforge.map.rendertheme.InternalRenderTheme;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.drawable.Drawable;
import android.location.Location;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.preference.PreferenceManager;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ToggleButton;
/**
* A map application which uses the features from the mapsforge map library. The map can be centered to the current
* location. A simple file browser for selecting the map file is also included. Some preferences can be adjusted via the
* {@link EditPreferences} activity and screenshots of the map may be taken in different image formats.
*/
public class AdvancedMapViewer extends MapActivity {
/**
* The default number of tiles in the file system cache.
*/
public static final int FILE_SYSTEM_CACHE_SIZE_DEFAULT = 250;
/**
* The maximum number of tiles in the file system cache.
*/
public static final int FILE_SYSTEM_CACHE_SIZE_MAX = 500;
/**
* The default move speed factor of the map.
*/
public static final int MOVE_SPEED_DEFAULT = 10;
/**
* The maximum move speed factor of the map.
*/
public static final int MOVE_SPEED_MAX = 30;
private static final String BUNDLE_CENTER_AT_FIRST_FIX = "centerAtFirstFix";
private static final String BUNDLE_SHOW_MY_LOCATION = "showMyLocation";
private static final String BUNDLE_SNAP_TO_LOCATION = "snapToLocation";
private static final int DIALOG_ENTER_COORDINATES = 0;
private static final int DIALOG_INFO_MAP_FILE = 1;
private static final int DIALOG_LOCATION_PROVIDER_DISABLED = 2;
private static final FileFilter FILE_FILTER_EXTENSION_MAP = new FilterByFileExtension(".map");
private static final FileFilter FILE_FILTER_EXTENSION_XML = new FilterByFileExtension(".xml");
private static final int SELECT_MAP_FILE = 0;
private static final int SELECT_RENDER_THEME_FILE = 1;
private MyLocationOverlay myLocationOverlay;
private ScreenshotCapturer screenshotCapturer;
private ToggleButton snapToLocationView;
private WakeLock wakeLock;
MapView mapView;
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.options_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_info:
return true;
case R.id.menu_info_map_file:
showDialog(DIALOG_INFO_MAP_FILE);
return true;
case R.id.menu_info_about:
startActivity(new Intent(this, InfoView.class));
return true;
case R.id.menu_position:
return true;
case R.id.menu_position_my_location_enable:
enableShowMyLocation(true);
return true;
case R.id.menu_position_my_location_disable:
disableShowMyLocation();
return true;
case R.id.menu_position_last_known:
gotoLastKnownPosition();
return true;
case R.id.menu_position_enter_coordinates:
showDialog(DIALOG_ENTER_COORDINATES);
return true;
case R.id.menu_position_map_center:
// disable GPS follow mode if it is enabled
disableSnapToLocation(true);
MapFileInfo mapFileInfo = this.mapView.getMapDatabase().getMapFileInfo();
this.mapView.getMapViewPosition().setCenter(mapFileInfo.boundingBox.getCenterPoint());
return true;
case R.id.menu_screenshot:
return true;
case R.id.menu_screenshot_jpeg:
this.screenshotCapturer.captureScreenshot(CompressFormat.JPEG);
return true;
case R.id.menu_screenshot_png:
this.screenshotCapturer.captureScreenshot(CompressFormat.PNG);
return true;
case R.id.menu_preferences:
startActivity(new Intent(this, EditPreferences.class));
return true;
case R.id.menu_render_theme:
return true;
case R.id.menu_render_theme_osmarender:
this.mapView.setRenderTheme(InternalRenderTheme.OSMARENDER);
return true;
case R.id.menu_render_theme_select_file:
startRenderThemePicker();
return true;
case R.id.menu_mapfile:
startMapFilePicker();
return true;
default:
return false;
}
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (this.myLocationOverlay.isMyLocationEnabled()) {
menu.findItem(R.id.menu_position_my_location_enable).setVisible(false);
menu.findItem(R.id.menu_position_my_location_enable).setEnabled(false);
menu.findItem(R.id.menu_position_my_location_disable).setVisible(true);
menu.findItem(R.id.menu_position_my_location_disable).setEnabled(true);
} else {
menu.findItem(R.id.menu_position_my_location_enable).setVisible(true);
menu.findItem(R.id.menu_position_my_location_enable).setEnabled(true);
menu.findItem(R.id.menu_position_my_location_disable).setVisible(false);
menu.findItem(R.id.menu_position_my_location_disable).setEnabled(false);
}
return true;
}
@Override
public boolean onTrackballEvent(MotionEvent event) {
// forward the event to the MapView
return this.mapView.onTrackballEvent(event);
}
private void configureMapView() {
this.mapView.setBuiltInZoomControls(true);
this.mapView.setClickable(true);
this.mapView.setFocusable(true);
MapScaleBar mapScaleBar = this.mapView.getMapScaleBar();
mapScaleBar.setText(TextField.KILOMETER, getString(R.string.unit_symbol_kilometer));
mapScaleBar.setText(TextField.METER, getString(R.string.unit_symbol_meter));
}
private void disableShowMyLocation() {
if (this.myLocationOverlay.isMyLocationEnabled()) {
this.myLocationOverlay.disableMyLocation();
}
}
/**
* Enables the "show my location" mode.
*
* @param centerAtFirstFix
* defines whether the map should be centered to the first fix.
*/
private void enableShowMyLocation(boolean centerAtFirstFix) {
if (!this.myLocationOverlay.isMyLocationEnabled()) {
if (!this.myLocationOverlay.enableMyLocation(centerAtFirstFix)) {
showDialog(DIALOG_LOCATION_PROVIDER_DISABLED);
return;
}
this.mapView.getOverlays().add(this.myLocationOverlay);
this.snapToLocationView.setVisibility(View.VISIBLE);
}
}
/**
* Centers the map to the last known position as reported by the most accurate location provider. If the last
* location is unknown, a toast message is displayed instead.
*/
private void gotoLastKnownPosition() {
Location bestLocation = null;
LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
for (String provider : locationManager.getProviders(true)) {
Location currentLocation = locationManager.getLastKnownLocation(provider);
if (bestLocation == null || bestLocation.getAccuracy() > currentLocation.getAccuracy()) {
bestLocation = currentLocation;
}
}
// check if a location has been found
if (bestLocation != null) {
GeoPoint geoPoint = new GeoPoint(bestLocation.getLatitude(), bestLocation.getLongitude());
this.mapView.getMapViewPosition().setCenter(geoPoint);
} else {
showToastOnUiThread(getString(R.string.error_last_location_unknown));
}
}
/**
* Sets all file filters and starts the FilePicker to select a map file.
*/
private void startMapFilePicker() {
FilePicker.setFileDisplayFilter(FILE_FILTER_EXTENSION_MAP);
FilePicker.setFileSelectFilter(new ValidMapFile());
startActivityForResult(new Intent(this, FilePicker.class), SELECT_MAP_FILE);
}
/**
* Sets all file filters and starts the FilePicker to select an XML file.
*/
private void startRenderThemePicker() {
FilePicker.setFileDisplayFilter(FILE_FILTER_EXTENSION_XML);
FilePicker.setFileSelectFilter(new ValidRenderTheme());
startActivityForResult(new Intent(this, FilePicker.class), SELECT_RENDER_THEME_FILE);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
if (requestCode == SELECT_MAP_FILE) {
if (resultCode == RESULT_OK) {
disableSnapToLocation(true);
if (intent != null && intent.getStringExtra(FilePicker.SELECTED_FILE) != null) {
this.mapView.setMapFile(new File(intent.getStringExtra(FilePicker.SELECTED_FILE)));
}
} else if (resultCode == RESULT_CANCELED && this.mapView.getMapFile() == null) {
finish();
}
} else if (requestCode == SELECT_RENDER_THEME_FILE && resultCode == RESULT_OK && intent != null
&& intent.getStringExtra(FilePicker.SELECTED_FILE) != null) {
try {
this.mapView.setRenderTheme(new File(intent.getStringExtra(FilePicker.SELECTED_FILE)));
} catch (FileNotFoundException e) {
showToastOnUiThread(e.getLocalizedMessage());
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.screenshotCapturer = new ScreenshotCapturer(this);
this.screenshotCapturer.start();
setContentView(R.layout.activity_advanced_map_viewer);
this.mapView = (MapView) findViewById(R.id.mapView);
configureMapView();
this.snapToLocationView = (ToggleButton) findViewById(R.id.snapToLocationView);
this.snapToLocationView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
invertSnapToLocation();
}
});
Drawable drawable = getResources().getDrawable(R.drawable.ic_maps_indicator_current_position_anim1);
drawable = Marker.boundCenter(drawable);
this.myLocationOverlay = new MyLocationOverlay(this, this.mapView, drawable);
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
this.wakeLock = powerManager.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "AMV");
if (savedInstanceState != null && savedInstanceState.getBoolean(BUNDLE_SHOW_MY_LOCATION)) {
enableShowMyLocation(savedInstanceState.getBoolean(BUNDLE_CENTER_AT_FIRST_FIX));
if (savedInstanceState.getBoolean(BUNDLE_SNAP_TO_LOCATION)) {
enableSnapToLocation(false);
}
}
}
@Deprecated
@Override
protected Dialog onCreateDialog(int id) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
if (id == DIALOG_ENTER_COORDINATES) {
builder.setIcon(android.R.drawable.ic_menu_mylocation);
builder.setTitle(R.string.menu_position_enter_coordinates);
LayoutInflater factory = LayoutInflater.from(this);
final View view = factory.inflate(R.layout.dialog_enter_coordinates, null);
builder.setView(view);
builder.setPositiveButton(R.string.go_to_position, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// disable GPS follow mode if it is enabled
disableSnapToLocation(true);
// set the map center and zoom level
EditText latitudeView = (EditText) view.findViewById(R.id.latitude);
EditText longitudeView = (EditText) view.findViewById(R.id.longitude);
double latitude = Double.parseDouble(latitudeView.getText().toString());
double longitude = Double.parseDouble(longitudeView.getText().toString());
GeoPoint geoPoint = new GeoPoint(latitude, longitude);
SeekBar zoomLevelView = (SeekBar) view.findViewById(R.id.zoomLevel);
MapPosition newMapPosition = new MapPosition(geoPoint, (byte) zoomLevelView.getProgress());
AdvancedMapViewer.this.mapView.getMapViewPosition().setMapPosition(newMapPosition);
}
});
builder.setNegativeButton(R.string.cancel, null);
return builder.create();
} else if (id == DIALOG_LOCATION_PROVIDER_DISABLED) {
builder.setIcon(android.R.drawable.ic_menu_info_details);
builder.setTitle(R.string.error);
builder.setMessage(R.string.no_location_provider_available);
builder.setPositiveButton(R.string.ok, null);
return builder.create();
} else if (id == DIALOG_INFO_MAP_FILE) {
builder.setIcon(android.R.drawable.ic_menu_info_details);
builder.setTitle(R.string.menu_info_map_file);
LayoutInflater factory = LayoutInflater.from(this);
builder.setView(factory.inflate(R.layout.dialog_info_map_file, null));
builder.setPositiveButton(R.string.ok, null);
return builder.create();
} else {
// do dialog will be created
return null;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
this.screenshotCapturer.interrupt();
disableShowMyLocation();
}
@Override
protected void onPause() {
super.onPause();
if (this.wakeLock.isHeld()) {
this.wakeLock.release();
}
}
@Deprecated
@Override
protected void onPrepareDialog(int id, final Dialog dialog) {
if (id == DIALOG_ENTER_COORDINATES) {
// latitude
EditText editText = (EditText) dialog.findViewById(R.id.latitude);
MapViewPosition mapViewPosition = this.mapView.getMapViewPosition();
GeoPoint mapCenter = mapViewPosition.getCenter();
editText.setText(Double.toString(mapCenter.latitude));
// longitude
editText = (EditText) dialog.findViewById(R.id.longitude);
editText.setText(Double.toString(mapCenter.longitude));
// zoom level
SeekBar zoomlevel = (SeekBar) dialog.findViewById(R.id.zoomLevel);
zoomlevel.setMax(this.mapView.getDatabaseRenderer().getZoomLevelMax());
zoomlevel.setProgress(mapViewPosition.getZoomLevel());
// zoom level value
final TextView textView = (TextView) dialog.findViewById(R.id.zoomlevelValue);
textView.setText(String.valueOf(zoomlevel.getProgress()));
zoomlevel.setOnSeekBarChangeListener(new SeekBarChangeListener(textView));
} else if (id == DIALOG_INFO_MAP_FILE) {
MapFileInfo mapFileInfo = this.mapView.getMapDatabase().getMapFileInfo();
// map file name
TextView textView = (TextView) dialog.findViewById(R.id.infoMapFileViewName);
textView.setText(this.mapView.getMapFile().getAbsolutePath());
// map file size
textView = (TextView) dialog.findViewById(R.id.infoMapFileViewSize);
textView.setText(FileUtils.formatFileSize(mapFileInfo.fileSize, getResources()));
// map file version
textView = (TextView) dialog.findViewById(R.id.infoMapFileViewVersion);
textView.setText(String.valueOf(mapFileInfo.fileVersion));
// map file debug
textView = (TextView) dialog.findViewById(R.id.infoMapFileViewDebug);
if (mapFileInfo.debugFile) {
textView.setText(R.string.info_map_file_debug_yes);
} else {
textView.setText(R.string.info_map_file_debug_no);
}
// map file date
textView = (TextView) dialog.findViewById(R.id.infoMapFileViewDate);
Date date = new Date(mapFileInfo.mapDate);
textView.setText(DateFormat.getDateTimeInstance().format(date));
// map file area
textView = (TextView) dialog.findViewById(R.id.infoMapFileViewArea);
BoundingBox boundingBox = mapFileInfo.boundingBox;
textView.setText(boundingBox.minLatitude + ", " + boundingBox.minLongitude + " – \n"
+ boundingBox.maxLatitude + ", " + boundingBox.maxLongitude);
// map file start position
textView = (TextView) dialog.findViewById(R.id.infoMapFileViewStartPosition);
GeoPoint startPosition = mapFileInfo.startPosition;
if (startPosition == null) {
textView.setText(null);
} else {
textView.setText(startPosition.latitude + ", " + startPosition.longitude);
}
// map file start zoom level
textView = (TextView) dialog.findViewById(R.id.infoMapFileViewStartZoomLevel);
Byte startZoomLevel = mapFileInfo.startZoomLevel;
if (startZoomLevel == null) {
textView.setText(null);
} else {
textView.setText(startZoomLevel.toString());
}
// map file language preference
textView = (TextView) dialog.findViewById(R.id.infoMapFileViewLanguagePreference);
textView.setText(mapFileInfo.languagePreference);
// map file comment text
textView = (TextView) dialog.findViewById(R.id.infoMapFileViewComment);
textView.setText(mapFileInfo.comment);
// map file created by text
textView = (TextView) dialog.findViewById(R.id.infoMapFileViewCreatedBy);
textView.setText(mapFileInfo.createdBy);
} else {
super.onPrepareDialog(id, dialog);
}
}
@Override
protected void onResume() {
super.onResume();
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
MapScaleBar mapScaleBar = this.mapView.getMapScaleBar();
mapScaleBar.setShowMapScaleBar(sharedPreferences.getBoolean("showScaleBar", false));
String scaleBarUnitDefault = getString(R.string.preferences_scale_bar_unit_default);
String scaleBarUnit = sharedPreferences.getString("scaleBarUnit", scaleBarUnitDefault);
mapScaleBar.setImperialUnits(scaleBarUnit.equals("imperial"));
try {
String textScaleDefault = getString(R.string.preferences_text_scale_default);
this.mapView.setTextScale(Float.parseFloat(sharedPreferences.getString("textScale", textScaleDefault)));
} catch (NumberFormatException e) {
this.mapView.setTextScale(1);
}
if (sharedPreferences.getBoolean("fullscreen", false)) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
} else {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
}
if (sharedPreferences.getBoolean("wakeLock", false) && !this.wakeLock.isHeld()) {
this.wakeLock.acquire();
}
// retrieve caching settings
boolean persistent = sharedPreferences.getBoolean("cachePersistence", true);
int capacity = Math.min(sharedPreferences.getInt("cacheSize", FILE_SYSTEM_CACHE_SIZE_DEFAULT),
FILE_SYSTEM_CACHE_SIZE_MAX);
TileCache fileSystemTileCache = this.mapView.getFileSystemTileCache();
fileSystemTileCache.setPersistent(persistent);
fileSystemTileCache.setCapacity(capacity);
float moveSpeedFactor = Math.min(sharedPreferences.getInt("mapMoveSpeed", MOVE_SPEED_DEFAULT), MOVE_SPEED_MAX) / 10f;
this.mapView.getMapMover().setMoveSpeedFactor(moveSpeedFactor);
this.mapView.getFpsCounter().setFpsCounter(sharedPreferences.getBoolean("showFpsCounter", false));
boolean drawTileFrames = sharedPreferences.getBoolean("drawTileFrames", false);
boolean drawTileCoordinates = sharedPreferences.getBoolean("drawTileCoordinates", false);
boolean highlightWaterTiles = sharedPreferences.getBoolean("highlightWaterTiles", false);
DebugSettings debugSettings = new DebugSettings(drawTileCoordinates, drawTileFrames, highlightWaterTiles);
this.mapView.setDebugSettings(debugSettings);
if (this.mapView.getMapFile() == null) {
startMapFilePicker();
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(BUNDLE_SHOW_MY_LOCATION, this.myLocationOverlay.isMyLocationEnabled());
outState.putBoolean(BUNDLE_CENTER_AT_FIRST_FIX, this.myLocationOverlay.isCenterAtNextFix());
outState.putBoolean(BUNDLE_SNAP_TO_LOCATION, this.myLocationOverlay.isSnapToLocationEnabled());
}
/**
* @param showToast
* defines whether a toast message is displayed or not.
*/
void disableSnapToLocation(boolean showToast) {
if (this.myLocationOverlay.isSnapToLocationEnabled()) {
this.myLocationOverlay.setSnapToLocationEnabled(false);
this.snapToLocationView.setChecked(false);
this.mapView.setClickable(true);
if (showToast) {
showToastOnUiThread(getString(R.string.snap_to_location_disabled));
}
}
}
/**
* @param showToast
* defines whether a toast message is displayed or not.
*/
void enableSnapToLocation(boolean showToast) {
if (!this.myLocationOverlay.isSnapToLocationEnabled()) {
this.myLocationOverlay.setSnapToLocationEnabled(true);
this.mapView.setClickable(false);
if (showToast) {
showToastOnUiThread(getString(R.string.snap_to_location_enabled));
}
}
}
void invertSnapToLocation() {
if (this.myLocationOverlay.isSnapToLocationEnabled()) {
disableSnapToLocation(true);
} else {
enableSnapToLocation(true);
}
}
/**
* Uses the UI thread to display the given text message as toast notification.
*
* @param text
* the text message to display
*/
void showToastOnUiThread(final String text) {
if (AndroidUtils.currentThreadIsUiThread()) {
Toast toast = Toast.makeText(this, text, Toast.LENGTH_LONG);
toast.show();
} else {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast toast = Toast.makeText(AdvancedMapViewer.this, text, Toast.LENGTH_LONG);
toast.show();
}
});
}
}
}