forked from upstream-mirrors/OpenTracks
Update to map view v2.
This commit is contained in:
@@ -556,7 +556,7 @@ public class ChartView extends View {
|
||||
float x = getX(getWaypointXValue(waypoint));
|
||||
canvas.drawLine(
|
||||
x, topBorder + spacer + markerHeight / 2, x, topBorder + effectiveHeight, markerPaint);
|
||||
canvas.translate(x - (float) (markerWidth * Constants.WAYPOINT_X_OFFSET_PERCENTAGE),
|
||||
canvas.translate(x - (float) (markerWidth * MapOverlay.WAYPOINT_X_ANCHOR),
|
||||
topBorder + spacer);
|
||||
if (waypoints.get(i).getType() == Waypoint.TYPE_STATISTICS) {
|
||||
statisticsMarker.draw(canvas);
|
||||
|
||||
@@ -126,10 +126,6 @@ public abstract class Constants {
|
||||
|
||||
public static final String SETTINGS_NAME = "SettingsActivity";
|
||||
|
||||
public static final double WAYPOINT_X_OFFSET_PERCENTAGE = 13 / 48.0;
|
||||
|
||||
public static final double MARKER_Y_OFFSET_PERCENTAGE = 91 / 96.0;
|
||||
|
||||
/**
|
||||
* This is an abstract utility class.
|
||||
*/
|
||||
|
||||
@@ -19,30 +19,22 @@ package com.google.android.apps.mytracks;
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.maps.TrackPathPainter;
|
||||
import com.google.android.apps.mytracks.maps.TrackPathPainterFactory;
|
||||
import com.google.android.apps.mytracks.maps.TrackPathUtils;
|
||||
import com.google.android.apps.mytracks.util.IntentUtils;
|
||||
import com.google.android.apps.mytracks.maps.TrackPath;
|
||||
import com.google.android.apps.mytracks.maps.TrackPathFactory;
|
||||
import com.google.android.apps.mytracks.util.LocationUtils;
|
||||
import com.google.android.apps.mytracks.util.PreferencesUtils;
|
||||
import com.google.android.apps.mytracks.util.UnitConversions;
|
||||
import com.google.android.maps.GeoPoint;
|
||||
import com.google.android.maps.MapView;
|
||||
import com.google.android.maps.Overlay;
|
||||
import com.google.android.maps.Projection;
|
||||
import com.google.android.gms.maps.GoogleMap;
|
||||
import com.google.android.gms.maps.model.BitmapDescriptors;
|
||||
import com.google.android.gms.maps.model.LatLng;
|
||||
import com.google.android.gms.maps.model.Marker;
|
||||
import com.google.android.gms.maps.model.Model;
|
||||
import com.google.android.gms.maps.model.Polyline;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.location.Location;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -56,41 +48,33 @@ import java.util.concurrent.BlockingQueue;
|
||||
*
|
||||
* @author Leif Hendrik Wilden
|
||||
*/
|
||||
public class MapOverlay extends Overlay {
|
||||
public class MapOverlay {
|
||||
|
||||
public static final float WAYPOINT_X_ANCHOR = 13f / 48f;
|
||||
|
||||
private static final float WAYPOINT_Y_ANCHOR = 43f / 48f;
|
||||
private static final float MARKER_X_ANCHOR = 50f / 96f;
|
||||
private static final float MARKER_Y_ANCHOR = 90f / 96f;
|
||||
private static final int INITIAL_LOCATIONS_SIZE = 1024;
|
||||
|
||||
private final OnSharedPreferenceChangeListener
|
||||
sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
if (PreferencesUtils.getKey(context, R.string.track_color_mode_key).equals(key)) {
|
||||
trackPathPainter = TrackPathPainterFactory.getTrackPathPainter(context);
|
||||
if (key == null
|
||||
|| PreferencesUtils.getKey(context, R.string.track_color_mode_key).equals(key)) {
|
||||
trackPath = TrackPathFactory.getTrackPath(context);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final Context context;
|
||||
private final List<CachedLocation> locations;
|
||||
private final BlockingQueue<CachedLocation> pendingLocations;
|
||||
private final List<Waypoint> waypoints;
|
||||
private final List<CachedLocation> points;
|
||||
private final BlockingQueue<CachedLocation> pendingPoints;
|
||||
private final Drawable[] arrows;
|
||||
private final int arrowWidth;
|
||||
private final int arrowHeight;
|
||||
private final Drawable statsMarker;
|
||||
private final Drawable waypointMarker;
|
||||
private final Drawable startMarker;
|
||||
private final Drawable endMarker;
|
||||
private final int markerWidth;
|
||||
private final int markerHeight;
|
||||
private final Paint errorCirclePaint;
|
||||
private TrackPathPainter trackPathPainter;
|
||||
|
||||
private boolean trackDrawingEnabled;
|
||||
private boolean showEndMarker = true;
|
||||
private int headingIndex = 0;
|
||||
private Location myLocation;
|
||||
|
||||
private GeoPoint lastReferencePoint;
|
||||
private Rect lastViewRect;
|
||||
private TrackPath trackPath;
|
||||
|
||||
/**
|
||||
* A pre-processed {@link Location} to speed up drawing.
|
||||
@@ -100,7 +84,7 @@ public class MapOverlay extends Overlay {
|
||||
public static class CachedLocation {
|
||||
|
||||
private final boolean valid;
|
||||
private final GeoPoint geoPoint;
|
||||
private final LatLng latLng;
|
||||
private final int speed;
|
||||
|
||||
/**
|
||||
@@ -108,7 +92,7 @@ public class MapOverlay extends Overlay {
|
||||
*/
|
||||
public CachedLocation() {
|
||||
this.valid = false;
|
||||
this.geoPoint = null;
|
||||
this.latLng = null;
|
||||
this.speed = -1;
|
||||
}
|
||||
|
||||
@@ -117,7 +101,7 @@ public class MapOverlay extends Overlay {
|
||||
*/
|
||||
public CachedLocation(Location location) {
|
||||
this.valid = LocationUtils.isValidLocation(location);
|
||||
this.geoPoint = valid ? LocationUtils.getGeoPoint(location) : null;
|
||||
this.latLng = valid ? new LatLng(location.getLatitude(), location.getLongitude()) : null;
|
||||
this.speed = (int) Math.floor(location.getSpeed() * UnitConversions.MS_TO_KMH);
|
||||
}
|
||||
|
||||
@@ -128,94 +112,52 @@ public class MapOverlay extends Overlay {
|
||||
return valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link GeoPoint}.
|
||||
*/
|
||||
public GeoPoint getGeoPoint() {
|
||||
return geoPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the speed in kilometers per hour.
|
||||
*/
|
||||
public int getSpeed() {
|
||||
return speed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the LatLng.
|
||||
*/
|
||||
public LatLng getLatLng() {
|
||||
return latLng;
|
||||
}
|
||||
};
|
||||
|
||||
public MapOverlay(Context context) {
|
||||
this.context = context;
|
||||
this.waypoints = new ArrayList<Waypoint>();
|
||||
this.points = new ArrayList<CachedLocation>(1024);
|
||||
this.pendingPoints = new ArrayBlockingQueue<CachedLocation>(
|
||||
this.locations = new ArrayList<CachedLocation>(INITIAL_LOCATIONS_SIZE);
|
||||
this.pendingLocations = new ArrayBlockingQueue<CachedLocation>(
|
||||
Constants.MAX_DISPLAYED_TRACK_POINTS, true);
|
||||
|
||||
/*
|
||||
* TODO: Use animation rather than individual resources for each arrow
|
||||
* direction.
|
||||
*/
|
||||
final Resources resources = context.getResources();
|
||||
arrows = new Drawable[] { resources.getDrawable(R.drawable.arrow_0),
|
||||
resources.getDrawable(R.drawable.arrow_20), resources.getDrawable(R.drawable.arrow_40),
|
||||
resources.getDrawable(R.drawable.arrow_60), resources.getDrawable(R.drawable.arrow_80),
|
||||
resources.getDrawable(R.drawable.arrow_100), resources.getDrawable(R.drawable.arrow_120),
|
||||
resources.getDrawable(R.drawable.arrow_140), resources.getDrawable(R.drawable.arrow_160),
|
||||
resources.getDrawable(R.drawable.arrow_180), resources.getDrawable(R.drawable.arrow_200),
|
||||
resources.getDrawable(R.drawable.arrow_220), resources.getDrawable(R.drawable.arrow_240),
|
||||
resources.getDrawable(R.drawable.arrow_260), resources.getDrawable(R.drawable.arrow_280),
|
||||
resources.getDrawable(R.drawable.arrow_300), resources.getDrawable(R.drawable.arrow_320),
|
||||
resources.getDrawable(R.drawable.arrow_340) };
|
||||
arrowWidth = arrows[headingIndex].getIntrinsicWidth();
|
||||
arrowHeight = arrows[headingIndex].getIntrinsicHeight();
|
||||
for (Drawable arrow : arrows) {
|
||||
arrow.setBounds(0, 0, arrowWidth, arrowHeight);
|
||||
}
|
||||
|
||||
statsMarker = resources.getDrawable(R.drawable.yellow_pushpin);
|
||||
markerWidth = statsMarker.getIntrinsicWidth();
|
||||
markerHeight = statsMarker.getIntrinsicHeight();
|
||||
statsMarker.setBounds(0, 0, markerWidth, markerHeight);
|
||||
|
||||
waypointMarker = resources.getDrawable(R.drawable.blue_pushpin);
|
||||
waypointMarker.setBounds(0, 0, markerWidth, markerHeight);
|
||||
|
||||
startMarker = resources.getDrawable(R.drawable.green_dot);
|
||||
startMarker.setBounds(0, 0, markerWidth, markerHeight);
|
||||
|
||||
endMarker = resources.getDrawable(R.drawable.red_dot);
|
||||
endMarker.setBounds(0, 0, markerWidth, markerHeight);
|
||||
|
||||
errorCirclePaint = TrackPathUtils.getPaint(context, R.color.blue);
|
||||
errorCirclePaint.setAlpha(127);
|
||||
|
||||
trackPathPainter = TrackPathPainterFactory.getTrackPathPainter(context);
|
||||
|
||||
context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE)
|
||||
.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a location to the map overlay.
|
||||
* <p>
|
||||
* NOTE: This method doesn't take ownership of the given location, so it is
|
||||
* safe to reuse the same location while calling this method.
|
||||
* Add a location.
|
||||
*
|
||||
* @param location the location
|
||||
*/
|
||||
public void addLocation(Location location) {
|
||||
// Queue up in the pendingPoints until it's merged with points.
|
||||
if (!pendingPoints.offer(new CachedLocation(location))) {
|
||||
Log.e(TAG, "Unable to add to pendingPoints.");
|
||||
// Queue up in the pendingLocations until it's merged with locations
|
||||
if (!pendingLocations.offer(new CachedLocation(location))) {
|
||||
Log.e(TAG, "Unable to add to pendingLocations.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a segment split to the map overlay.
|
||||
* Adds a segment split.
|
||||
*/
|
||||
public void addSegmentSplit() {
|
||||
// Queue up in the pendingPoints until it's merged with points.
|
||||
if (!pendingPoints.offer(new CachedLocation())) {
|
||||
Log.e(TAG, "Unable to add to pendingPoints");
|
||||
// Queue up in the pendingLocations until it's merged with locations
|
||||
if (!pendingLocations.offer(new CachedLocation())) {
|
||||
Log.e(TAG, "Unable to add to pendingLocations.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,26 +165,20 @@ public class MapOverlay extends Overlay {
|
||||
* Clears the locations.
|
||||
*/
|
||||
public void clearPoints() {
|
||||
synchronized (points) {
|
||||
points.clear();
|
||||
pendingPoints.clear();
|
||||
trackPathPainter.clearPath();
|
||||
lastReferencePoint = null;
|
||||
lastViewRect = null;
|
||||
synchronized (locations) {
|
||||
locations.clear();
|
||||
pendingLocations.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a waypoint to the map overlay.
|
||||
* Adds a waypoint.
|
||||
*
|
||||
* @param waypoint the waypoint
|
||||
*/
|
||||
public void addWaypoint(Waypoint waypoint) {
|
||||
// Note: We don't cache waypoints because it's not worth the effort.
|
||||
if (waypoint != null && waypoint.getLocation() != null) {
|
||||
synchronized (waypoints) {
|
||||
waypoints.add(waypoint);
|
||||
}
|
||||
synchronized (waypoints) {
|
||||
waypoints.add(waypoint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,327 +192,92 @@ public class MapOverlay extends Overlay {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to draw the track or not.
|
||||
* Sets whether to show the end marker.
|
||||
*
|
||||
* @param trackDrawingEnabled true to draw track
|
||||
* @param show true to show the end marker
|
||||
*/
|
||||
public void setTrackDrawingEnabled(boolean trackDrawingEnabled) {
|
||||
this.trackDrawingEnabled = trackDrawingEnabled;
|
||||
public void setShowEndMarker(boolean show) {
|
||||
showEndMarker = show;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to draw the end maker or not.
|
||||
* Updates the track, start and end markers, and waypoints.
|
||||
*
|
||||
* @param showEndMarker true to draw end marker
|
||||
* @param googleMap the google map
|
||||
* @param paths the paths
|
||||
* @param reload true to reload all points
|
||||
*/
|
||||
public void setShowEndMarker(boolean showEndMarker) {
|
||||
this.showEndMarker = showEndMarker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets my location.
|
||||
*
|
||||
* @param myLocation my location
|
||||
*/
|
||||
public void setMyLocation(Location myLocation) {
|
||||
this.myLocation = myLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the heading.
|
||||
*
|
||||
* @param heading the heading
|
||||
* @return true if the visible heading has changed.
|
||||
*/
|
||||
public boolean setHeading(float heading) {
|
||||
/*
|
||||
* Use -heading because the arrow images are counter-clockwise rather than
|
||||
* clockwise.
|
||||
*/
|
||||
int index = Math.round(-heading / 360 * 18);
|
||||
while (index < 0) {
|
||||
index += 18;
|
||||
}
|
||||
while (index > 17) {
|
||||
index -= 18;
|
||||
}
|
||||
if (index != headingIndex) {
|
||||
headingIndex = index;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas, MapView mapView, boolean shadow) {
|
||||
if (shadow) {
|
||||
return;
|
||||
}
|
||||
|
||||
// It's safe to keep projection within a single draw operation
|
||||
Projection projection = getMapProjection(mapView);
|
||||
if (projection == null) {
|
||||
Log.w(TAG, "No projection, unable to draw.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (trackDrawingEnabled) {
|
||||
// Get the current viewing Rect
|
||||
Rect viewRect = getMapViewRect(mapView);
|
||||
|
||||
// Draw the selected track
|
||||
drawTrack(canvas, projection, viewRect);
|
||||
|
||||
// Draw the start and end markers
|
||||
drawMarkers(canvas, projection);
|
||||
|
||||
// Draw the waypoints
|
||||
drawWaypoints(canvas, projection);
|
||||
}
|
||||
|
||||
// Draw the current location
|
||||
drawMyLocation(canvas, projection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTap(GeoPoint geoPoint, MapView mapView) {
|
||||
if (geoPoint.equals(mapView.getMapCenter())) {
|
||||
/*
|
||||
* There is (unfortunately) no good way to determine whether the tap was
|
||||
* caused by an actual tap on the screen or the track ball. If the
|
||||
* location is equal to the map center,then it was a track ball press with
|
||||
* very high likelihood.
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
final Location tapLocation = LocationUtils.getLocation(geoPoint);
|
||||
double minDistance = Double.MAX_VALUE;
|
||||
Waypoint waypoint = null;
|
||||
synchronized (waypoints) {
|
||||
for (int i = 0; i < waypoints.size(); i++) {
|
||||
final Location waypointLocation = waypoints.get(i).getLocation();
|
||||
if (waypointLocation == null) {
|
||||
continue;
|
||||
}
|
||||
final double distance = waypointLocation.distanceTo(tapLocation);
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
waypoint = waypoints.get(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (waypoint != null && minDistance < 15000000 / Math.pow(2, mapView.getZoomLevel())) {
|
||||
Intent intent = IntentUtils.newIntent(context, MarkerDetailActivity.class)
|
||||
.putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, waypoint.getId());
|
||||
context.startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
return super.onTap(geoPoint, mapView);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the points.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public List<CachedLocation> getPoints() {
|
||||
return points;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the track path painter.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public TrackPathPainter getTrackPathPainter() {
|
||||
return trackPathPainter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the track path painter.
|
||||
*
|
||||
* @param trackPathPainter the track path painter
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public void setTrackPathPainter(TrackPathPainter trackPathPainter) {
|
||||
this.trackPathPainter = trackPathPainter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the map view projection.
|
||||
*
|
||||
* @param mapView the map view
|
||||
*/
|
||||
@VisibleForTesting
|
||||
protected Projection getMapProjection(MapView mapView) {
|
||||
return mapView.getProjection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the map view Rect.
|
||||
*
|
||||
* @param mapView the map view
|
||||
*/
|
||||
@VisibleForTesting
|
||||
protected Rect getMapViewRect(MapView mapView) {
|
||||
int width = mapView.getLongitudeSpan();
|
||||
int height = mapView.getLatitudeSpan();
|
||||
int centerX = mapView.getMapCenter().getLongitudeE6();
|
||||
int centerY = mapView.getMapCenter().getLatitudeE6();
|
||||
return new Rect(centerX - width / 2, centerY - height / 2, centerX + width / 2, centerY + height
|
||||
/ 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets number of locations.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
int getNumLocations() {
|
||||
synchronized (points) {
|
||||
return points.size() + pendingPoints.size();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets number of waypoints.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
int getNumWaypoints() {
|
||||
synchronized (waypoints) {
|
||||
return waypoints.size();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the track.
|
||||
*
|
||||
* @param canvas the canvas
|
||||
* @param projection the projection
|
||||
* @param viewRect the view rect
|
||||
*/
|
||||
private void drawTrack(Canvas canvas, Projection projection, Rect viewRect) {
|
||||
boolean draw;
|
||||
|
||||
synchronized (points) {
|
||||
// Merge the pending points with the list of cached locations.
|
||||
GeoPoint referencePoint = projection.fromPixels(0, 0);
|
||||
int newPoints = pendingPoints.drainTo(points);
|
||||
boolean newProjection = !viewRect.equals(lastViewRect)
|
||||
|| !referencePoint.equals(lastReferencePoint);
|
||||
// Call updateState first to trigger its side effects.
|
||||
boolean currentPathValid = !trackPathPainter.updateState() && !newProjection
|
||||
&& trackPathPainter.hasPath();
|
||||
if (newPoints == 0 && currentPathValid) {
|
||||
// No need to update
|
||||
draw = true;
|
||||
public void update(GoogleMap googleMap, ArrayList<Polyline> paths, boolean reload) {
|
||||
synchronized (locations) {
|
||||
// Merge pendingLocations with locations
|
||||
int newLocations = pendingLocations.drainTo(locations);
|
||||
boolean needReload = reload || trackPath.updateState();
|
||||
if (needReload) {
|
||||
googleMap.clear();
|
||||
paths.clear();
|
||||
trackPath.updatePath(googleMap, paths, 0, locations);
|
||||
updateStartAndEndMarkers(googleMap);
|
||||
updateWaypoints(googleMap);
|
||||
} else {
|
||||
int numPoints = points.size();
|
||||
if (numPoints < 2) {
|
||||
// Not enough points to draw a path
|
||||
draw = false;
|
||||
} else if (currentPathValid) {
|
||||
// Incremental update of the path
|
||||
draw = true;
|
||||
trackPathPainter.updatePath(projection, viewRect, numPoints - newPoints, points);
|
||||
} else {
|
||||
// Reload the path
|
||||
draw = true;
|
||||
trackPathPainter.clearPath();
|
||||
trackPathPainter.updatePath(projection, viewRect, 0, points);
|
||||
if (newLocations != 0) {
|
||||
int numLocations = locations.size();
|
||||
trackPath.updatePath(googleMap, paths, numLocations - newLocations, locations);
|
||||
}
|
||||
}
|
||||
lastReferencePoint = referencePoint;
|
||||
lastViewRect = viewRect;
|
||||
}
|
||||
if (draw) {
|
||||
trackPathPainter.drawPath(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the start and end markers.
|
||||
* Updates the start and end markers.
|
||||
*
|
||||
* @param canvas the canvas
|
||||
* @param projection the projection
|
||||
* @param googleMap the google map
|
||||
*/
|
||||
private void drawMarkers(Canvas canvas, Projection projection) {
|
||||
int offsetY = (int) (markerHeight * Constants.MARKER_Y_OFFSET_PERCENTAGE);
|
||||
// Draw the end marker
|
||||
private void updateStartAndEndMarkers(GoogleMap googleMap) {
|
||||
// Add the end marker
|
||||
if (showEndMarker) {
|
||||
for (int i = points.size() - 1; i >= 0; i--) {
|
||||
if (points.get(i).valid) {
|
||||
drawElement(
|
||||
canvas, projection, points.get(i).geoPoint, endMarker, -markerWidth / 2, -offsetY);
|
||||
for (int i = locations.size() - 1; i >= 0; i--) {
|
||||
CachedLocation cachedLocation = locations.get(i);
|
||||
if (cachedLocation.valid) {
|
||||
Marker.Options options = Model.newMarkerOptions().position(cachedLocation.getLatLng())
|
||||
.anchor(MARKER_X_ANCHOR, MARKER_Y_ANCHOR).draggable(false).visible(true)
|
||||
.icon(BitmapDescriptors.fromResource(R.drawable.red_dot));
|
||||
googleMap.addMarker(options);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the start marker
|
||||
for (int i = 0; i < points.size(); i++) {
|
||||
if (points.get(i).valid) {
|
||||
drawElement(
|
||||
canvas, projection, points.get(i).geoPoint, startMarker, -markerWidth / 2, -offsetY);
|
||||
// Add the start marker
|
||||
for (int i = 0; i < locations.size(); i++) {
|
||||
CachedLocation cachedLocation = locations.get(i);
|
||||
if (cachedLocation.valid) {
|
||||
Marker.Options options = Model.newMarkerOptions().position(cachedLocation.getLatLng())
|
||||
.anchor(MARKER_X_ANCHOR, MARKER_Y_ANCHOR).draggable(false).visible(true)
|
||||
.icon(BitmapDescriptors.fromResource(R.drawable.green_dot));
|
||||
googleMap.addMarker(options);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the waypoints.
|
||||
* Updates the waypoints.
|
||||
*
|
||||
* @param canvas the canvas
|
||||
* @param projection the projection
|
||||
* @param googleMap the google map.
|
||||
*/
|
||||
private void drawWaypoints(Canvas canvas, Projection projection) {
|
||||
private void updateWaypoints(GoogleMap googleMap) {
|
||||
synchronized (waypoints) {
|
||||
int offsetX = (int) (markerWidth * Constants.WAYPOINT_X_OFFSET_PERCENTAGE);
|
||||
for (Waypoint waypoint : waypoints) {
|
||||
Location location = waypoint.getLocation();
|
||||
Drawable drawable = waypoint.getType() == Waypoint.TYPE_STATISTICS ? statsMarker
|
||||
: waypointMarker;
|
||||
drawElement(canvas, projection, LocationUtils.getGeoPoint(location), drawable, -offsetX,
|
||||
-markerHeight);
|
||||
LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude());
|
||||
int drawableId = waypoint.getType() == Waypoint.TYPE_STATISTICS ? R.drawable.yellow_pushpin
|
||||
: R.drawable.blue_pushpin;
|
||||
Marker.Options options = Model.newMarkerOptions().position(latLng)
|
||||
.anchor(WAYPOINT_X_ANCHOR, WAYPOINT_Y_ANCHOR).draggable(false).visible(true)
|
||||
.icon(BitmapDescriptors.fromResource(drawableId))
|
||||
.title(String.valueOf(waypoint.getId()));
|
||||
googleMap.addMarker(options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws my location.
|
||||
*
|
||||
* @param canvas the canvas
|
||||
* @param projection the projection
|
||||
*/
|
||||
private void drawMyLocation(Canvas canvas, Projection projection) {
|
||||
if (myLocation == null) {
|
||||
return;
|
||||
}
|
||||
Point point = drawElement(canvas, projection, LocationUtils.getGeoPoint(myLocation),
|
||||
arrows[headingIndex], -(arrowWidth / 2), -(arrowHeight / 2));
|
||||
// Draw the error circle
|
||||
float radius = projection.metersToEquatorPixels(myLocation.getAccuracy());
|
||||
canvas.drawCircle(point.x, point.y, radius, errorCirclePaint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws an element.
|
||||
*
|
||||
* @param canvas the canvas
|
||||
* @param projection the projection
|
||||
* @param geoPoint the geo point
|
||||
* @param drawable the drawable
|
||||
* @param offsetX the x offset
|
||||
* @param offsetY the y offset
|
||||
* @return the point of the drawing.
|
||||
*/
|
||||
private Point drawElement(Canvas canvas, Projection projection, GeoPoint geoPoint,
|
||||
Drawable drawable, int offsetX, int offsetY) {
|
||||
Point point = new Point();
|
||||
projection.toPixels(geoPoint, point);
|
||||
canvas.save();
|
||||
canvas.translate(point.x + offsetX, point.y + offsetY);
|
||||
drawable.draw(canvas);
|
||||
canvas.restore();
|
||||
return point;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import com.google.android.apps.mytracks.fragments.DeleteOneTrackDialogFragment;
|
||||
import com.google.android.apps.mytracks.fragments.DeleteOneTrackDialogFragment.DeleteOneTrackCaller;
|
||||
import com.google.android.apps.mytracks.fragments.FrequencyDialogFragment;
|
||||
import com.google.android.apps.mytracks.fragments.InstallEarthDialogFragment;
|
||||
import com.google.android.apps.mytracks.fragments.MapFragment;
|
||||
import com.google.android.apps.mytracks.fragments.MyTracksMapFragment;
|
||||
import com.google.android.apps.mytracks.fragments.StatsFragment;
|
||||
import com.google.android.apps.mytracks.io.file.SaveActivity;
|
||||
import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat;
|
||||
@@ -36,7 +36,6 @@ import com.google.android.apps.mytracks.io.sendtogoogle.SendRequest;
|
||||
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
|
||||
import com.google.android.apps.mytracks.settings.SettingsActivity;
|
||||
import com.google.android.apps.mytracks.util.AnalyticsUtils;
|
||||
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
|
||||
import com.google.android.apps.mytracks.util.IntentUtils;
|
||||
import com.google.android.apps.mytracks.util.PreferencesUtils;
|
||||
import com.google.android.apps.mytracks.util.TrackRecordingServiceConnectionUtils;
|
||||
@@ -63,7 +62,7 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* An activity to show the track detail.
|
||||
*
|
||||
*
|
||||
* @author Leif Hendrik Wilden
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
@@ -74,19 +73,18 @@ public class TrackDetailActivity extends AbstractMyTracksActivity implements Del
|
||||
|
||||
private static final String TAG = TrackDetailActivity.class.getSimpleName();
|
||||
private static final String CURRENT_TAB_TAG_KEY = "current_tab_tag_key";
|
||||
|
||||
|
||||
// The following are set in onCreate
|
||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||
private TrackDataHub trackDataHub;
|
||||
private View mapViewContainer;
|
||||
private TabHost tabHost;
|
||||
private TabManager tabManager;
|
||||
private TrackController trackController;
|
||||
|
||||
|
||||
// From intent
|
||||
private long trackId;
|
||||
private long markerId;
|
||||
|
||||
|
||||
// Preferences
|
||||
private long recordingTrackId;
|
||||
private boolean recordingTrackPaused;
|
||||
@@ -136,7 +134,7 @@ public class TrackDetailActivity extends AbstractMyTracksActivity implements Del
|
||||
if (key != null) {
|
||||
boolean isRecording = trackId == recordingTrackId;
|
||||
updateMenuItems(isRecording, recordingTrackPaused);
|
||||
trackController.update(isRecording, recordingTrackPaused);
|
||||
trackController.update(isRecording, recordingTrackPaused);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -170,25 +168,6 @@ public class TrackDetailActivity extends AbstractMyTracksActivity implements Del
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* We are not displaying driving directions. Just an arbitrary track that is
|
||||
* not associated to any licensed mapping data. Therefore it should be okay to
|
||||
* return false here and still comply with the terms of service.
|
||||
*/
|
||||
@Override
|
||||
protected boolean isRouteDisplayed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* We are displaying a location. This needs to return true in order to comply
|
||||
* with the terms of service.
|
||||
*/
|
||||
@Override
|
||||
protected boolean isLocationDisplayed() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -199,18 +178,16 @@ public class TrackDetailActivity extends AbstractMyTracksActivity implements Del
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
|
||||
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, bindChangedCallback);
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(
|
||||
this, bindChangedCallback);
|
||||
trackDataHub = TrackDataHub.newInstance(this);
|
||||
|
||||
mapViewContainer = getLayoutInflater().inflate(R.layout.map, null);
|
||||
ApiAdapterFactory.getApiAdapter().disableHardwareAccelerated(mapViewContainer);
|
||||
|
||||
tabHost = (TabHost) findViewById(android.R.id.tabhost);
|
||||
tabHost.setup();
|
||||
tabManager = new TabManager(this, tabHost, R.id.realtabcontent);
|
||||
TabSpec mapTabSpec = tabHost.newTabSpec(MapFragment.MAP_FRAGMENT_TAG).setIndicator(
|
||||
TabSpec mapTabSpec = tabHost.newTabSpec(MyTracksMapFragment.MAP_FRAGMENT_TAG).setIndicator(
|
||||
getString(R.string.track_detail_map_tab), getResources().getDrawable(R.drawable.tab_map));
|
||||
tabManager.addTab(mapTabSpec, MapFragment.class, null);
|
||||
tabManager.addTab(mapTabSpec, MyTracksMapFragment.class, null);
|
||||
TabSpec chartTabSpec = tabHost.newTabSpec(ChartFragment.CHART_FRAGMENT_TAG).setIndicator(
|
||||
getString(R.string.track_detail_chart_tab),
|
||||
getResources().getDrawable(R.drawable.tab_chart));
|
||||
@@ -222,7 +199,8 @@ public class TrackDetailActivity extends AbstractMyTracksActivity implements Del
|
||||
if (savedInstanceState != null) {
|
||||
tabHost.setCurrentTabByTag(savedInstanceState.getString(CURRENT_TAB_TAG_KEY));
|
||||
}
|
||||
trackController = new TrackController(this, trackRecordingServiceConnection, false, recordListener, stopListener);
|
||||
trackController = new TrackController(
|
||||
this, trackRecordingServiceConnection, false, recordListener, stopListener);
|
||||
showMarker();
|
||||
}
|
||||
|
||||
@@ -274,7 +252,7 @@ public class TrackDetailActivity extends AbstractMyTracksActivity implements Del
|
||||
super.onDestroy();
|
||||
trackRecordingServiceConnection.unbind();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onHomeSelected() {
|
||||
Intent intent = IntentUtils.newIntent(this, TrackListActivity.class);
|
||||
@@ -427,13 +405,6 @@ public class TrackDetailActivity extends AbstractMyTracksActivity implements Del
|
||||
return trackRecordingServiceConnection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the map view container.
|
||||
*/
|
||||
public View getMapViewContainer() {
|
||||
return mapViewContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link TrackDataHub}.
|
||||
*/
|
||||
@@ -475,10 +446,10 @@ public class TrackDetailActivity extends AbstractMyTracksActivity implements Del
|
||||
*/
|
||||
private void showMarker() {
|
||||
if (markerId != -1L) {
|
||||
MapFragment mapFragmet = (MapFragment) getSupportFragmentManager()
|
||||
.findFragmentByTag(MapFragment.MAP_FRAGMENT_TAG);
|
||||
MyTracksMapFragment mapFragmet = (MyTracksMapFragment) getSupportFragmentManager()
|
||||
.findFragmentByTag(MyTracksMapFragment.MAP_FRAGMENT_TAG);
|
||||
if (mapFragmet != null) {
|
||||
tabHost.setCurrentTabByTag(MapFragment.MAP_FRAGMENT_TAG);
|
||||
tabHost.setCurrentTabByTag(MyTracksMapFragment.MAP_FRAGMENT_TAG);
|
||||
mapFragmet.showMarker(trackId, markerId);
|
||||
} else {
|
||||
Log.e(TAG, "MapFragment is null");
|
||||
@@ -488,7 +459,7 @@ public class TrackDetailActivity extends AbstractMyTracksActivity implements Del
|
||||
|
||||
/**
|
||||
* Updates the menu items.
|
||||
*
|
||||
*
|
||||
* @param isRecording true if recording
|
||||
*/
|
||||
private void updateMenuItems(boolean isRecording, boolean isPaused) {
|
||||
@@ -516,8 +487,7 @@ public class TrackDetailActivity extends AbstractMyTracksActivity implements Del
|
||||
|
||||
String title;
|
||||
if (isRecording) {
|
||||
title = getString(
|
||||
isPaused ? R.string.generic_paused : R.string.generic_recording);
|
||||
title = getString(isPaused ? R.string.generic_paused : R.string.generic_recording);
|
||||
} else {
|
||||
Track track = MyTracksProviderUtils.Factory.get(this).getTrack(trackId);
|
||||
title = track != null ? track.getName() : getString(R.string.my_tracks_app_name);
|
||||
@@ -527,7 +497,7 @@ public class TrackDetailActivity extends AbstractMyTracksActivity implements Del
|
||||
|
||||
/**
|
||||
* Starts the {@link SaveActivity} to save a track.
|
||||
*
|
||||
*
|
||||
* @param trackFileFormat the track file format
|
||||
*/
|
||||
private void startSaveActivity(TrackFileFormat trackFileFormat) {
|
||||
|
||||
@@ -1,594 +0,0 @@
|
||||
/*
|
||||
* 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.TrackDetailActivity;
|
||||
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.TrackDataType;
|
||||
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.GoogleLocationUtils;
|
||||
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 CURRENT_LOCATION_KEY = "current_location_key";
|
||||
private static final String KEEP_MY_LOCATION_VISIBLE_KEY = "keep_my_location_visible_key";
|
||||
|
||||
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(KEEP_MY_LOCATION_VISIBLE_KEY, false);
|
||||
currentLocation = (Location) savedInstanceState.getParcelable(CURRENT_LOCATION_KEY);
|
||||
if (currentLocation != null) {
|
||||
updateCurrentLocation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
resumeTrackDataHub();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putBoolean(KEEP_MY_LOCATION_VISIBLE_KEY, keepMyLocationVisible);
|
||||
if (currentLocation != null) {
|
||||
outState.putParcelable(CURRENT_LOCATION_KEY, 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 = GoogleLocationUtils.isAvailable(getActivity()) ? new Intent(
|
||||
GoogleLocationUtils.ACTION_GOOGLE_LOCATION_SETTINGS)
|
||||
: new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationStateChanged(LocationState state) {
|
||||
if (isResumed()) {
|
||||
final String message;
|
||||
final boolean isGpsDisabled;
|
||||
if (!isSelectedTrackRecording()) {
|
||||
message = null;
|
||||
isGpsDisabled = false;
|
||||
} else {
|
||||
switch (state) {
|
||||
case DISABLED:
|
||||
String setting = getString(
|
||||
GoogleLocationUtils.isAvailable(getActivity()) ? R.string.gps_google_location_settings
|
||||
: R.string.gps_location_access);
|
||||
message = getString(R.string.gps_disabled, setting);
|
||||
isGpsDisabled = true;
|
||||
currentLocation = null;
|
||||
updateCurrentLocation();
|
||||
break;
|
||||
case NO_FIX:
|
||||
message = getString(R.string.gps_wait_for_signal);
|
||||
isGpsDisabled = false;
|
||||
break;
|
||||
case BAD_FIX:
|
||||
message = getString(R.string.gps_wait_for_better_signal);
|
||||
isGpsDisabled = false;
|
||||
break;
|
||||
case GOOD_FIX:
|
||||
message = null;
|
||||
isGpsDisabled = false;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unexpected state: " + state);
|
||||
}
|
||||
}
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isResumed()) {
|
||||
if (message == null) {
|
||||
messageTextView.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
messageTextView.setText(message);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationChanged(Location location) {
|
||||
if (isResumed()) {
|
||||
currentLocation = location;
|
||||
updateCurrentLocation();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHeadingChanged(double heading) {
|
||||
if (isResumed()) {
|
||||
if (mapOverlay.setHeading((float) heading)) {
|
||||
mapView.postInvalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectedTrackChanged(final Track track) {
|
||||
if (isResumed()) {
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isResumed()) {
|
||||
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(!isSelectedTrackRecording());
|
||||
}
|
||||
mapView.invalidate();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrackUpdated(Track track) {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearTrackPoints() {
|
||||
if (isResumed()) {
|
||||
mapOverlay.clearPoints();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledInTrackPoint(Location location) {
|
||||
if (isResumed()) {
|
||||
mapOverlay.addLocation(location);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledOutTrackPoint(Location location) {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSegmentSplit(Location location) {
|
||||
if (isResumed()) {
|
||||
mapOverlay.addSegmentSplit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewTrackPointsDone() {
|
||||
if (isResumed()) {
|
||||
mapView.postInvalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearWaypoints() {
|
||||
if (isResumed()) {
|
||||
mapOverlay.clearWaypoints();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewWaypoint(Waypoint waypoint) {
|
||||
if (isResumed() && waypoint != null && LocationUtils.isValidLocation(waypoint.getLocation())) {
|
||||
// TODO: Optimize locking inside addWaypoint
|
||||
mapOverlay.addWaypoint(waypoint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewWaypointsDone() {
|
||||
if (isResumed()) {
|
||||
mapView.postInvalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumes the trackDataHub. Needs to be synchronized because 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, TrackDataType.HEADING));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the selected track is recording. Needs to be synchronized
|
||||
* because trackDataHub can be accessed by multiple threads.
|
||||
*/
|
||||
private synchronized boolean isSelectedTrackRecording() {
|
||||
return trackDataHub != null && trackDataHub.isSelectedTrackRecording();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,667 @@
|
||||
/*
|
||||
* 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.MarkerDetailActivity;
|
||||
import com.google.android.apps.mytracks.TrackDetailActivity;
|
||||
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.TrackDataType;
|
||||
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.GoogleLocationUtils;
|
||||
import com.google.android.apps.mytracks.util.IntentUtils;
|
||||
import com.google.android.apps.mytracks.util.LocationUtils;
|
||||
import com.google.android.gms.maps.CameraUpdate;
|
||||
import com.google.android.gms.maps.CameraUpdates;
|
||||
import com.google.android.gms.maps.GoogleMap;
|
||||
import com.google.android.gms.maps.LocationSource;
|
||||
import com.google.android.gms.maps.LocationSource.OnLocationChangedListener;
|
||||
import com.google.android.gms.maps.OnMapInitializeListener;
|
||||
import com.google.android.gms.maps.SupportMapFragment;
|
||||
import com.google.android.gms.maps.model.CameraPosition;
|
||||
import com.google.android.gms.maps.model.LatLng;
|
||||
import com.google.android.gms.maps.model.LatLngBounds;
|
||||
import com.google.android.gms.maps.model.Marker;
|
||||
import com.google.android.gms.maps.model.Model;
|
||||
import com.google.android.gms.maps.model.Polyline;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.location.Location;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* A fragment to display map to the user.
|
||||
*
|
||||
* @author Leif Hendrik Wilden
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class MyTracksMapFragment extends SupportMapFragment
|
||||
implements OnMapInitializeListener, TrackDataListener {
|
||||
|
||||
public static final String MAP_FRAGMENT_TAG = "mapFragment";
|
||||
|
||||
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 float DEFAULT_ZOOM_LEVEL = 18f;
|
||||
|
||||
// If current zoom level is above this, no need to zoom to DEFAULT_ZOOM_LEVEL
|
||||
private static final float MIN_ALLOWED_ZOOM_LEVEL = 10f;
|
||||
|
||||
// Google's latitude and longitude
|
||||
private static final double DEFAULT_LATITUDE = 37.423;
|
||||
private static final double DEFAULT_LONGITUDE = -122.084;
|
||||
|
||||
private TrackDataHub trackDataHub;
|
||||
|
||||
// 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 OnLocationChangedListener onLocationChangedListener;
|
||||
|
||||
// For showing a marker
|
||||
private long markerTrackId = -1L;
|
||||
private long markerId = -1L;
|
||||
|
||||
// Current track
|
||||
private Track currentTrack;
|
||||
|
||||
// Current paths
|
||||
private ArrayList<Polyline> paths = new ArrayList<Polyline>();
|
||||
boolean reloadPaths = true;
|
||||
|
||||
// UI elements
|
||||
private GoogleMap googleMap;
|
||||
private MapOverlay mapOverlay;
|
||||
private ImageButton myLocationImageButton;
|
||||
private TextView messageTextView;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
setHasOptionsMenu(true);
|
||||
ApiAdapterFactory.getApiAdapter().invalidMenu(getActivity());
|
||||
mapOverlay = new MapOverlay(getActivity());
|
||||
setOnMapInitializeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(
|
||||
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = super.onCreateView(inflater, container, savedInstanceState);
|
||||
View layout = inflater.inflate(R.layout.map, container, false);
|
||||
RelativeLayout mapContainer = (RelativeLayout) layout.findViewById(R.id.map_container);
|
||||
mapContainer.addView(view, 0);
|
||||
|
||||
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);
|
||||
return layout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMapInitialize(GoogleMap map) {
|
||||
googleMap = map;
|
||||
googleMap.setMyLocationEnabled(true);
|
||||
googleMap.getUiSettings().setMyLocationButtonEnabled(false);
|
||||
googleMap.setIndoorEnabled(true);
|
||||
googleMap.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;
|
||||
}
|
||||
});
|
||||
googleMap.setLocationSource(new LocationSource() {
|
||||
|
||||
@Override
|
||||
public void activate(OnLocationChangedListener listener) {
|
||||
onLocationChangedListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
onLocationChangedListener = null;
|
||||
}
|
||||
});
|
||||
googleMap.setOnCameraChangeListener(new OnCameraChangeListener() {
|
||||
|
||||
@Override
|
||||
public void onCameraChange(CameraPosition cameraPosition) {
|
||||
if (keepCurrentLocationVisible && currentLocation != null
|
||||
&& !isLocationVisible(currentLocation)) {
|
||||
keepCurrentLocationVisible = false;
|
||||
zoomToCurrentLocation = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
LatLng latLng = getDefaultLatLng();
|
||||
if (latLng != null) {
|
||||
googleMap.moveCamera(CameraUpdates.newLatLngZoom(latLng, googleMap.getMinZoomLevel()));
|
||||
}
|
||||
if (isResumed()) {
|
||||
resetTrackDataHub();
|
||||
}
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
resumeTrackDataHub();
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
pauseTrackDataHub();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 boolean onOptionsItemSelected(MenuItem menuItem) {
|
||||
int type = MAP_TYPE_NORMAL;
|
||||
switch (menuItem.getItemId()) {
|
||||
case R.id.menu_map:
|
||||
type = MAP_TYPE_NORMAL;
|
||||
break;
|
||||
case R.id.menu_satellite:
|
||||
type = MAP_TYPE_SATELLITE;
|
||||
break;
|
||||
case R.id.menu_terrain:
|
||||
type = MAP_TYPE_TERRAIN;
|
||||
break;
|
||||
default:
|
||||
return super.onOptionsItemSelected(menuItem);
|
||||
}
|
||||
if (googleMap != null) {
|
||||
googleMap.setMapType(type);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationStateChanged(final LocationState state) {
|
||||
if (isResumed()) {
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!isResumed() || googleMap == null) {
|
||||
return;
|
||||
}
|
||||
boolean myLocationEnabled = true;
|
||||
if (state == LocationState.DISABLED) {
|
||||
currentLocation = null;
|
||||
myLocationEnabled = false;
|
||||
}
|
||||
googleMap.setMyLocationEnabled(myLocationEnabled);
|
||||
|
||||
String message;
|
||||
boolean isGpsDisabled;
|
||||
if (!isSelectedTrackRecording()) {
|
||||
message = null;
|
||||
isGpsDisabled = false;
|
||||
} else {
|
||||
switch (state) {
|
||||
case DISABLED:
|
||||
String setting = getString(GoogleLocationUtils.isAvailable(getActivity())
|
||||
? R.string.gps_google_location_settings
|
||||
: R.string.gps_location_access);
|
||||
message = getString(R.string.gps_disabled, setting);
|
||||
isGpsDisabled = true;
|
||||
break;
|
||||
case NO_FIX:
|
||||
message = getString(R.string.gps_wait_for_signal);
|
||||
isGpsDisabled = false;
|
||||
break;
|
||||
case BAD_FIX:
|
||||
message = getString(R.string.gps_wait_for_better_signal);
|
||||
isGpsDisabled = false;
|
||||
break;
|
||||
case GOOD_FIX:
|
||||
message = null;
|
||||
isGpsDisabled = false;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unexpected state: " + state);
|
||||
}
|
||||
}
|
||||
if (message == null) {
|
||||
messageTextView.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
messageTextView.setText(message);
|
||||
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(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = GoogleLocationUtils.isAvailable(getActivity()) ? new Intent(
|
||||
GoogleLocationUtils.ACTION_GOOGLE_LOCATION_SETTINGS)
|
||||
: new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
messageTextView.setOnClickListener(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationChanged(final Location location) {
|
||||
if (isResumed()) {
|
||||
if (isSelectedTrackRecording() && currentLocation == null && location != null) {
|
||||
zoomToCurrentLocation = true;
|
||||
}
|
||||
currentLocation = location;
|
||||
updateCurrentLocation();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHeadingChanged(double heading) {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrackUpdated(Track track) {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearTrackPoints() {
|
||||
if (isResumed()) {
|
||||
mapOverlay.clearPoints();
|
||||
reloadPaths = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledInTrackPoint(final Location location) {
|
||||
if (isResumed()) {
|
||||
mapOverlay.addLocation(location);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledOutTrackPoint(Location location) {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSegmentSplit(Location location) {
|
||||
if (isResumed()) {
|
||||
mapOverlay.addSegmentSplit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewTrackPointsDone() {
|
||||
if (isResumed()) {
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
if (isResumed() && googleMap != null) {
|
||||
mapOverlay.update(googleMap, paths, reloadPaths);
|
||||
reloadPaths = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearWaypoints() {
|
||||
if (isResumed()) {
|
||||
mapOverlay.clearWaypoints();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewWaypoint(Waypoint waypoint) {
|
||||
if (isResumed() && waypoint != null && LocationUtils.isValidLocation(waypoint.getLocation())) {
|
||||
mapOverlay.addWaypoint(waypoint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewWaypointsDone() {
|
||||
if (isResumed()) {
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
if (isResumed() && googleMap != null) {
|
||||
mapOverlay.update(googleMap, 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, TrackDataType.HEADING));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the trackDataHub. Needs to be synchronized because the trackDataHub
|
||||
* can be accessed by multiple threads.
|
||||
*/
|
||||
private synchronized void resetTrackDataHub() {
|
||||
pauseTrackDataHub();
|
||||
resumeTrackDataHub();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the current location and zoom to it if necessary.
|
||||
*/
|
||||
private void updateCurrentLocation() {
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
if (!isResumed() || googleMap == null || onLocationChangedListener == null
|
||||
|| currentLocation == null) {
|
||||
return;
|
||||
}
|
||||
onLocationChangedListener.onLocationChanged(currentLocation);
|
||||
if (zoomToCurrentLocation
|
||||
|| (keepCurrentLocationVisible && !isLocationVisible(currentLocation))) {
|
||||
LatLng latLng = new LatLng(currentLocation.getLatitude(), currentLocation.getLongitude());
|
||||
googleMap.animateCamera(CameraUpdates.newLatLngZoom(latLng, getDefaultZoomLevel()));
|
||||
zoomToCurrentLocation = false;
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the camera over a track.
|
||||
*/
|
||||
private void showTrack() {
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!isResumed() || googleMap == null || currentTrack == null
|
||||
|| currentTrack.getNumberOfPoints() < 2) {
|
||||
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) {
|
||||
LatLng southWest = new LatLng(
|
||||
tripStatistics.getBottomDegrees(), tripStatistics.getLeftDegrees());
|
||||
LatLng northEast = new LatLng(
|
||||
tripStatistics.getTopDegrees(), tripStatistics.getRightDegrees());
|
||||
LatLngBounds bounds = Model.newLatLngBounds(southWest, northEast);
|
||||
CameraUpdate cameraUpdate = CameraUpdates.newLatLngBounds(bounds, 32);
|
||||
googleMap.moveCamera(cameraUpdate);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() || googleMap == null) {
|
||||
return;
|
||||
}
|
||||
MyTracksProviderUtils MyTracksProviderUtils = Factory.get(getActivity());
|
||||
Waypoint waypoint = MyTracksProviderUtils.getWaypoint(id);
|
||||
if (waypoint != null && waypoint.getLocation() != null) {
|
||||
Location location = waypoint.getLocation();
|
||||
LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude());
|
||||
keepCurrentLocationVisible = false;
|
||||
zoomToCurrentLocation = false;
|
||||
CameraUpdate cameraUpdate = CameraUpdates.newLatLngZoom(latLng, getDefaultZoomLevel());
|
||||
googleMap.moveCamera(cameraUpdate);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default LatLng.
|
||||
*/
|
||||
private LatLng getDefaultLatLng() {
|
||||
MyTracksProviderUtils myTracksProviderUtils = MyTracksProviderUtils.Factory.get(getActivity());
|
||||
Location location = myTracksProviderUtils.getLastValidTrackPoint();
|
||||
if (location != null) {
|
||||
return new LatLng(location.getLatitude(), location.getLongitude());
|
||||
} else {
|
||||
return new LatLng(DEFAULT_LATITUDE, DEFAULT_LONGITUDE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default zoom level. Needs to run on the UI thread.
|
||||
*/
|
||||
private float getDefaultZoomLevel() {
|
||||
if (googleMap == null) {
|
||||
return DEFAULT_ZOOM_LEVEL;
|
||||
}
|
||||
float zoom = googleMap.getCameraPosition().getZoom();
|
||||
if (zoom <= MIN_ALLOWED_ZOOM_LEVEL) {
|
||||
return DEFAULT_ZOOM_LEVEL;
|
||||
}
|
||||
return zoom;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 || googleMap == null) {
|
||||
return false;
|
||||
}
|
||||
LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude());
|
||||
return googleMap.getProjection().getVisibleRegion().getLatLngBounds().contains(latLng);
|
||||
}
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
/*
|
||||
* Copyright 2011 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.maps;
|
||||
|
||||
import com.google.android.apps.mytracks.ColoredPath;
|
||||
import com.google.android.apps.mytracks.MapOverlay.CachedLocation;
|
||||
import com.google.android.maps.GeoPoint;
|
||||
import com.google.android.maps.Projection;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A path painter that varies the path colors based on fixed speeds or average
|
||||
* speed margin depending of the TrackPathDescriptor passed to its constructor.
|
||||
*
|
||||
* @author Vangelis S.
|
||||
*/
|
||||
public class DynamicSpeedTrackPathPainter implements TrackPathPainter {
|
||||
private final TrackPathDescriptor trackPathDescriptor;
|
||||
private final Paint slowPaint;
|
||||
private final Paint mediumPaint;
|
||||
private final Paint fastPaint;
|
||||
private final List<ColoredPath> coloredPaths;
|
||||
|
||||
public DynamicSpeedTrackPathPainter(Context context, TrackPathDescriptor trackPathDescriptor) {
|
||||
this.trackPathDescriptor = trackPathDescriptor;
|
||||
slowPaint = TrackPathUtils.getPaint(context, R.color.slow_path);
|
||||
mediumPaint = TrackPathUtils.getPaint(context, R.color.normal_path);
|
||||
fastPaint = TrackPathUtils.getPaint(context, R.color.fast_path);
|
||||
coloredPaths = new ArrayList<ColoredPath>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPath() {
|
||||
return !coloredPaths.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateState() {
|
||||
return trackPathDescriptor.updateState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePath(
|
||||
Projection projection, Rect viewRect, int startIndex, List<CachedLocation> points) {
|
||||
boolean hasLastPoint = startIndex != 0 && points.get(startIndex -1).isValid();
|
||||
Point point = new Point();
|
||||
if (hasLastPoint) {
|
||||
GeoPoint geoPoint = points.get(startIndex -1).getGeoPoint();
|
||||
projection.toPixels(geoPoint, point);
|
||||
}
|
||||
boolean newSegment = !hasLastPoint;
|
||||
// Assume if last point exists, it is visible
|
||||
boolean lastPointVisible = hasLastPoint;
|
||||
int slowSpeed = trackPathDescriptor.getSlowSpeed();
|
||||
int normalSpeed = trackPathDescriptor.getNormalSpeed();
|
||||
|
||||
for (int i = startIndex; i < points.size(); ++i) {
|
||||
CachedLocation cachedLocation = points.get(i);
|
||||
|
||||
// If not valid, start a new segment
|
||||
if (!cachedLocation.isValid()) {
|
||||
newSegment = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
GeoPoint geoPoint = cachedLocation.getGeoPoint();
|
||||
// Check if this breaks the existing segment.
|
||||
boolean pointVisible = viewRect.contains(geoPoint.getLongitudeE6(), geoPoint.getLatitudeE6());
|
||||
if (!pointVisible && !lastPointVisible) {
|
||||
// This point and the last point are both outside visible area.
|
||||
newSegment = true;
|
||||
}
|
||||
lastPointVisible = pointVisible;
|
||||
|
||||
// Either update point or draw a line from the last point
|
||||
if (newSegment) {
|
||||
projection.toPixels(geoPoint, point);
|
||||
newSegment = false;
|
||||
} else {
|
||||
ColoredPath coloredPath;
|
||||
if (cachedLocation.getSpeed() <= slowSpeed) {
|
||||
coloredPath = new ColoredPath(slowPaint);
|
||||
} else if (cachedLocation.getSpeed() <= normalSpeed) {
|
||||
coloredPath = new ColoredPath(mediumPaint);
|
||||
} else {
|
||||
coloredPath = new ColoredPath(fastPaint);
|
||||
}
|
||||
coloredPath.getPath().moveTo(point.x, point.y);
|
||||
projection.toPixels(geoPoint, point);
|
||||
coloredPath.getPath().lineTo(point.x, point.y);
|
||||
coloredPaths.add(coloredPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearPath() {
|
||||
coloredPaths.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawPath(Canvas canvas) {
|
||||
for (int i = 0; i < coloredPaths.size(); i++) {
|
||||
ColoredPath coloredPath = coloredPaths.get(i);
|
||||
canvas.drawPath(coloredPath.getPath(), coloredPath.getPathPaint());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the colored paths.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
List<ColoredPath> getColoredPaths() {
|
||||
return coloredPaths;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright 2011 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.maps;
|
||||
|
||||
import com.google.android.apps.mytracks.MapOverlay.CachedLocation;
|
||||
import com.google.android.gms.maps.GoogleMap;
|
||||
import com.google.android.gms.maps.model.LatLng;
|
||||
import com.google.android.gms.maps.model.Polyline;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A path painter that varies the path colors based on fixed speeds or average
|
||||
* speed margin depending of the TrackPathDescriptor passed to its constructor.
|
||||
*
|
||||
* @author Vangelis S.
|
||||
*/
|
||||
public class MultiColorTrackPath implements TrackPath {
|
||||
private final TrackPathDescriptor trackPathDescriptor;
|
||||
private final int slowColor;
|
||||
private final int normalColor;
|
||||
private final int fastColor;
|
||||
|
||||
public MultiColorTrackPath(Context context, TrackPathDescriptor trackPathDescriptor) {
|
||||
this.trackPathDescriptor = trackPathDescriptor;
|
||||
slowColor = context.getResources().getColor(R.color.slow_path);
|
||||
normalColor = context.getResources().getColor(R.color.normal_path);
|
||||
fastColor = context.getResources().getColor(R.color.fast_path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateState() {
|
||||
return trackPathDescriptor.updateState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePath(GoogleMap googleMap, ArrayList<Polyline> paths, int startIndex,
|
||||
List<CachedLocation> locations) {
|
||||
if (googleMap == null) {
|
||||
return;
|
||||
}
|
||||
if (startIndex >= locations.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean newSegment = startIndex == 0 || !locations.get(startIndex - 1).isValid();
|
||||
LatLng lastLatLng = startIndex != 0 ? locations.get(startIndex -1).getLatLng() : null;
|
||||
|
||||
ArrayList<LatLng> lastSegmentPoints = new ArrayList<LatLng>();
|
||||
int lastSegmentColor = paths.size() != 0 ? paths.get(paths.size() - 1).getColor() : slowColor;
|
||||
boolean useLastPolyline = true;
|
||||
|
||||
for (int i = startIndex; i < locations.size(); ++i) {
|
||||
CachedLocation cachedLocation = locations.get(i);
|
||||
|
||||
// If not valid, start a new segment
|
||||
if (!cachedLocation.isValid()) {
|
||||
newSegment = true;
|
||||
lastLatLng = null;
|
||||
continue;
|
||||
}
|
||||
LatLng latLng = cachedLocation.getLatLng();
|
||||
int color = getColor(cachedLocation.getSpeed());
|
||||
|
||||
// Either update point or draw a line from the last point
|
||||
if (newSegment) {
|
||||
TrackPathUtils.addPath(googleMap, paths, lastSegmentPoints, lastSegmentColor, useLastPolyline);
|
||||
useLastPolyline = false;
|
||||
lastSegmentColor = color;
|
||||
newSegment = false;
|
||||
}
|
||||
if (lastSegmentColor == color) {
|
||||
lastSegmentPoints.add(latLng);
|
||||
} else {
|
||||
TrackPathUtils.addPath(googleMap, paths, lastSegmentPoints, lastSegmentColor, useLastPolyline);
|
||||
useLastPolyline = false;
|
||||
if (lastLatLng != null) {
|
||||
lastSegmentPoints.add(lastLatLng);
|
||||
}
|
||||
lastSegmentPoints.add(latLng);
|
||||
lastSegmentColor = color;
|
||||
}
|
||||
lastLatLng = latLng;
|
||||
}
|
||||
TrackPathUtils.addPath(googleMap, paths, lastSegmentPoints, lastSegmentColor, useLastPolyline);
|
||||
}
|
||||
|
||||
private int getColor(int speed) {
|
||||
if (speed <= trackPathDescriptor.getSlowSpeed()) {
|
||||
return slowColor;
|
||||
} else if (speed <= trackPathDescriptor.getNormalSpeed()) {
|
||||
return normalColor;
|
||||
} else {
|
||||
return fastColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2012 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.maps;
|
||||
|
||||
import com.google.android.apps.mytracks.MapOverlay.CachedLocation;
|
||||
import com.google.android.gms.maps.GoogleMap;
|
||||
import com.google.android.gms.maps.model.LatLng;
|
||||
import com.google.android.gms.maps.model.Polyline;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A single color track path.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class SingleColorTrackPath implements TrackPath {
|
||||
|
||||
final int color;
|
||||
|
||||
public SingleColorTrackPath(Context context) {
|
||||
color = context.getResources().getColor(R.color.fast_path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateState() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePath(GoogleMap googleMap, ArrayList<Polyline> paths, int startIndex,
|
||||
List<CachedLocation> locations) {
|
||||
if (googleMap == null) {
|
||||
return;
|
||||
}
|
||||
if (startIndex >= locations.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean newSegment = startIndex == 0 || !locations.get(startIndex - 1).isValid();
|
||||
ArrayList<LatLng> lastSegmentPoints = new ArrayList<LatLng>();
|
||||
boolean useLastPolyline = true;
|
||||
for (int i = startIndex; i < locations.size(); i++) {
|
||||
CachedLocation cachedLocation = locations.get(i);
|
||||
|
||||
// If not valid, start a new segment
|
||||
if (!cachedLocation.isValid()) {
|
||||
newSegment = true;
|
||||
continue;
|
||||
}
|
||||
LatLng latLng = cachedLocation.getLatLng();
|
||||
if (newSegment) {
|
||||
TrackPathUtils.addPath(googleMap, paths, lastSegmentPoints, color, useLastPolyline);
|
||||
useLastPolyline = false;
|
||||
newSegment = false;
|
||||
}
|
||||
lastSegmentPoints.add(latLng);
|
||||
}
|
||||
TrackPathUtils.addPath(googleMap, paths, lastSegmentPoints, color, useLastPolyline);
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
/*
|
||||
* Copyright 2011 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.maps;
|
||||
|
||||
import com.google.android.apps.mytracks.MapOverlay.CachedLocation;
|
||||
import com.google.android.maps.GeoPoint;
|
||||
import com.google.android.maps.Projection;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A path painter that not variates the path colors.
|
||||
*
|
||||
* @author Vangelis S.
|
||||
*/
|
||||
public class SingleColorTrackPathPainter implements TrackPathPainter {
|
||||
private final Paint selectedTrackPaint;
|
||||
private Path path;
|
||||
|
||||
public SingleColorTrackPathPainter(Context context) {
|
||||
selectedTrackPaint = TrackPathUtils.getPaint(context, R.color.red);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPath() {
|
||||
return path != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateState() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePath(
|
||||
Projection projection, Rect viewRect, int startIndex, List<CachedLocation> points) {
|
||||
if (!hasPath()) {
|
||||
path = newPath();
|
||||
}
|
||||
updatePath(projection, viewRect, startIndex, points, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearPath() {
|
||||
path = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawPath(Canvas canvas) {
|
||||
if (path != null) {
|
||||
canvas.drawPath(path, selectedTrackPaint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the path.
|
||||
*
|
||||
* @param projection the projection
|
||||
* @param viewRect the view rectangle
|
||||
* @param startIndex the start index
|
||||
* @param points the points
|
||||
* @param pathToUpdate the path to update
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void updatePath(Projection projection, Rect viewRect, int startIndex, List<CachedLocation> points,
|
||||
Path pathToUpdate) {
|
||||
pathToUpdate.incReserve(points.size() - startIndex);
|
||||
|
||||
boolean hasLastPoint = startIndex != 0 && points.get(startIndex - 1).isValid();
|
||||
boolean newSegment = !hasLastPoint;
|
||||
// Assume if last point exists, it is visible
|
||||
boolean lastPointVisible = hasLastPoint;
|
||||
Point point = new Point();
|
||||
|
||||
for (int i = startIndex; i < points.size(); i++) {
|
||||
CachedLocation cachedLocation = points.get(i);
|
||||
|
||||
// If not valid, start a new segment
|
||||
if (!cachedLocation.isValid()) {
|
||||
newSegment = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
GeoPoint geoPoint = cachedLocation.getGeoPoint();
|
||||
// Check if this breaks the existing segment.
|
||||
boolean pointVisible = viewRect.contains(geoPoint.getLongitudeE6(), geoPoint.getLatitudeE6());
|
||||
if (!pointVisible && !lastPointVisible) {
|
||||
// This point and the last point are both outside visible area.
|
||||
newSegment = true;
|
||||
}
|
||||
lastPointVisible = pointVisible;
|
||||
|
||||
// Either update point or draw a line from the last point
|
||||
projection.toPixels(geoPoint, point);
|
||||
if (newSegment) {
|
||||
pathToUpdate.moveTo(point.x, point.y);
|
||||
newSegment = false;
|
||||
} else {
|
||||
pathToUpdate.lineTo(point.x, point.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new path.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
protected Path newPath() {
|
||||
return new Path();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public Path getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
}
|
||||
+10
-33
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2011 Google Inc.
|
||||
* Copyright 2012 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
|
||||
@@ -17,50 +17,27 @@
|
||||
package com.google.android.apps.mytracks.maps;
|
||||
|
||||
import com.google.android.apps.mytracks.MapOverlay.CachedLocation;
|
||||
import com.google.android.maps.Projection;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import com.google.android.gms.maps.GoogleMap;
|
||||
import com.google.android.gms.maps.model.Polyline;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An interface for classes which paint the track path.
|
||||
* A track path.
|
||||
*
|
||||
* @author Vangelis S.
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public interface TrackPathPainter {
|
||||
public interface TrackPath {
|
||||
|
||||
/**
|
||||
* Returns true if has path.
|
||||
*/
|
||||
public boolean hasPath();
|
||||
|
||||
/**
|
||||
* Updates state. Returns true if the state is updated.
|
||||
*/
|
||||
public boolean updateState();
|
||||
|
||||
/**
|
||||
* Updates the path. Creates a new path if necessary
|
||||
* Updates the path.
|
||||
*
|
||||
* @param projection the projection
|
||||
* @param viewRect the view rectangle
|
||||
* @param startIndex the start index
|
||||
* @param points the points
|
||||
*/
|
||||
public void updatePath(
|
||||
Projection projection, Rect viewRect, int startIndex, List<CachedLocation> points);
|
||||
|
||||
/**
|
||||
* Clears the path.
|
||||
*/
|
||||
public void clearPath();
|
||||
|
||||
/**
|
||||
* Draws the path.
|
||||
*
|
||||
* @param canvas the canvas
|
||||
*/
|
||||
public void drawPath(Canvas canvas);
|
||||
public void updatePath(GoogleMap googleMap, ArrayList<Polyline> paths, int startIndex,
|
||||
List<CachedLocation> points);
|
||||
}
|
||||
+8
-13
@@ -16,42 +16,37 @@
|
||||
|
||||
package com.google.android.apps.mytracks.maps;
|
||||
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import com.google.android.apps.mytracks.util.PreferencesUtils;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* A factory for {@link TrackPathPainter}.
|
||||
* A factory for {@link TrackPath}.
|
||||
*
|
||||
* @author Vangelis S.
|
||||
*/
|
||||
public class TrackPathPainterFactory {
|
||||
public class TrackPathFactory {
|
||||
|
||||
private TrackPathPainterFactory() {}
|
||||
private TrackPathFactory() {}
|
||||
|
||||
/**
|
||||
* Get a new {@link TrackPathPainter}.
|
||||
* Get a new {@link TrackPath}.
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
public static TrackPathPainter getTrackPathPainter(Context context) {
|
||||
public static TrackPath getTrackPath(Context context) {
|
||||
String trackColorMode = PreferencesUtils.getString(
|
||||
context, R.string.track_color_mode_key, PreferencesUtils.TRACK_COLOR_MODE_DEFAULT);
|
||||
Log.i(TAG, "Creating track path painter of type: " + trackColorMode);
|
||||
|
||||
if (context.getString(R.string.settings_map_track_color_mode_dynamic_value)
|
||||
.equals(trackColorMode)) {
|
||||
return new DynamicSpeedTrackPathPainter(context, new DynamicSpeedTrackPathDescriptor(
|
||||
context));
|
||||
return new MultiColorTrackPath(context, new DynamicSpeedTrackPathDescriptor(context));
|
||||
} else if (context.getString(R.string.settings_map_track_color_mode_fixed_value)
|
||||
.equals(trackColorMode)) {
|
||||
return new DynamicSpeedTrackPathPainter(context, new FixedSpeedTrackPathDescriptor(context));
|
||||
return new MultiColorTrackPath(context, new FixedSpeedTrackPathDescriptor(context));
|
||||
} else {
|
||||
return new SingleColorTrackPathPainter(context);
|
||||
return new SingleColorTrackPath(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,12 @@
|
||||
|
||||
package com.google.android.apps.mytracks.maps;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Paint;
|
||||
import com.google.android.gms.maps.GoogleMap;
|
||||
import com.google.android.gms.maps.model.LatLng;
|
||||
import com.google.android.gms.maps.model.Model;
|
||||
import com.google.android.gms.maps.model.Polyline;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Various utility functions for track path painting.
|
||||
@@ -29,17 +33,32 @@ public class TrackPathUtils {
|
||||
private TrackPathUtils() {}
|
||||
|
||||
/**
|
||||
* Gets a paint.
|
||||
* Add a path.
|
||||
*
|
||||
* @param context the context
|
||||
* @param colorId the color id
|
||||
* @param googleMap the google map
|
||||
* @param paths the existing paths
|
||||
* @param points the path points
|
||||
* @param color the path color
|
||||
* @param append true to append to the last path
|
||||
*/
|
||||
public static Paint getPaint(Context context, int colorId) {
|
||||
Paint paint = new Paint();
|
||||
paint.setColor(context.getResources().getColor(colorId));
|
||||
paint.setStrokeWidth(3);
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
paint.setAntiAlias(true);
|
||||
return paint;
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void addPath(GoogleMap googleMap, ArrayList<Polyline> paths,
|
||||
ArrayList<LatLng> points, int color, boolean append) {
|
||||
if (points.size() == 0) {
|
||||
return;
|
||||
}
|
||||
if (append && paths.size() != 0) {
|
||||
Polyline lastPolyline = paths.get(paths.size() - 1);
|
||||
ArrayList<LatLng> pathPoints = new ArrayList<LatLng>();
|
||||
pathPoints.addAll(lastPolyline.getPoints());
|
||||
pathPoints.addAll(points);
|
||||
lastPolyline.setPoints(pathPoints);
|
||||
} else {
|
||||
Polyline.Options options = Model.newPolylineOptions()
|
||||
.addAll(points).width(5).color(color);
|
||||
Polyline polyline = googleMap.addPolyline(options);
|
||||
paths.add(polyline);
|
||||
}
|
||||
points.clear();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user