/* * Copyright (c) 2013, Will Szumski * Copyright (c) 2013, Doug Szumski * * This file is part of Cyclismo. * * Cyclismo 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. * * Cyclismo 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 Cyclismo. If not, see <http://www.gnu.org/licenses/>. */ /* * Copyright 2008 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package org.cowboycoders.cyclismo.fragments; import android.graphics.drawable.Drawable; import android.location.Location; import android.os.Bundle; import android.support.v4.app.Fragment; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.ImageButton; //FIXME: maphack //import com.google.android.gms.maps.CameraUpdate; //import com.google.android.gms.maps.CameraUpdateFactory; //import com.google.android.gms.maps.GoogleMap; //import com.google.android.gms.maps.GoogleMap.OnCameraChangeListener; //import com.google.android.gms.maps.GoogleMap.OnMarkerClickListener; //import com.google.android.gms.maps.LocationSource; //import com.google.android.gms.maps.LocationSource.OnLocationChangedListener; //import com.google.android.gms.maps.SupportMapFragment; //import com.google.android.gms.maps.model.CameraPosition; //import com.google.android.gms.maps.model.LatLong; //import com.google.android.gms.maps.model.LatLongBounds; //import com.google.android.gms.maps.model.Marker; //import com.google.android.gms.maps.model.Polyline; import org.cowboycoders.cyclismo.maps.AugmentedPolyline; import org.mapsforge.core.graphics.Bitmap; import org.mapsforge.map.android.util.AndroidUtil; import org.mapsforge.map.android.view.MapView; import org.mapsforge.map.layer.Layer; import org.mapsforge.map.layer.LayerManager; import org.mapsforge.map.layer.cache.TileCache; import org.mapsforge.map.layer.download.TileDownloadLayer; import org.mapsforge.map.layer.download.tilesource.OpenStreetMapMapnik; import org.mapsforge.core.model.LatLong; import org.mapsforge.core.model.BoundingBox; import org.mapsforge.map.layer.overlay.Marker; import org.mapsforge.map.model.MapViewPosition; import org.mapsforge.core.model.Dimension; import org.mapsforge.map.android.graphics.AndroidGraphicFactory; import org.mapsforge.map.layer.Layers; import org.cowboycoders.cyclismo.DummyOverlay; import org.cowboycoders.cyclismo.MapOverlay; import org.cowboycoders.cyclismo.R; import org.cowboycoders.cyclismo.StaticOverlay; import org.cowboycoders.cyclismo.TrackDetailActivity; import org.cowboycoders.cyclismo.content.MyTracksCourseProviderUtils; import org.cowboycoders.cyclismo.content.MyTracksProviderUtils; import org.cowboycoders.cyclismo.content.MyTracksProviderUtils.Factory; import org.cowboycoders.cyclismo.content.Track; import org.cowboycoders.cyclismo.content.TrackDataHub; import org.cowboycoders.cyclismo.content.TrackDataListener; import org.cowboycoders.cyclismo.content.TrackDataType; import org.cowboycoders.cyclismo.content.Waypoint; import org.cowboycoders.cyclismo.stats.TripStatistics; import org.cowboycoders.cyclismo.util.ApiAdapterFactory; import org.cowboycoders.cyclismo.util.LocationUtils; import org.mapsforge.map.util.MapPositionUtil; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; import java.util.concurrent.TimeUnit; import static org.mapsforge.core.util.LatLongUtils.zoomForBounds; /** * A fragment to display map to the user. * * FIXME: added lots or redrawCourseOverlay()'s : remove some (if poss) * * @author Leif Hendrik Wilden * @author Rodrigo Damazio */ public class MapFragment extends Fragment implements TrackDataListener { public static final String TAG = MapFragment.class.getSimpleName(); private static final String CURRENT_LOCATION_KEY = "current_location_key"; private static final String KEEP_CURRENT_LOCATION_VISIBLE_KEY = "keep_current_location_visible_key"; private static final String ZOOM_TO_CURRENT_LOCATION_KEY = "zoom_to_current_location_key"; private static final double DEFAULT_LATITUDE = 52.15; private static final double DEFAULT_LONGITUDE = 93.916667; private static final long COURSE_LOAD_TIMEOUT_NS = TimeUnit.SECONDS.toNanos(10); private TrackDataHub trackDataHub; private TrackDataHub courseDataHub; // Current location private Location currentLocation; // True to keep the currentLocation visible private boolean keepCurrentLocationVisible; // True to zoom to currentLocation when it is available private boolean zoomToCurrentLocation; private List<TileCache> tileCaches = new ArrayList<TileCache>(); private TileDownloadLayer downloadLayer; // For showing a marker private long markerTrackId = -1L; private long markerId = -1L; // Current track private Track currentTrack; private Track currentCourse; private boolean courseMode = false; // Current paths private ArrayList<AugmentedPolyline> paths = new ArrayList<AugmentedPolyline>(); boolean reloadPaths = true; // UI elements private MapView mapView; private MapViewPosition mapViewPos; private MapOverlay mapOverlay; private DummyOverlay courseDummyOverlay; private StaticOverlay courseOverlay; private View mapContainer; private ImageButton myLocationImageButton; private Marker currentPosMarker; private Bitmap currentPosBitmap; private boolean mUseCourseProvider; private long courseTrackId; private static float CURRENT_LOC_ANCHOR_X = 48f / 96f; private static float CURRENT_LOC_ANCHOR_Y = 92f / 96f; private TrackDataListener courseTrackDataListener = new TrackDataListener() { @SuppressWarnings("hiding") //private boolean reloadPaths = true; @Override public void onLocationStateChanged(final LocationState state) { //ignore return; } @Override public void onLocationChanged(final Location location) { //ignore } @Override public void onHeadingChanged(double heading) { // We don't care. } @Override public void onSelectedTrackChanged(final Track track) { synchronized (MapFragment.this) { if (isResumed()) { if (courseMode) { Log.d(TAG,"in courseTrackDataListener : onSelectedTrackChanged"); currentCourse = track; // boolean hasTrack = track != null; // if (hasTrack) { //courseOverlay.setShowEndMarker(true); if(courseOverlay == null) { showTrack(track); } }} //redrawCourseOverlay(); //currentTrack = track; //showTrack(); // synchronized (this) { // /* // * Synchronize to prevent race condition in changing markerTrackId and // * markerId variables. // */ // if (track.getId() == markerTrackId) { // // Show the marker // showMarker(markerId); // // markerTrackId = -1L; // markerId = -1L; // } else { // // Show the track // showTrack(); // } // } // } } } @Override public void onTrackUpdated(Track track) { // We don't care. } @Override public void clearTrackPoints() { synchronized (MapFragment.this) { if (isResumed()) { //courseDummyOverlay.clearPoints(); //reloadPaths = true; }} } @Override public void onSampledInTrackPoint(final Location location) { synchronized (MapFragment.this) { if (isResumed()) { courseDummyOverlay.addLocation(location); } } } @Override public void onSampledOutTrackPoint(Location location) { // We don't care. } @Override public void onSegmentSplit(Location location) { synchronized (MapFragment.this) { if (isResumed()) { courseDummyOverlay.addSegmentSplit(); }} } @Override public void onNewTrackPointsDone() { // we have our data synchronized (MapFragment.this) { if (isResumed()) { if (courseDataHub != null) courseDataHub.unregisterTrackDataListener(this); getActivity().runOnUiThread(new Runnable() { public void run() { synchronized (MapFragment.this) { if (isResumed() && mapView != null) { courseDummyOverlay.update(null, null, true); if (courseOverlay == null && courseMode) { courseOverlay = new StaticOverlay(MapFragment.this.getActivity(), courseDummyOverlay.getLocations()); Log.d(TAG, "new courseOverlay"); MapFragment.this.notifyAll(); } } } } }); } }} @Override public void clearWaypoints() { if (isResumed()) { //courseDummyOverlay.clearWaypoints(); } } @Override public void onNewWaypoint(Waypoint waypoint) { synchronized (MapFragment.this) { if (isResumed() && waypoint != null && LocationUtils.isValidLocation(waypoint.getLocation())) { courseDummyOverlay.addWaypoint(waypoint); }} } @Override public void onNewWaypointsDone() { synchronized (MapFragment.this) { if (isResumed()) { getActivity().runOnUiThread(new Runnable() { public void run() { if (isResumed() && mapView != null) { courseDummyOverlay.update(null, null, true); } } }); } } } @Override public boolean onMetricUnitsChanged(boolean metric) { // We don't care. return false; } @Override public boolean onReportSpeedChanged(boolean reportSpeed) { // We don't care. return false; } @Override public boolean onMinRecordingDistanceChanged(int minRecordingDistance) { // We don't care. return false; } }; @Override public synchronized void onCreate(Bundle bundle) { super.onCreate(bundle); //courseTrackId = bundle.getLong(TrackDetailActivity.EXTRA_COURSE_TRACK_ID); setHasOptionsMenu(true); ApiAdapterFactory.getApiAdapter().invalidMenu(getActivity()); courseTrackId = -1l; if (getActivity() instanceof TrackDetailActivity) { mUseCourseProvider = ((TrackDetailActivity) getActivity()).isUsingCourseProvider(); courseTrackId = ((TrackDetailActivity) getActivity()).getCourseTrackId(); } courseMode = ((TrackDetailActivity) getActivity()).isCourseMode(); Log.d(TAG,"courseMode : " + courseMode); Log.d(TAG,"courseTrackId in bundle : " + courseTrackId); } @Override public synchronized View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Log.d(TAG, "onCreateView"); AndroidGraphicFactory.createInstance(this.getActivity().getApplication()); mapContainer = super.onCreateView(inflater, container, savedInstanceState); View layout = inflater.inflate(R.layout.map, container, false); mapView = (MapView) layout.findViewById(R.id.mapView); myLocationImageButton = (ImageButton) layout.findViewById(R.id.map_my_location); myLocationImageButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { forceUpdateLocation(); keepCurrentLocationVisible = true; zoomToCurrentLocation = true; updateCurrentLocation(); } }); //messageTextView = (TextView) layout.findViewById(R.id.map_message); //TODO: reimplement this with mapforge (see Marker#onTap) // mapView.setOnMarkerClickListener(new OnMarkerClickListener() { // // @Override // public boolean onMarkerClick(Marker marker) { // if (isResumed()) { // String title = marker.getTitle(); // if (title != null && title.length() > 0) { // long id = Long.valueOf(title); // Context context = getActivity(); // Intent intent = IntentUtils.newIntent(context, MarkerDetailActivity.class) // .putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, id); // context.startActivity(intent); // } // } // return true; // } // }); // mapView.setLocationSource(new LocationSource() { // // @Override // public void activate(OnLocationChangedListener listener) { // onLocationChangedListener = listener; // } // // @Override // public void deactivate() { // onLocationChangedListener = null; // } // }); createTileCaches(); //TODO: move this to button mapView.setClickable(true); mapView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { if (isResumed() && keepCurrentLocationVisible && currentLocation != null && !isLocationVisible(currentLocation)) { keepCurrentLocationVisible = false; zoomToCurrentLocation = false; } return false; } }); return layout; } /** * Called when the Fragment is visible to the user. This is generally * tied to {@link android.app.Activity#onStart() Activity.onStart} of the containing * Activity's lifecycle. */ @Override public synchronized void onStart() { super.onStart(); Log.d(TAG, "onStart"); Drawable currentPosDrawable = this.getActivity().getResources().getDrawable(R.drawable.location_marker); currentPosBitmap = AndroidGraphicFactory.convertToBitmap(currentPosDrawable); currentPosBitmap.incrementRefCount(); // keep the bitmap alive // FIXME: we have to double increment here to stop a null pointer issue on reload // NOTE2: we dec the ref count in onStop; is some other method decrementing the count?! currentPosBitmap.incrementRefCount(); mapOverlay = new MapOverlay(getActivity()); mapViewPos = mapView.getModel().mapViewPosition; LayerManager layerManager = this.mapView.getLayerManager(); Layers layers = layerManager.getLayers(); // OnlineTileSource onlineTileSource = new OnlineTileSource(new String[]{ // "otile1.mqcdn.com", "otile2.mqcdn.com", "otile3.mqcdn.com", // "otile4.mqcdn.com"}, 80); // onlineTileSource.setName("MapQuest").setAlpha(false) // .setBaseUrl("/tiles/1.0.0/map/").setExtension("png") // .setParallelRequestsLimit(8).setProtocol("http") // .setTileSize(256).setZoomLevelMax((byte) 18) // .setZoomLevelMin((byte) 0); mapViewPos.setZoomLevel((byte) 16); mapViewPos.animateTo(getDefaultLatLong()); layers.add(makeMapLayer()); currentPosMarker = new Marker(getDefaultLatLong(), currentPosBitmap, (int) ((currentPosBitmap.getWidth() / 2) - getLocationXAnchor() * currentPosBitmap.getWidth()), (int) ((currentPosBitmap.getWidth() / 2) - getLocationYAnchor() * currentPosBitmap.getWidth())); currentPosMarker.setVisible(false); if (currentLocation != null) { currentPosMarker.setLatLong(new LatLong(currentLocation.getLongitude(), currentLocation.getLongitude())); currentPosMarker.setVisible(true); } layers.add(currentPosMarker); } /** * Called when the Fragment is no longer started. This is generally * tied to {@link android.app.Activity#onStop() Activity.onStop} of the containing * Activity's lifecycle. */ @Override public synchronized void onStop() { super.onStop(); Log.d(TAG, "onStop"); // clean up markers in map overlay before destroy layers mapOverlay.destroy(); destroyLayers(); currentPosMarker = null; if (currentPosBitmap != null) { currentPosBitmap.decrementRefCount(); } Log.d(TAG, "onStop"); } @Override public synchronized void onDestroyView() { super.onDestroyView(); Log.d(TAG, "destroying MapFragment"); destroyTileCaches(); if (this.mapView != null) { mapView.destroy(); } AndroidGraphicFactory.clearResourceMemoryCache(); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); if (savedInstanceState != null) { keepCurrentLocationVisible = savedInstanceState.getBoolean( KEEP_CURRENT_LOCATION_VISIBLE_KEY, false); zoomToCurrentLocation = savedInstanceState.getBoolean(ZOOM_TO_CURRENT_LOCATION_KEY, false); currentLocation = (Location) savedInstanceState.getParcelable(CURRENT_LOCATION_KEY); updateCurrentLocation(); if (mapView != null) { // set map type e.g map vs satellite } } } @Override public synchronized void onResume() { Log.d(TAG, "onResume"); super.onResume(); if (courseMode) { // first run if (courseDummyOverlay == null) { courseDummyOverlay = new DummyOverlay(getActivity()); resumeCourseDataHub(); } else { // use locations courseDummy overlay if available courseOverlay = new StaticOverlay(MapFragment.this.getActivity(), courseDummyOverlay.getLocations()); mapOverlay.addUnderlay(courseOverlay); } } resumeTrackDataHub(); if (this.downloadLayer != null) this.downloadLayer.onResume(); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); if (currentLocation != null) { outState.putParcelable(CURRENT_LOCATION_KEY, currentLocation); } outState.putBoolean(KEEP_CURRENT_LOCATION_VISIBLE_KEY, keepCurrentLocationVisible); outState.putBoolean(ZOOM_TO_CURRENT_LOCATION_KEY, zoomToCurrentLocation); if (mapView != null) { // set map type e.g map vs satellite } } @Override public synchronized void onPause() { Log.d(TAG, "onPause"); super.onPause(); if (this.downloadLayer != null) this.downloadLayer.onPause(); pauseTrackDataHub(); pauseCourseDataHub(); courseOverlay = null; mapOverlay.addUnderlay(null); } @Override public void onDestroy() { super.onDestroy(); } //TODO: override online maps with a local offline map // protected MapFile getMapFile() { // return new MapFile(new File(Environment.getExternalStorageDirectory(), // this.getMapFileName())); // } // // protected String getMapFileName() { // return "germany.map"; // } /** * Shows the marker on the map. * * @param trackId the track id * @param id the marker id */ public void showMarker(long trackId, long id) { /* * Synchronize to prevent race condition in changing markerTrackId and * markerId variables. */ synchronized (this) { if (currentTrack != null && currentTrack.getId() == trackId) { showMarker(id); markerTrackId = -1L; markerId = -1L; return; } markerTrackId = trackId; markerId = id; } } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflator) { menuInflator.inflate(R.menu.map, menu); } @Override public void onPrepareOptionsMenu(Menu menu) { if (mapView != null) { // map types } super.onPrepareOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem menuItem) { return true; } @Override public void onLocationStateChanged(final LocationState state) { // we don't care } @Override public synchronized void onLocationChanged(final Location location) { if (isResumed()) { Log.v(TAG, "location changed"); if (isSelectedTrackRecording() && currentLocation == null && location != null) { zoomToCurrentLocation = true; } currentLocation = location; updateCurrentLocation(); } } /** * Hook to destroy layers. By default we destroy every layer that * has been added to the layer manager. */ public void destroyLayers() { for (Layer layer : mapView.getLayerManager().getLayers()) { mapView.getLayerManager().getLayers().remove(layer); layer.onDestroy(); } } /** * Hook to destroy tile caches. * By default we destroy every tile cache that has been added to the tileCaches list. */ protected void destroyTileCaches() { for (TileCache tileCache : tileCaches) { tileCache.destroy(); } tileCaches.clear(); } @Override public void onHeadingChanged(double heading) { // We don't care. } @Override public synchronized void onSelectedTrackChanged(final Track track) { if (isResumed()) { currentTrack = track; boolean hasTrack = track != null; if (hasTrack) { mapOverlay.setShowEndMarker(!isSelectedTrackRecording()); synchronized (this) { /* * Synchronize to prevent race condition in changing markerTrackId and * markerId variables. */ if (track.getId() == markerTrackId) { // Show the marker showMarker(markerId); markerTrackId = -1L; markerId = -1L; } else { // Show the track showTrack(); } } } } } private void reloadCourse() { if (courseTrackId != -1L) { courseDataHub.loadTrack(courseTrackId); } } private void reloadTrack() { if (currentTrack != null && !this.isSelectedTrackRecording()) { trackDataHub.loadTrack(currentTrack.getId()); } } @Override public void onTrackUpdated(Track track) { // We don't care. } @Override public synchronized void clearTrackPoints() { //FIXED: (left around in case) // otherwise temperamental showing of previously recored track // (will sometimes show map and sometimes will clear). // Correlated with number of trackDetailInstances open? // && this.isSelectedTrackRecording() if (isResumed()) { mapOverlay.clearPoints(); reloadPaths = true; //redrawCourseOverlay(); } } @Override public synchronized void onSampledInTrackPoint(final Location location) { if (isResumed()) { Log.v(TAG,"sampled in track point"); // if (!this.isSelectedTrackRecording() && loadCompleted) { // return; // } mapOverlay.addLocation(location); //redrawCourseOverlay(); } } @Override public synchronized void onSampledOutTrackPoint(Location location) { // We don't care. } @Override public synchronized void onSegmentSplit(Location location) { if (isResumed()) { // if (!this.isSelectedTrackRecording() && loadCompleted) { // return; // } mapOverlay.addSegmentSplit(); } } @Override public synchronized void onNewTrackPointsDone() { if (isResumed()) { Log.v(TAG,"track points done"); if (courseMode && courseOverlay == null) { final long startTime = System.nanoTime(); while( courseOverlay == null) { long timeLeft = COURSE_LOAD_TIMEOUT_NS - (System.nanoTime() - startTime); try { this.wait((long) (timeLeft / Math.pow(10, 6))); } catch (InterruptedException e) { e.printStackTrace(); } } } getActivity().runOnUiThread(new Runnable() { public void run() { if (isResumed() && mapView != null) { mapOverlay.addUnderlay(courseOverlay); mapOverlay.update(mapView, paths, reloadPaths); //add the overlays reloadPaths = false; } } }); } } @Override public synchronized void clearWaypoints() { if (isResumed()) { //clear layer mapOverlay.clearWaypoints(); } } @Override public synchronized void onNewWaypoint(Waypoint waypoint) { if (isResumed() && waypoint != null && LocationUtils.isValidLocation(waypoint.getLocation())) { mapOverlay.addWaypoint(waypoint); } } @Override public synchronized void onNewWaypointsDone() { if (isResumed()) { getActivity().runOnUiThread(new Runnable() { public void run() { if (isResumed() && mapView != null) { mapOverlay.update(mapView, paths, true); } } }); } } @Override public boolean onMetricUnitsChanged(boolean metric) { // We don't care. return false; } @Override public boolean onReportSpeedChanged(boolean reportSpeed) { // We don't care. return false; } @Override public boolean onMinRecordingDistanceChanged(int minRecordingDistance) { // We don't care. return false; } private synchronized void redrawCourseOverlay() { if (courseOverlay != null && mapView != null && courseMode) { Log.d(TAG,"redrawing courseOverlay"); getActivity().runOnUiThread(new Runnable() { @Override public void run() { try { courseOverlay.update(mapView); } catch (IllegalStateException e) { Log.d(TAG,"Illegal state exception whilst updating map polyline"); } } }); } } /** * Resumes the trackDataHub. Needs to be synchronized because the trackDataHub * can be accessed by multiple threads. */ private synchronized void resumeTrackDataHub() { trackDataHub = ((TrackDetailActivity) getActivity()).getTrackDataHub(); trackDataHub.registerTrackDataListener(this, EnumSet.of(TrackDataType.SELECTED_TRACK, TrackDataType.WAYPOINTS_TABLE, TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE, TrackDataType.LOCATION)); } /** * Pauses the trackDataHub. Needs to be synchronized because the trackDataHub * can be accessed by multiple threads. */ private synchronized void pauseCourseDataHub() { //FIXME: needs new listener if (courseDataHub != null) { courseDataHub.unregisterTrackDataListener(courseTrackDataListener); } courseDataHub = null; } /** * Resumes the trackDataHub. Needs to be synchronized because the trackDataHub * can be accessed by multiple threads. */ private synchronized void resumeCourseDataHub() { courseDataHub = ((TrackDetailActivity) getActivity()).getCourseDataHub(); courseDataHub.registerTrackDataListener(courseTrackDataListener, EnumSet.of(TrackDataType.SELECTED_TRACK, TrackDataType.WAYPOINTS_TABLE, TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE, TrackDataType.LOCATION)); } /** * Pauses the trackDataHub. Needs to be synchronized because the trackDataHub * can be accessed by multiple threads. */ private synchronized void pauseTrackDataHub() { if (trackDataHub != null) { trackDataHub.unregisterTrackDataListener(this); } trackDataHub = null; } /** * Returns true if the selected track is recording. Needs to be synchronized * because the trackDataHub can be accessed by multiple threads. */ private synchronized boolean isSelectedTrackRecording() { return trackDataHub != null && trackDataHub.isSelectedTrackRecording(); } /** * Forces update location. Needs to be synchronized because the trackDataHub * can be accessed by multiple threads. */ private synchronized void forceUpdateLocation() { if (trackDataHub != null) { trackDataHub.forceUpdateLocation(); } if (courseDataHub != null) { courseDataHub.forceUpdateLocation(); } } /** * Updates the current location and zoom to it if necessary. */ private synchronized void updateCurrentLocation() { getActivity().runOnUiThread(new Runnable() { public void run() { if (!isResumed() || mapView == null || currentLocation == null || currentPosMarker == null) { return; } LatLong latLong = new LatLong(currentLocation.getLatitude(), currentLocation.getLongitude()); //Log.d(TAG,"redrawing currentPositionMarker"); currentPosMarker.setLatLong(latLong); currentPosMarker.setVisible(true); currentPosMarker.requestRedraw(); if (zoomToCurrentLocation || (keepCurrentLocationVisible && !isLocationVisible(currentLocation))) { mapViewPos.animateTo(latLong); zoomToCurrentLocation = false; } }; }); } /** * The persistable ID is used to store settings information, like the center of the last view * and the zoomlevel. By default the simple name of the class is used. The value is not user * visibile. * @return the id that is used to save this mapview. */ protected String getPersistableId() { return this.getClass().getSimpleName(); } /** * Returns the relative size of a map view in relation to the screen size of the device. This * is used for cache size calculations. * By default this returns 1.0, for a full size map view. * @return the screen ratio of the mapview */ protected float getScreenRatio() { return 1.0f; } protected void createTileCaches() { // see SampleBaseActivity boolean threaded = true; int queueSize = 4; boolean persistent = true; this.tileCaches.add(AndroidUtil.createTileCache(this.getActivity(), getPersistableId(), mapView.getModel().displayModel.getTileSize(), this.getScreenRatio(), mapView.getModel().frameBufferModel.getOverdrawFactor(), threaded, queueSize, persistent )); } /** * Sets the camera over a track. */ private void showTrack() { showTrack(currentTrack); } private void showTrack(final Track track) { getActivity().runOnUiThread(new Runnable() { @Override public void run() { @SuppressWarnings("hiding") Track currentTrack = track; if (!isResumed() || mapView == null || currentTrack == null || currentTrack.getNumberOfPoints() < 2) { return; } /** * Check that mapContainer is valid. */ if (mapContainer == null || mapContainer.getWidth() == 0 || mapContainer.getHeight() == 0) { return; } TripStatistics tripStatistics = currentTrack.getTripStatistics(); int latitudeSpanE6 = tripStatistics.getTop() - tripStatistics.getBottom(); int longitudeSpanE6 = tripStatistics.getRight() - tripStatistics.getLeft(); if (latitudeSpanE6 > 0 && latitudeSpanE6 < 180E6 && longitudeSpanE6 > 0 && longitudeSpanE6 < 360E6) { LatLong southWest = new LatLong( tripStatistics.getBottomDegrees(), tripStatistics.getLeftDegrees()); LatLong northEast = new LatLong( tripStatistics.getTopDegrees(), tripStatistics.getRightDegrees()); BoundingBox bounds = new BoundingBox(southWest.latitude, southWest.longitude, northEast.latitude, northEast.longitude); mapViewPos.setMapLimit(bounds); zoomForBounds(mapView.getDimension(), bounds, mapView.getModel().displayModel.getTileSize()); mapViewPos.animateTo(bounds.getCenterPoint()); } } }); } /** * Sets the camera over a marker. * * @param id the marker id */ private void showMarker(final long id) { getActivity().runOnUiThread(new Runnable() { @Override public void run() { if (!isResumed() || mapView == null) { return; } MyTracksProviderUtils MyTracksProviderUtils = getProvider(); Waypoint waypoint = MyTracksProviderUtils.getWaypoint(id); if (waypoint != null && waypoint.getLocation() != null) { Location location = waypoint.getLocation(); LatLong latLong = new LatLong(location.getLatitude(), location.getLongitude()); keepCurrentLocationVisible = false; zoomToCurrentLocation = false; mapViewPos.animateTo(latLong); } } }); } /** * Gets the default LatLong. */ private LatLong getDefaultLatLong() { MyTracksProviderUtils myTracksProviderUtils = getProvider(); Location location = myTracksProviderUtils.getLastValidTrackPoint(); if (location != null) { return new LatLong(location.getLatitude(), location.getLongitude()); } else { return new LatLong(DEFAULT_LATITUDE, DEFAULT_LONGITUDE); } } private MyTracksProviderUtils getProvider() { if (mUseCourseProvider) { return new MyTracksCourseProviderUtils(this.getActivity().getContentResolver()); } return Factory.get(getActivity()); } /** * Returns true if the location is visible. Needs to run on the UI thread. * * @param location the location */ private boolean isLocationVisible(Location location) { if (location == null || mapView == null) { return false; } LatLong latLong = new LatLong(location.getLatitude(), location.getLongitude()); Dimension dimension = mapView.getModel().mapViewDimension.getDimension(); int tileSize = mapView.getModel().displayModel.getTileSize(); BoundingBox bounds = MapPositionUtil.getBoundingBox(mapViewPos.getMapPosition(), dimension, tileSize); return bounds.contains(latLong); } public Layer makeMapLayer() { this.downloadLayer = new TileDownloadLayer(this.tileCaches.get(0), this.mapView.getModel().mapViewPosition, OpenStreetMapMapnik.INSTANCE, AndroidGraphicFactory.INSTANCE); return this.downloadLayer; } public float getLocationYAnchor() { return CURRENT_LOC_ANCHOR_Y; } public float getLocationXAnchor() { return CURRENT_LOC_ANCHOR_X; } }