/*
* ____.____ __.____ ___ _____
* | | |/ _| | \ / _ \ ______ ______
* | | < | | / / /_\ \\____ \\____ \
* /\__| | | \| | / / | \ |_> > |_> >
* \________|____|__ \______/ \____|__ / __/| __/
* \/ \/|__| |__|
*
* Copyright (c) 2014-2015 Paul "Marunjar" Pretsch
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package org.voidsink.anewjkuapp.fragment;
import android.app.AlertDialog;
import android.app.SearchManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.widget.SearchView;
import android.util.Log;
import android.view.Gravity;
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.widget.TextView;
import android.widget.Toast;
import org.mapsforge.core.graphics.Bitmap;
import org.mapsforge.core.model.BoundingBox;
import org.mapsforge.core.model.Dimension;
import org.mapsforge.core.model.LatLong;
import org.mapsforge.core.model.MapPosition;
import org.mapsforge.core.util.LatLongUtils;
import org.mapsforge.core.util.MercatorProjection;
import org.mapsforge.map.android.graphics.AndroidGraphicFactory;
import org.mapsforge.map.android.rendertheme.AssetsRenderTheme;
import org.mapsforge.map.android.util.AndroidUtil;
import org.mapsforge.map.android.view.MapView;
import org.mapsforge.map.datastore.MapDataStore;
import org.mapsforge.map.layer.Layers;
import org.mapsforge.map.layer.cache.TileCache;
import org.mapsforge.map.layer.labels.LabelLayer;
import org.mapsforge.map.layer.overlay.Marker;
import org.mapsforge.map.layer.renderer.TileRendererLayer;
import org.mapsforge.map.model.MapViewPosition;
import org.mapsforge.map.reader.MapFile;
import org.mapsforge.map.rendertheme.InternalRenderTheme;
import org.mapsforge.map.rendertheme.XmlRenderTheme;
import org.voidsink.anewjkuapp.ImportPoiTask;
import org.voidsink.anewjkuapp.LocationOverlay;
import org.voidsink.anewjkuapp.Poi;
import org.voidsink.anewjkuapp.PoiAdapter;
import org.voidsink.anewjkuapp.PoiContentContract;
import org.voidsink.anewjkuapp.PreferenceWrapper;
import org.voidsink.anewjkuapp.R;
import org.voidsink.anewjkuapp.activity.MainActivity;
import org.voidsink.anewjkuapp.analytics.Analytics;
import org.voidsink.anewjkuapp.base.BaseFragment;
import org.voidsink.anewjkuapp.utils.Consts;
import org.voidsink.anewjkuapp.utils.MapUtils;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class MapFragment extends BaseFragment implements
SearchView.OnQueryTextListener {
/**
* The fragment argument representing the item ID that this fragment
* represents.
*/
public static final String MAP_FILE_NAME = "campus.map";
private static final String TAG = MapFragment.class.getSimpleName();
private static final byte MAX_ZOOM_LEVEL = 19;
private static final byte MIN_ZOOM_LEVEL = 14; // full campus fits to screen at zoom level 15
private static final byte DEFAULT_ZOOM_LEVEL = 17;
private static final String KEY_GOAL_LATITUDE = "GOAL_LATITUDE";
private static final String KEY_GOAL_LONGITUDE = "GOAL_LONGITUDE";
private static final String KEY_GOAL_NAME = "GOAL_NAME";
// Map Layer
private MyMarker goalLocation = null;
private Marker goalLocationOverlay;
private LocationOverlay mMyLocationOverlay;
/**
* The dummy content this fragment is presenting.
*/
private MapView mapView;
private SearchView mSearchView;
/**
* Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes).
*/
public MapFragment() {
super();
}
@Override
public void onPause() {
mMyLocationOverlay.disableMyLocation();
super.onPause();
}
class MyMarker {
private final LatLong mLatLon;
private final String mName;
public MyMarker(double lat, double lon, String name) {
this.mLatLon = new LatLong(lat, lon);
this.mName = name;
}
public LatLong getLatLon() {
return mLatLon;
}
public String getName() {
return mName;
}
}
@Override
public void handlePendingIntent(Intent intent) {
super.handlePendingIntent(intent);
if (intent != null) {
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
if (intent.getData() != null) {
finishSearch(intent.getData());
} else {
String query = intent.getStringExtra(SearchManager.QUERY);
boolean isExactLocation = intent.getBooleanExtra(MainActivity.ARG_EXACT_LOCATION, false);
doSearch(query, isExactLocation);
}
} else if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
if (intent.getData() != null) {
finishSearch(intent.getData());
} else {
String query = intent.getStringExtra(SearchManager.QUERY);
boolean isExactLocation = intent.getBooleanExtra(MainActivity.ARG_EXACT_LOCATION, false);
doSearch(query, isExactLocation);
}
}
}
}
private void doSearch(String query, boolean isExactLocation) {
query = query.trim();
Log.i(TAG, "query: " + query);
List<Poi> pois = new ArrayList<>();
ContentResolver cr = getContext().getContentResolver();
Uri searchUri = PoiContentContract.CONTENT_URI.buildUpon()
.appendPath(SearchManager.SUGGEST_URI_PATH_QUERY)
.appendPath(query).build();
Cursor c = cr.query(searchUri, ImportPoiTask.POI_PROJECTION, null,
null, null);
if (c != null) {
while (c.moveToNext()) {
Poi p = new Poi(c);
if (!isExactLocation || p.getName().equalsIgnoreCase(query)) {
pois.add(p);
}
}
c.close();
}
if (pois.size() == 0) {
Toast.makeText(getContext(), getContext().getString(R.string.map_place_not_found, query), Toast.LENGTH_LONG)
.show();
} else if (pois.size() == 1) {
finishSearch(pois.get(0));
} else {
AlertDialog.Builder poiSelector = new AlertDialog.Builder(getContext());
poiSelector.setTitle(R.string.map_select_location);
final PoiAdapter arrayAdapter = new PoiAdapter(getContext());
arrayAdapter.addAll(pois);
poiSelector.setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
poiSelector.setAdapter(arrayAdapter,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finishSearch(arrayAdapter.getItem(which));
}
});
poiSelector.show();
}
}
private void finishSearch(Poi poi) {
if (poi != null) {
finishSearch(PoiContentContract.Poi.CONTENT_URI.buildUpon()
.appendEncodedPath(Integer.toString(poi.getId())).build());
}
}
private void finishSearch(Uri uri) {
Log.i(TAG, "finish search: " + uri.toString());
// jump to point with given Uri
ContentResolver cr = getActivity().getContentResolver();
Cursor c = cr
.query(uri, ImportPoiTask.POI_PROJECTION, null, null, null);
if (c != null) {
if (c.moveToNext()) {
String name = c.getString(ImportPoiTask.COLUMN_POI_NAME);
double lon = c.getDouble(ImportPoiTask.COLUMN_POI_LON);
double lat = c.getDouble(ImportPoiTask.COLUMN_POI_LAT);
setNewGoal(new MyMarker(lat, lon, name));
}
c.close();
}
if (mSearchView != null) {
mSearchView.setQuery("", false);
}
}
@Override
public void onResume() {
super.onResume();
this.mMyLocationOverlay.enableMyLocation(false);
}
private void setNewGoal(MyMarker marker) {
if (goalLocationOverlay != null) {
if (marker != null) {
this.goalLocation = marker;
if (!marker.getName().isEmpty()) {
// generate Bubble image
TextView bubbleView = new TextView(this.getContext());
MapUtils.setBackground(bubbleView, ContextCompat.getDrawable(getContext(), R.drawable.balloon_overlay));
bubbleView.setGravity(Gravity.CENTER);
bubbleView.setMaxEms(20);
bubbleView.setTextSize(15);
bubbleView.setTextColor(Color.BLACK);
bubbleView.setText(marker.getName());
Bitmap bubble = MapUtils.viewToBitmap(getContext(), bubbleView);
bubble.incrementRefCount();
// set new goal
this.goalLocationOverlay.setLatLong(marker.getLatLon());
this.goalLocationOverlay.setBitmap(bubble);
this.goalLocationOverlay.setHorizontalOffset(0);
this.goalLocationOverlay.setVerticalOffset(-bubble.getHeight() / 2);
} else {
Drawable d = ContextCompat.getDrawable(getContext(), R.drawable.ic_marker_goal_position);
Bitmap b = AndroidGraphicFactory.convertToBitmap(d);
// set new goal
this.goalLocationOverlay.setLatLong(marker.getLatLon());
this.goalLocationOverlay.setBitmap(b);
this.goalLocationOverlay.setHorizontalOffset(0);
this.goalLocationOverlay.setVerticalOffset(0);
}
if (this.mMyLocationOverlay != null) {
this.mMyLocationOverlay.setSnapToLocationEnabled(false);
if (this.mMyLocationOverlay.getLastLocation() != null) {
LatLong mLocation = LocationOverlay
.locationToLatLong(this.mMyLocationOverlay
.getLastLocation());
// lat long difference for bounding box
double mDLat = Math.abs((mLocation.latitude - marker.getLatLon().latitude));
double mDLong = Math.abs((mLocation.longitude - marker.getLatLon().longitude));
// trunc distance
double distance = mLocation.distance(marker.getLatLon());
if (distance > 0.0088) {
mDLat = (mDLat * 0.0088 / distance);
mDLong = (mDLong * 0.0088 / distance);
}
// zoom to bounds
BoundingBox bb = new BoundingBox(
Math.max(MercatorProjection.LATITUDE_MIN, marker.getLatLon().latitude - mDLat),
Math.max(-180, marker.getLatLon().longitude - mDLong),
Math.min(MercatorProjection.LATITUDE_MAX, marker.getLatLon().latitude + mDLat),
Math.min(180, marker.getLatLon().longitude + mDLong));
Dimension dimension = this.mapView.getModel().mapViewDimension
.getDimension();
// zoom to bounding box, center at goalPosition
this.mapView.getModel().mapViewPosition
.setMapPosition(new MapPosition(marker.getLatLon(), LatLongUtils
.zoomForBounds(dimension, bb, this.mapView
.getModel().displayModel.getTileSize())));
} else {
this.mapView.setCenter(marker.getLatLon());
}
} else {
this.mapView.setCenter(marker.getLatLon());
}
} else {
this.goalLocationOverlay.setLatLong(null);
this.mMyLocationOverlay.setSnapToLocationEnabled(true);
}
this.goalLocationOverlay.requestRedraw();
this.mMyLocationOverlay.requestRedraw();
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_snap_to_location:
item.setChecked(!item.isChecked());
this.mMyLocationOverlay.setSnapToLocationEnabled(item.isChecked());
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.map, menu);
MenuItem searchItem = menu.findItem(R.id.action_search_poi);
mSearchView = (SearchView) MenuItemCompat.getActionView(searchItem);
setupSearchView(mSearchView);
if (mMyLocationOverlay != null) {
MenuItem snapToLocationItem = menu
.findItem(R.id.action_snap_to_location);
mMyLocationOverlay.setSnapToLocationItem(snapToLocationItem);
}
}
private void setupSearchView(SearchView searchView) {
// Get the SearchView and set the searchable configuration
SearchManager searchManager = (SearchManager) getActivity()
.getSystemService(Context.SEARCH_SERVICE);
// Assumes current activity is the searchable activity
if (searchView != null) {
searchView.setSearchableInfo(searchManager
.getSearchableInfo(getActivity().getComponentName()));
searchView.setOnQueryTextListener(this);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_map, container,
false);
this.mapView = (MapView) rootView.findViewById(R.id.mapView);
this.mapView.setClickable(true);
//this.mapView.getFpsCounter().setVisible(true);
this.mapView.getMapScaleBar().setVisible(true);
this.mapView.setBuiltInZoomControls(true);
this.mapView.setZoomLevelMin(MIN_ZOOM_LEVEL);
this.mapView.setZoomLevelMax(MAX_ZOOM_LEVEL);
this.mapView.getMapZoomControls().setZoomInResource(R.drawable.zoom_control_in);
this.mapView.getMapZoomControls().setZoomOutResource(R.drawable.zoom_control_out);
this.mapView.getMapZoomControls().setMarginHorizontal(getContext().getResources().getDimensionPixelSize(R.dimen.map_zoom_control_margin_horizontal));
this.mapView.getMapZoomControls().setMarginVertical(getContext().getResources().getDimensionPixelSize(R.dimen.map_zoom_control_margin_vertical));
createLayers();
restoreMarker(savedInstanceState);
return rootView;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
restoreMarker(savedInstanceState);
}
private void restoreMarker(Bundle savedInstanceState) {
if (savedInstanceState != null) {
if (savedInstanceState.containsKey(KEY_GOAL_LATITUDE) && savedInstanceState.containsKey(KEY_GOAL_LONGITUDE) && savedInstanceState.containsKey(KEY_GOAL_NAME)) {
setNewGoal(new MyMarker(savedInstanceState.getDouble(KEY_GOAL_LATITUDE), savedInstanceState.getDouble(KEY_GOAL_LONGITUDE), savedInstanceState.getString(KEY_GOAL_NAME)));
}
}
}
@Override
public void onStart() {
super.onStart();
}
private void createLayers() {
TileCache tileCache = AndroidUtil.createTileCache(getContext(),
"mapFragment",
this.mapView.getModel().displayModel.getTileSize(),
1.0f,
this.mapView.getModel().frameBufferModel.getOverdrawFactor());
final Layers layers = this.mapView.getLayerManager().getLayers();
MapViewPosition mapViewPosition = this.mapView.getModel().mapViewPosition;
initializePosition(mapViewPosition);
TileRendererLayer tileRendererLayer = createTileRendererLayer(tileCache, mapViewPosition,
getMapFile(), getRenderTheme());
layers.add(tileRendererLayer);
LabelLayer labelLayer = new LabelLayer(AndroidGraphicFactory.INSTANCE, tileRendererLayer.getLabelStore());
mapView.getLayerManager().getLayers().add(labelLayer);
// overlay with a marker to show the goal position
this.goalLocationOverlay = new Marker(null, null, 0, 0);
layers.add(this.goalLocationOverlay);
// overlay with a marker to show the actual position
Drawable drawable = ContextCompat.getDrawable(getContext(), R.drawable.ic_marker_own_position);
Bitmap bitmap = AndroidGraphicFactory.convertToBitmap(drawable);
this.mMyLocationOverlay = new LocationOverlay(getContext(), mapViewPosition, bitmap);
this.mMyLocationOverlay.setSnapToLocationEnabled(false);
layers.add(this.mMyLocationOverlay);
}
private XmlRenderTheme getRenderTheme() {
try {
return new AssetsRenderTheme(getContext(), "", "renderthemes/rendertheme-v5.xml");
} catch (Exception e) {
Analytics.sendException(getContext(), e, false);
}
return InternalRenderTheme.OSMARENDER;
}
@Override
public void onDestroyView() {
removeLayers();
super.onDestroyView();
}
@Override
public void onStop() {
super.onStop();
}
private void removeLayers() {
this.mMyLocationOverlay = null;
this.goalLocationOverlay = null;
this.mapView.destroyAll();
this.mapView = null;
AndroidGraphicFactory.clearResourceMemoryCache();
}
private TileRendererLayer createTileRendererLayer(TileCache tileCache,
MapViewPosition mapViewPosition, File mapFile,
XmlRenderTheme renderTheme) {
MapDataStore mapDataStore = new MapFile(mapFile);
TileRendererLayer tileRendererLayer = AndroidUtil.createTileRendererLayer(tileCache,
mapViewPosition, mapDataStore, renderTheme, false, false, true);
tileRendererLayer.setTextScale(1.5f);
return tileRendererLayer;
}
private MapViewPosition initializePosition(MapViewPosition mvp) {
LatLong center = mvp.getCenter();
if (center.equals(new LatLong(0, 0))) {
mvp.setMapPosition(this.getInitialPosition());
}
return mvp;
}
private MapPosition getInitialPosition() {
File mapFile = getMapFile();
MapDataStore mapDataStore = new MapFile(mapFile);
final LatLong uniteich = new LatLong(48.33706, 14.31960);
final byte zoomLevel = (byte) Math.max(Math.min(DEFAULT_ZOOM_LEVEL /*mapDataStore.startZoomLevel()*/, MAX_ZOOM_LEVEL), MIN_ZOOM_LEVEL);
if (mapDataStore.boundingBox().contains(uniteich)) {
// Insel im Uniteich
return new MapPosition(uniteich, zoomLevel);
} else if (mapDataStore.startPosition() != null &&
mapDataStore.boundingBox().contains(mapDataStore.startPosition())) {
// given start position, zoom in range
return new MapPosition(mapDataStore.startPosition(), zoomLevel);
} else {
// center of the map
return new MapPosition(
mapDataStore.boundingBox().getCenterPoint(),
zoomLevel);
}
}
@Override
public void onDestroy() {
super.onDestroy();
}
private File getMapFile() {
File mapFile = PreferenceWrapper.getMapFile(getContext());
if (mapFile == null || !mapFile.exists() || !mapFile.canRead()) {
mapFile = new File(getActivity().getFilesDir(), MAP_FILE_NAME);
Log.i(TAG, "use internal map: " + mapFile.toString());
} else {
Log.i(TAG, "use external map: " + mapFile.toString());
}
return mapFile;
}
@Override
public boolean onQueryTextChange(String newText) {
// Log.i(TAG, newText);
return false;
}
@Override
public boolean onQueryTextSubmit(String newText) {
// Toast.makeText(mContext, newText + " submitted", Toast.LENGTH_SHORT)
// .show();
return false;
}
@Override
protected String getScreenName() {
return Consts.SCREEN_MAP;
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (goalLocation != null) {
outState.putDouble(KEY_GOAL_LATITUDE, goalLocation.getLatLon().latitude);
outState.putDouble(KEY_GOAL_LONGITUDE, goalLocation.getLatLon().longitude);
outState.putString(KEY_GOAL_NAME, goalLocation.getName());
}
}
}