Files
OpenTracks/MyTracks/src/com/google/android/apps/mytracks/fragments/MapFragment.java
T
2012-07-11 15:42:36 -07:00

547 lines
16 KiB
Java

/*
* 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 com.google.android.apps.mytracks.fragments;
import com.google.android.apps.mytracks.MapOverlay;
import com.google.android.apps.mytracks.MyTracksApplication;
import com.google.android.apps.mytracks.TrackDetailActivity;
import com.google.android.apps.mytracks.content.TrackDataType;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TrackDataHub;
import com.google.android.apps.mytracks.content.TrackDataListener;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
import com.google.android.apps.mytracks.util.GeoRect;
import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;
import com.google.android.maps.mytracks.R;
import android.content.Intent;
import android.location.Location;
import android.os.Bundle;
import android.provider.Settings;
import android.support.v4.app.Fragment;
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;
import android.widget.TextView;
import android.widget.Toast;
import java.util.EnumSet;
import java.util.List;
/**
* A fragment to display map to the user.
*
* @author Leif Hendrik Wilden
* @author Rodrigo Damazio
*/
public class MapFragment extends Fragment
implements View.OnTouchListener, View.OnClickListener, TrackDataListener {
public static final String MAP_FRAGMENT_TAG = "mapFragment";
private static final String KEY_CURRENT_LOCATION = "currentLocation";
private static final String KEY_KEEP_MY_LOCATION_VISIBLE = "keepMyLocationVisible";
private TrackDataHub trackDataHub;
// True to keep my location visible.
private boolean keepMyLocationVisible;
// True to zoom to my location. Only apply when keepMyLocationVisible is true.
private boolean zoomToMyLocation;
// The track id of the marker to show.
private long markerTrackId;
// The marker id to show
private long markerId;
// The current selected track id. Set in onSelectedTrackChanged.
private long currentSelectedTrackId;
// The current location. Set in onCurrentLocationChanged.
private Location currentLocation;
// UI elements
private View mapViewContainer;
private MapView mapView;
private MapOverlay mapOverlay;
private ImageButton myLocationImageButton;
private TextView messageTextView;
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mapViewContainer = ((TrackDetailActivity) getActivity()).getMapViewContainer();
mapView = (MapView) mapViewContainer.findViewById(R.id.map_view);
mapOverlay = new MapOverlay(getActivity());
List<Overlay> overlays = mapView.getOverlays();
overlays.clear();
overlays.add(mapOverlay);
mapView.requestFocus();
mapView.setOnTouchListener(this);
mapView.setBuiltInZoomControls(true);
myLocationImageButton = (ImageButton) mapViewContainer.findViewById(R.id.map_my_location);
myLocationImageButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showMyLocation();
}
});
messageTextView = (TextView) mapViewContainer.findViewById(R.id.map_message);
ApiAdapterFactory.getApiAdapter().invalidMenu(getActivity());
return mapViewContainer;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
keepMyLocationVisible = savedInstanceState.getBoolean(KEY_KEEP_MY_LOCATION_VISIBLE, false);
currentLocation = (Location) savedInstanceState.getParcelable(KEY_CURRENT_LOCATION);
if (currentLocation != null) {
updateCurrentLocation();
}
}
}
@Override
public void onResume() {
super.onResume();
resumeTrackDataHub();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(KEY_KEEP_MY_LOCATION_VISIBLE, keepMyLocationVisible);
if (currentLocation != null) {
outState.putParcelable(KEY_CURRENT_LOCATION, currentLocation);
}
}
@Override
public void onPause() {
super.onPause();
pauseTrackDataHub();
}
@Override
public void onDestroyView() {
super.onDestroyView();
ViewGroup parentViewGroup = (ViewGroup) mapViewContainer.getParent();
if (parentViewGroup != null) {
parentViewGroup.removeView(mapViewContainer);
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflator) {
menuInflator.inflate(R.menu.map, menu);
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
int titleId = R.string.menu_satellite_mode;
if (mapView != null) {
titleId = mapView.isSatellite() ? R.string.menu_map_mode : R.string.menu_satellite_mode;
}
menu.findItem(R.id.map_satellite_mode).setTitle(titleId);
super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem menuItem) {
if (mapView != null && menuItem.getItemId() == R.id.map_satellite_mode) {
mapView.setSatellite(!mapView.isSatellite());
return true;
}
return super.onOptionsItemSelected(menuItem);
}
/**
* Shows my location.
*/
private void showMyLocation() {
updateTrackDataHub();
keepMyLocationVisible = true;
zoomToMyLocation = true;
if (currentLocation != null) {
updateCurrentLocation();
}
}
/**
* Shows the marker.
*
* @param id the marker id
*/
private void showMarker(long id) {
MyTracksProviderUtils MyTracksProviderUtils = Factory.get(getActivity());
Waypoint waypoint = MyTracksProviderUtils.getWaypoint(id);
if (waypoint != null && waypoint.getLocation() != null) {
keepMyLocationVisible = false;
GeoPoint center = new GeoPoint((int) (waypoint.getLocation().getLatitude() * 1E6),
(int) (waypoint.getLocation().getLongitude() * 1E6));
mapView.getController().setCenter(center);
mapView.getController().setZoom(mapView.getMaxZoomLevel());
mapView.invalidate();
}
}
/**
* Shows the marker.
*
* @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 (trackId == currentSelectedTrackId) {
showMarker(id);
markerTrackId = -1L;
markerId = -1L;
return;
}
markerTrackId = trackId;
markerId = id;
}
}
@Override
public boolean onTouch(View view, MotionEvent event) {
if (keepMyLocationVisible && event.getAction() == MotionEvent.ACTION_MOVE) {
if (!isVisible(currentLocation)) {
/*
* Only set to false when no longer visible. Thus can keep showing the
* current location with the next location update.
*/
keepMyLocationVisible = false;
}
}
return false;
}
@Override
public void onClick(View v) {
if (v == messageTextView) {
Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
startActivity(intent);
}
}
@Override
public void onProviderStateChange(ProviderState state) {
final int messageId;
final boolean isGpsDisabled;
switch (state) {
case DISABLED:
messageId = R.string.gps_need_to_enable;
isGpsDisabled = true;
break;
case NO_FIX:
case BAD_FIX:
messageId = R.string.gps_wait_for_signal;
isGpsDisabled = false;
break;
case GOOD_FIX:
messageId = -1;
isGpsDisabled = false;
break;
default:
throw new IllegalArgumentException("Unexpected state: " + state);
}
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
if (messageId != -1) {
messageTextView.setText(messageId);
messageTextView.setVisibility(View.VISIBLE);
if (isGpsDisabled) {
Toast.makeText(getActivity(), R.string.gps_not_found, Toast.LENGTH_LONG).show();
// Click to show the location source settings
messageTextView.setOnClickListener(MapFragment.this);
} else {
messageTextView.setOnClickListener(null);
}
} else {
messageTextView.setVisibility(View.GONE);
}
}
});
}
@Override
public void onCurrentLocationChanged(Location location) {
currentLocation = location;
updateCurrentLocation();
}
@Override
public void onCurrentHeadingChanged(double heading) {
if (mapOverlay.setHeading((float) heading)) {
mapView.postInvalidate();
}
}
@Override
public void onSelectedTrackChanged(final Track track, final boolean isRecording) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
boolean hasTrack = track != null;
mapOverlay.setTrackDrawingEnabled(hasTrack);
if (hasTrack) {
synchronized (this) {
/*
* Synchronize to prevent race condition in changing markerTrackId
* and markerId variables.
*/
currentSelectedTrackId = track.getId();
updateMap(track);
}
mapOverlay.setShowEndMarker(!isRecording);
}
mapView.invalidate();
}
});
}
@Override
public void onTrackUpdated(Track track) {
// We don't care.
}
@Override
public void clearTrackPoints() {
mapOverlay.clearPoints();
}
@Override
public void onNewTrackPoint(Location location) {
if (LocationUtils.isValidLocation(location)) {
mapOverlay.addLocation(location);
}
}
@Override
public void onSampledOutTrackPoint(Location loc) {
// We don't care.
}
@Override
public void onSegmentSplit() {
mapOverlay.addSegmentSplit();
}
@Override
public void onNewTrackPointsDone() {
mapView.postInvalidate();
}
@Override
public void clearWaypoints() {
mapOverlay.clearWaypoints();
}
@Override
public void onNewWaypoint(Waypoint waypoint) {
if (waypoint != null && LocationUtils.isValidLocation(waypoint.getLocation())) {
// TODO: Optimize locking inside addWaypoint
mapOverlay.addWaypoint(waypoint);
}
}
@Override
public void onNewWaypointsDone() {
mapView.postInvalidate();
}
@Override
public boolean onUnitsChanged(boolean metric) {
// We don't care.
return false;
}
@Override
public boolean onReportSpeedChanged(boolean reportSpeed) {
// We don't care.
return false;
}
/**
* Resumes the trackDataHub. Needs to be synchronized because trackDataHub can be
* accessed by multiple threads.
*/
private synchronized void resumeTrackDataHub() {
trackDataHub = ((MyTracksApplication) getActivity().getApplication()).getTrackDataHub();
trackDataHub.registerTrackDataListener(this, EnumSet.of(
TrackDataType.SELECTED_TRACK,
TrackDataType.WAYPOINTS_TABLE,
TrackDataType.TRACK_POINTS_TABLE,
TrackDataType.LOCATION,
TrackDataType.COMPASS));
}
/**
* Pauses the trackDataHub. Needs to be synchronized because trackDataHub can be
* accessed by multiple threads.
*/
private synchronized void pauseTrackDataHub() {
trackDataHub.unregisterTrackDataListener(this);
trackDataHub = null;
}
/**
* Updates the trackDataHub. Needs to be synchronized because trackDataHub can be
* accessed by multiple threads.
*/
private synchronized void updateTrackDataHub() {
if (trackDataHub != null) {
trackDataHub.forceUpdateLocation();
}
}
/**
* Updates the map by either zooming to the requested marker or showing the track.
*
* @param track the track
*/
private void updateMap(Track track) {
if (track.getId() == markerTrackId) {
// Show the marker
showMarker(markerId);
markerTrackId = -1L;
markerId = -1L;
} else {
// Show the track
showTrack(track);
}
}
/**
* Returns true if the location is visible.
*
* @param location the location
*/
private boolean isVisible(Location location) {
if (location == null || mapView == null) {
return false;
}
GeoPoint mapCenter = mapView.getMapCenter();
int latitudeSpan = mapView.getLatitudeSpan();
int longitudeSpan = mapView.getLongitudeSpan();
/*
* The bottom of the mapView is obscured by the zoom controls, subtract its
* height from the visible area.
*/
GeoPoint zoomControlBottom = mapView.getProjection().fromPixels(0, mapView.getHeight());
GeoPoint zoomControlTop = mapView.getProjection().fromPixels(
0, mapView.getHeight() - mapView.getZoomButtonsController().getZoomControls().getHeight());
int zoomControlMargin = Math.abs(zoomControlTop.getLatitudeE6()
- zoomControlBottom.getLatitudeE6());
GeoRect geoRect = new GeoRect(mapCenter, latitudeSpan, longitudeSpan);
geoRect.top += zoomControlMargin;
GeoPoint geoPoint = LocationUtils.getGeoPoint(location);
return geoRect.contains(geoPoint);
}
/**
* Updates the current location and centers it if necessary.
*/
private void updateCurrentLocation() {
if (mapOverlay == null || mapView == null) {
return;
}
mapOverlay.setMyLocation(currentLocation);
mapView.postInvalidate();
if (currentLocation != null && keepMyLocationVisible && !isVisible(currentLocation)) {
GeoPoint geoPoint = LocationUtils.getGeoPoint(currentLocation);
MapController mapController = mapView.getController();
mapController.animateTo(geoPoint);
if (zoomToMyLocation) {
// Only zoom in the first time we show the location.
zoomToMyLocation = false;
if (mapView.getZoomLevel() < mapView.getMaxZoomLevel()) {
mapController.setZoom(mapView.getMaxZoomLevel());
}
}
}
}
/**
* Shows the track.
*
* @param track the track
*/
private void showTrack(Track track) {
if (mapView == null || track == null || track.getNumberOfPoints() < 2) {
return;
}
TripStatistics tripStatistics = track.getTripStatistics();
int bottom = tripStatistics.getBottom();
int left = tripStatistics.getLeft();
int latitudeSpanE6 = tripStatistics.getTop() - bottom;
int longitudeSpanE6 = tripStatistics.getRight() - left;
if (latitudeSpanE6 > 0 && latitudeSpanE6 < 180E6 && longitudeSpanE6 > 0
&& longitudeSpanE6 < 360E6) {
keepMyLocationVisible = false;
GeoPoint center = new GeoPoint(bottom + latitudeSpanE6 / 2, left + longitudeSpanE6 / 2);
if (LocationUtils.isValidGeoPoint(center)) {
mapView.getController().setCenter(center);
mapView.getController().zoomToSpan(latitudeSpanE6, longitudeSpanE6);
}
}
}
}