/*------------------------------------------------------------------------------
** Ident: Sogeti Smart Mobile Solutions
** Author: rene
** Copyright: (c) Apr 24, 2011 Sogeti Nederland B.V. All Rights Reserved.
**------------------------------------------------------------------------------
** Sogeti Nederland B.V. | No part of this file may be reproduced
** Distributed Software Engineering | or transmitted in any form or by any
** Lange Dreef 17 | means, electronic or mechanical, for the
** 4131 NJ Vianen | purpose, without the express written
** The Netherlands | permission of the copyright holder.
*------------------------------------------------------------------------------
*
* This file is part of OpenGPSTracker.
*
* OpenGPSTracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* OpenGPSTracker is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenGPSTracker. If not, see <http://www.gnu.org/licenses/>.
*
*/
package nl.sogeti.android.gpstracker.viewer;
import java.text.DateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import nl.sogeti.android.gpstracker.R;
import nl.sogeti.android.gpstracker.db.GPStracking;
import nl.sogeti.android.gpstracker.db.GPStracking.Media;
import nl.sogeti.android.gpstracker.db.GPStracking.Waypoints;
import nl.sogeti.android.gpstracker.util.UnitsI18n;
import nl.sogeti.android.gpstracker.viewer.proxy.MapViewProxy;
import nl.sogeti.android.gpstracker.viewer.proxy.OverlayProxy;
import nl.sogeti.android.gpstracker.viewer.proxy.ProjectionProxy;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.database.ContentObserver;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ComposeShader;
import android.graphics.CornerPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.PorterDuff.Mode;
import android.graphics.RadialGradient;
import android.graphics.Shader;
import android.graphics.Shader.TileMode;
import android.location.Location;
import android.net.Uri;
import android.os.Handler;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.Toast;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;
/**
* Creates an overlay that can draw a single segment of connected waypoints
*
* @version $Id: SegmentOverlay.java 1218 2012-01-05 19:29:23Z rcgroot $
* @author rene (c) Jan 11, 2009, Sogeti B.V.
*/
public class SegmentOverlay extends Overlay implements OverlayProxy
{
public static final int MIDDLE_SEGMENT = 0;
public static final int FIRST_SEGMENT = 1;
public static final int LAST_SEGMENT = 2;
public static final int DRAW_GREEN = 0;
public static final int DRAW_RED = 1;
public static final int DRAW_MEASURED = 2;
public static final int DRAW_CALCULATED = 3;
public static final int DRAW_DOTS = 4;
private static final String TAG = "OGT.SegmentOverlay";
private static final float MINIMUM_PX_DISTANCE = 15;
private static Map<Integer, Bitmap> sBitmapCache = new HashMap<Integer, Bitmap>();;
private int mTrackColoringMethod = DRAW_CALCULATED;
private ContentResolver mResolver;
private LoggerMap mLoggerMap;
private ProjectionProxy mProjection;
private org.osmdroid.views.overlay.Overlay mOsmOverlay;
private int mPlacement = SegmentOverlay.MIDDLE_SEGMENT;
private Uri mWaypointsUri;
private Uri mMediaUri;
private double mAvgSpeed;
private GeoPoint mGeoTopLeft;
private GeoPoint mGeoBottumRight;
private Vector<DotVO> mDotPath;
private Vector<DotVO> mDotPathCalculation;
private Path mPath;
private Path mPathCalculation;
private Shader mShader;
private Vector<MediaVO> mMediaPath;
private Vector<MediaVO> mMediaPathCalculation;
private GeoPoint mStartPoint;
private GeoPoint mEndPoint;
private Point mPrevDrawnScreenPoint;
private Point mScreenPointBackup;
private Point mScreenPoint;
private Point mMediaScreenPoint;
private Point startStopCirclePoint;
private int mStepSize = -1;
private MapViewProxy mMapView;
private Location mLocation;
private Location mPrevLocation;
private Cursor mWaypointsCursor;
private Cursor mMediaCursor;
private Uri mSegmentUri;
private int mWaypointCount = -1;
private int mWidth;
private int mHeight;
private GeoPoint mPrevGeoPoint;
private int mCurrentColor;
private Paint dotpaint;
private Paint radiusPaint;
private Paint routePaint;
private Paint defaultPaint;
private boolean mRequeryFlag;
private Handler mHandler;
private static Bitmap mStartBitmap;
private static Bitmap mStopBitmap;
private ContentObserver mTrackSegmentsObserver;
private final Runnable mMediaCalculator = new Runnable()
{
public void run()
{
SegmentOverlay.this.calculateMediaAsync();
}
};
private final Runnable mTrackCalculator = new Runnable()
{
public void run()
{
SegmentOverlay.this.calculateTrackAsync();
}
};
/**
* Constructor: create a new TrackingOverlay.
*
* @param loggermap
* @param segmentUri
* @param color
* @param avgSpeed
* @param mMapView
*/
public SegmentOverlay(LoggerMap loggermap, Uri segmentUri, int color, double avgSpeed, MapViewProxy mapView, Handler handler)
{
super();
mHandler = handler;
mLoggerMap = loggermap;
mMapView = mapView;
mTrackColoringMethod = color;
mAvgSpeed = avgSpeed;
mSegmentUri = segmentUri;
mMediaUri = Uri.withAppendedPath( mSegmentUri, "media" );
mWaypointsUri = Uri.withAppendedPath( mSegmentUri, "waypoints" );
mResolver = mLoggerMap.getContentResolver();
mRequeryFlag = true;
mCurrentColor = Color.rgb( 255, 0, 0 );
mProjection = mapView.getProjection();
dotpaint = new Paint();
radiusPaint = new Paint();
radiusPaint.setColor( Color.YELLOW );
radiusPaint.setAlpha( 100 );
routePaint = new Paint();
routePaint.setStyle( Paint.Style.STROKE );
routePaint.setStrokeWidth( 6 );
routePaint.setAntiAlias( true );
routePaint.setPathEffect( new CornerPathEffect( 10 ) );
defaultPaint = new Paint();
mScreenPoint = new Point();
mMediaScreenPoint = new Point();
startStopCirclePoint = new Point();
mScreenPointBackup = new Point();
mPrevDrawnScreenPoint = new Point();
mDotPath = new Vector<DotVO>();
mDotPathCalculation = new Vector<DotVO>();
mPath = new Path();
mPathCalculation = new Path();
mMediaPath = new Vector<MediaVO>();
mMediaPathCalculation = new Vector<MediaVO>();
mOsmOverlay = new org.osmdroid.views.overlay.Overlay(mLoggerMap) {
@Override
public boolean onSingleTapUp(MotionEvent e, org.osmdroid.views.MapView openStreetMapView)
{
int x = (int) e.getX();
int y = (int) e.getY();
GeoPoint tappedGeoPoint = mProjection.fromPixels(x, y);
return SegmentOverlay.this.commonOnTap(tappedGeoPoint );
}
@Override
protected void draw(Canvas canvas, org.osmdroid.views.MapView view, boolean shadow)
{
if( !shadow )
{
mProjection.setProjection(view);
SegmentOverlay.this.draw( canvas );
}
}
};
mTrackSegmentsObserver = new ContentObserver( new Handler() )
{
@Override
public void onChange( boolean selfUpdate )
{
if( !selfUpdate )
{
mRequeryFlag = true;
}
else
{
Log.w( TAG, "mTrackSegmentsObserver skipping change on " + mSegmentUri );
}
}
};
mResolver.registerContentObserver( mWaypointsUri, false, mTrackSegmentsObserver );
}
public void closeResources()
{
mResolver.unregisterContentObserver( mTrackSegmentsObserver );
mTrackSegmentsObserver = null;
mHandler.removeCallbacks(mMediaCalculator);
mHandler.removeCallbacks(mTrackCalculator);
mHandler.postAtFrontOfQueue(new Runnable()
{
public void run()
{
if( mWaypointsCursor != null )
{
mWaypointsCursor.close();
}
if( mMediaCursor != null )
{
mMediaCursor.close();
}
}
});
}
@Override
public void draw( Canvas canvas, MapView mapView, boolean shadow )
{
super.draw( canvas, mapView, shadow );
if( !shadow )
{
mProjection.setProjection( mapView.getProjection() );
draw( canvas );
}
}
/**
* Private draw method called by both the draw from Google Overlay and the OSM Overlay
*
* @param canvas
*/
private void draw( Canvas canvas )
{
switch( mTrackColoringMethod )
{
case ( DRAW_CALCULATED ):
case ( DRAW_MEASURED ):
case ( DRAW_RED ):
case ( DRAW_GREEN ):
drawPath( canvas );
break;
case ( DRAW_DOTS ):
drawDots( canvas );
break;
}
drawMedia( canvas );
drawStartStopCircles( canvas );
mWidth = canvas.getWidth();
mHeight = canvas.getHeight();
calculateMedia();
calculateTrack(); // Screen changed, need to adjust the path to match the screen
}
public void calculateTrack()
{
mHandler.removeCallbacks(mTrackCalculator);
mHandler.post(mTrackCalculator);
}
/**
* Either the Path or the Dots are calculated based on he current track coloring method
*
*/
private synchronized void calculateTrackAsync()
{
GeoPoint oldTopLeft = mGeoTopLeft;
GeoPoint oldBottumRight = mGeoBottumRight;
mGeoTopLeft = mProjection.fromPixels( 0, 0 );
mGeoBottumRight = mProjection.fromPixels( mWidth, mHeight );
if( mRequeryFlag
|| oldTopLeft == null
|| oldBottumRight == null
|| mGeoTopLeft.getLatitudeE6() / 100 != oldTopLeft.getLatitudeE6() / 100
|| mGeoTopLeft.getLongitudeE6() / 100 != oldTopLeft.getLongitudeE6() / 100
|| mGeoBottumRight.getLatitudeE6() / 100 != oldBottumRight.getLatitudeE6() / 100
|| mGeoBottumRight.getLongitudeE6() / 100 != oldBottumRight.getLongitudeE6() / 100 )
{
calculateStepSize();
mScreenPoint.x = -1;
mScreenPoint.y = -1;
this.mPrevDrawnScreenPoint.x = -1;
this.mPrevDrawnScreenPoint.y = -1;
switch( mTrackColoringMethod )
{
case ( DRAW_CALCULATED ):
case ( DRAW_MEASURED ):
case ( DRAW_RED ):
case ( DRAW_GREEN ):
calculatePath();
synchronized (mPath) // Switch the fresh path with the old Path object
{
Path oldPath = mPath;
mPath = mPathCalculation;
mPathCalculation = oldPath;
}
break;
case ( DRAW_DOTS ):
calculateDots();
synchronized (mDotPath) // Switch the fresh path with the old Path object
{
Vector<DotVO> oldDotPath = mDotPath;
mDotPath = mDotPathCalculation;
mDotPathCalculation = oldDotPath;
}
break;
}
mLoggerMap.onDateOverlayChanged();
}
}
/**
* Calculated the new contents of segment in the mDotPathCalculation
*/
private void calculatePath()
{
mDotPathCalculation.clear();
this.mPathCalculation.rewind();
this.mShader = null;
GeoPoint geoPoint;
this.mPrevLocation = null;
if( mWaypointsCursor == null )
{
mWaypointsCursor = this.mResolver.query( this.mWaypointsUri, new String[] { Waypoints.LATITUDE, Waypoints.LONGITUDE, Waypoints.SPEED, Waypoints.TIME, Waypoints.ACCURACY }, null, null, null );
mRequeryFlag = false;
}
if( mRequeryFlag )
{
mWaypointsCursor.requery();
mRequeryFlag = false;
}
if( mProjection != null && mWaypointsCursor.moveToFirst() )
{
// Start point of the segments, possible a dot
this.mStartPoint = extractGeoPoint();
mPrevGeoPoint = mStartPoint;
this.mLocation = new Location( this.getClass().getName() );
this.mLocation.setLatitude( mWaypointsCursor.getDouble( 0 ) );
this.mLocation.setLongitude( mWaypointsCursor.getDouble( 1 ) );
this.mLocation.setTime( mWaypointsCursor.getLong( 3 ) );
moveToGeoPoint( this.mStartPoint );
do
{
geoPoint = extractGeoPoint();
// Do no include log wrong 0.0 lat 0.0 long, skip to next value in while-loop
if( geoPoint.getLatitudeE6() == 0 || geoPoint.getLongitudeE6() == 0 )
{
continue;
}
double speed = -1d;
switch( mTrackColoringMethod )
{
case DRAW_GREEN:
case DRAW_RED:
lineToGeoPoint( geoPoint, speed );
break;
case DRAW_MEASURED:
lineToGeoPoint( geoPoint, mWaypointsCursor.getDouble( 2 ) );
break;
case DRAW_CALCULATED:
this.mPrevLocation = this.mLocation;
this.mLocation = new Location( this.getClass().getName() );
this.mLocation.setLatitude( mWaypointsCursor.getDouble( 0 ) );
this.mLocation.setLongitude( mWaypointsCursor.getDouble( 1 ) );
this.mLocation.setTime( mWaypointsCursor.getLong( 3 ) );
speed = calculateSpeedBetweenLocations( this.mPrevLocation, this.mLocation );
lineToGeoPoint( geoPoint, speed );
break;
default:
Log.w( TAG, "Unknown coloring method" );
break;
}
}
while( moveToNextWayPoint() );
this.mEndPoint = extractGeoPoint(); // End point of the segments, possible a dot
}
// Log.d( TAG, "transformSegmentToPath stop: points "+mCalculatedPoints+" from "+moves+" moves" );
}
/**
* @param canvas
* @param mapView
* @param shadow
* @see SegmentOverlay#draw(Canvas, MapView, boolean)
*/
private void calculateDots()
{
mPathCalculation.reset();
mDotPathCalculation.clear();
if( mWaypointsCursor == null )
{
mWaypointsCursor = this.mResolver.query( this.mWaypointsUri, new String[] { Waypoints.LATITUDE, Waypoints.LONGITUDE, Waypoints.SPEED, Waypoints.TIME, Waypoints.ACCURACY }, null, null, null );
}
if( mRequeryFlag )
{
mWaypointsCursor.requery();
mRequeryFlag = false;
}
if( mProjection != null && mWaypointsCursor.moveToFirst() )
{
GeoPoint geoPoint;
mStartPoint = extractGeoPoint();
mPrevGeoPoint = mStartPoint;
do
{
geoPoint = extractGeoPoint();
// Do no include log wrong 0.0 lat 0.0 long, skip to next value in while-loop
if( geoPoint.getLatitudeE6() == 0 || geoPoint.getLongitudeE6() == 0 )
{
continue;
}
setScreenPoint( geoPoint );
float distance = (float) distanceInPoints( this.mPrevDrawnScreenPoint, this.mScreenPoint );
if( distance > MINIMUM_PX_DISTANCE )
{
DotVO dotVO = new DotVO();
dotVO.x = this.mScreenPoint.x;
dotVO.y = this.mScreenPoint.y;
dotVO.speed = mWaypointsCursor.getLong( 2 );
dotVO.time = mWaypointsCursor.getLong( 3 );
dotVO.radius = mProjection.metersToEquatorPixels( mWaypointsCursor.getFloat( 4 ) );
mDotPathCalculation.add( dotVO );
this.mPrevDrawnScreenPoint.x = this.mScreenPoint.x;
this.mPrevDrawnScreenPoint.y = this.mScreenPoint.y;
}
}
while( moveToNextWayPoint() );
this.mEndPoint = extractGeoPoint();
DotVO pointVO = new DotVO();
pointVO.x = this.mScreenPoint.x;
pointVO.y = this.mScreenPoint.y;
pointVO.speed = mWaypointsCursor.getLong( 2 );
pointVO.time = mWaypointsCursor.getLong( 3 );
pointVO.radius = mProjection.metersToEquatorPixels( mWaypointsCursor.getFloat( 4 ) );
mDotPathCalculation.add( pointVO );
}
}
public void calculateMedia()
{
mHandler.removeCallbacks(mMediaCalculator);
mHandler.post(mMediaCalculator);
}
public synchronized void calculateMediaAsync()
{
mMediaPathCalculation.clear();
if (mMediaCursor == null)
{
mMediaCursor = this.mResolver.query(this.mMediaUri, new String[] { Media.WAYPOINT, Media.URI }, null, null, null);
}
else
{
mMediaCursor.requery();
}
if (mProjection != null && mMediaCursor.moveToFirst())
{
GeoPoint lastPoint = null;
int wiggle = 0;
do
{
MediaVO mediaVO = new MediaVO();
mediaVO.waypointId = mMediaCursor.getLong(0);
mediaVO.uri = Uri.parse(mMediaCursor.getString(1));
Uri mediaWaypoint = ContentUris.withAppendedId(mWaypointsUri, mediaVO.waypointId);
Cursor waypointCursor = null;
try
{
waypointCursor = this.mResolver.query(mediaWaypoint, new String[] { Waypoints.LATITUDE, Waypoints.LONGITUDE }, null, null, null);
if (waypointCursor != null && waypointCursor.moveToFirst())
{
int microLatitude = (int) (waypointCursor.getDouble(0) * 1E6d);
int microLongitude = (int) (waypointCursor.getDouble(1) * 1E6d);
mediaVO.geopoint = new GeoPoint(microLatitude, microLongitude);
}
}
finally
{
if (waypointCursor != null)
{
waypointCursor.close();
}
}
if( isGeoPointOnScreen( mediaVO.geopoint ) )
{
this.mProjection.toPixels( mediaVO.geopoint, this.mMediaScreenPoint );
if( mediaVO.geopoint.equals( lastPoint ) )
{
wiggle += 4;
}
else
{
wiggle = 0;
}
mediaVO.bitmapKey = getResourceForMedia( mLoggerMap.getResources(), mediaVO.uri );
mediaVO.w = sBitmapCache.get(mediaVO.bitmapKey).getWidth();
mediaVO.h = sBitmapCache.get(mediaVO.bitmapKey).getHeight();
int left = ( mediaVO.w * 3 ) / 7 + wiggle;
int up = ( mediaVO.h * 6 ) / 7 - wiggle;
mediaVO.x = mMediaScreenPoint.x - left;
mediaVO.y = mMediaScreenPoint.y - up;
lastPoint = mediaVO.geopoint;
}
mMediaPathCalculation.add(mediaVO);
}
while (mMediaCursor.moveToNext());
}
synchronized (mMediaPath) // Switch the fresh path with the old Path object
{
Vector<MediaVO> oldmMediaPath = mMediaPath;
mMediaPath = mMediaPathCalculation;
mMediaPathCalculation = oldmMediaPath;
}
if( mMediaPathCalculation.size() != mMediaPath.size() )
{
mLoggerMap.onDateOverlayChanged();
}
}
/**
* @param canvas
*
* @see SegmentOverlay#draw(Canvas, MapView, boolean)
*/
private void drawPath( Canvas canvas )
{
switch( mTrackColoringMethod )
{
case ( DRAW_CALCULATED ):
case ( DRAW_MEASURED ):
routePaint.setShader( this.mShader );
break;
case ( DRAW_RED ):
routePaint.setShader( null );
routePaint.setColor( Color.RED );
break;
case ( DRAW_GREEN ):
routePaint.setShader( null );
routePaint.setColor( Color.GREEN );
break;
default:
routePaint.setShader( null );
routePaint.setColor( Color.YELLOW );
break;
}
synchronized ( mPath )
{
canvas.drawPath( mPath, routePaint );
}
}
private void drawDots( Canvas canvas )
{
synchronized ( mDotPath )
{
Bitmap bitmap = BitmapFactory.decodeResource( this.mLoggerMap.getResources(), R.drawable.stip2 );
for( DotVO dotVO : mDotPath )
{
canvas.drawBitmap( bitmap, dotVO.x - 8, dotVO.y - 8, dotpaint );
if( dotVO.radius > 8f )
{
canvas.drawCircle( dotVO.x, dotVO.y, dotVO.radius, radiusPaint );
}
}
bitmap.recycle();
bitmap = null;
}
}
private void drawMedia( Canvas canvas )
{
synchronized( mMediaPath )
{
for( MediaVO mediaVO : mMediaPath )
{
if( mediaVO.bitmapKey != null )
{
canvas.drawBitmap( sBitmapCache.get(mediaVO.bitmapKey), mediaVO.x, mediaVO.y, defaultPaint );
}
}
}
}
private Integer getResourceForMedia( Resources resources, Uri uri )
{
int drawable = 0;
if( uri.getScheme().equals( "file" ) )
{
if( uri.getLastPathSegment().endsWith( "3gp" ) )
{
drawable = R.drawable.media_film;
}
else if( uri.getLastPathSegment().endsWith( "jpg" ) )
{
drawable = R.drawable.media_camera;
}
else if( uri.getLastPathSegment().endsWith( "txt" ) )
{
drawable = R.drawable.media_notepad;
}
}
else if( uri.getScheme().equals( "content" ) )
{
if( uri.getAuthority().equals( GPStracking.AUTHORITY + ".string" ) )
{
drawable = R.drawable.media_mark;
}
else if( uri.getAuthority().equals( "media" ) )
{
drawable = R.drawable.media_speech;
}
}
Bitmap bitmap = null;
Integer bitmapKey = new Integer(drawable);
synchronized (sBitmapCache)
{
if( !sBitmapCache.containsKey( bitmapKey) )
{
bitmap = BitmapFactory.decodeResource( resources, drawable );
sBitmapCache.put(bitmapKey, bitmap);
}
bitmap = sBitmapCache.get( bitmapKey );
}
return bitmapKey;
}
private void drawStartStopCircles( Canvas canvas )
{
if( ( this.mPlacement == FIRST_SEGMENT || this.mPlacement == FIRST_SEGMENT + LAST_SEGMENT ) && this.mStartPoint != null )
{
if( mStartBitmap == null )
{
mStartBitmap = BitmapFactory.decodeResource( this.mLoggerMap.getResources(), R.drawable.stip );
}
this.mProjection.toPixels( this.mStartPoint, startStopCirclePoint );
canvas.drawBitmap( mStartBitmap, startStopCirclePoint.x - 8, startStopCirclePoint.y - 8, defaultPaint );
}
if( ( this.mPlacement == LAST_SEGMENT || this.mPlacement == FIRST_SEGMENT + LAST_SEGMENT ) && this.mEndPoint != null )
{
if( mStopBitmap == null )
{
mStopBitmap = BitmapFactory.decodeResource( this.mLoggerMap.getResources(), R.drawable.stip2 );
}
this.mProjection.toPixels( this.mEndPoint, startStopCirclePoint );
canvas.drawBitmap( mStopBitmap, startStopCirclePoint.x - 5, startStopCirclePoint.y - 5, defaultPaint );
}
}
/**
* Set the mPlace to the specified value.
*
* @see SegmentOverlay.FIRST
* @see SegmentOverlay.MIDDLE
* @see SegmentOverlay.LAST
* @param place The placement of this segment in the line.
*/
public void addPlacement( int place )
{
this.mPlacement += place;
}
public boolean isLast()
{
return ( mPlacement >= LAST_SEGMENT );
}
public long getSegmentId()
{
return Long.parseLong( mSegmentUri.getLastPathSegment() );
}
/**
* Set the beginnging to the next contour of the line to the give GeoPoint
*
* @param geoPoint
*/
private void moveToGeoPoint( GeoPoint geoPoint )
{
setScreenPoint( geoPoint );
if( this.mPathCalculation != null )
{
this.mPathCalculation.moveTo( this.mScreenPoint.x, this.mScreenPoint.y );
this.mPrevDrawnScreenPoint.x = this.mScreenPoint.x;
this.mPrevDrawnScreenPoint.y = this.mScreenPoint.y;
}
}
private void lineToGeoPoint( GeoPoint geoPoint, double speed )
{
setScreenPoint( geoPoint );
// Log.d( TAG, "Draw line to " + geoPoint+" with speed "+speed );
if( speed > 0 )
{
int greenfactor = (int) Math.min( ( 127 * speed ) / mAvgSpeed, 255 );
int redfactor = 255 - greenfactor;
mCurrentColor = Color.rgb( redfactor, greenfactor, 0 );
}
else
{
int greenfactor = Color.green( mCurrentColor );
int redfactor = Color.red( mCurrentColor );
mCurrentColor = Color.argb( 128, redfactor, greenfactor, 0 );
}
float distance = (float) distanceInPoints( this.mPrevDrawnScreenPoint, this.mScreenPoint );
if( distance > MINIMUM_PX_DISTANCE )
{
// Log.d( TAG, "Circle between " + mPrevDrawnScreenPoint+" and "+mScreenPoint );
int x_circle = ( this.mPrevDrawnScreenPoint.x + this.mScreenPoint.x ) / 2;
int y_circle = ( this.mPrevDrawnScreenPoint.y + this.mScreenPoint.y ) / 2;
float radius_factor = 0.4f;
Shader lastShader = new RadialGradient( x_circle, y_circle, distance, new int[] { mCurrentColor, mCurrentColor, Color.TRANSPARENT }, new float[] { 0, radius_factor, 0.6f }, TileMode.CLAMP );
// Paint debug = new Paint();
// debug.setStyle( Paint.Style.FILL_AND_STROKE );
// this.mDebugCanvas.drawCircle(
// x_circle,
// y_circle,
// distance*radius_factor/2,
// debug );
// this.mDebugCanvas.drawCircle(
// x_circle,
// y_circle,
// distance*radius_factor,
// debug );
// if( distance > 100 )
// {
// Log.d( TAG, "Created shader for speed " + speed + " on " + x_circle + "," + y_circle );
// }
if( this.mShader != null )
{
this.mShader = new ComposeShader( this.mShader, lastShader, Mode.DST_OVER );
}
else
{
this.mShader = lastShader;
}
this.mPrevDrawnScreenPoint.x = this.mScreenPoint.x;
this.mPrevDrawnScreenPoint.y = this.mScreenPoint.y;
}
this.mPathCalculation.lineTo( this.mScreenPoint.x, this.mScreenPoint.y );
}
/**
* Use to update location/point state when calculating the line
*
* @param geoPoint
*/
private void setScreenPoint( GeoPoint geoPoint )
{
mScreenPointBackup.x = this.mScreenPoint.x;
mScreenPointBackup.y = this.mScreenPoint.x;
this.mProjection.toPixels( geoPoint, this.mScreenPoint );
}
/**
* Move to a next waypoint, for on screen this are the points with mStepSize % position == 0 to avoid jittering in the rendering or the points on the either side of the screen edge.
*
* @return if a next waypoint is pointed to with the mWaypointsCursor
*/
private boolean moveToNextWayPoint()
{
boolean cursorReady = true;
boolean onscreen = isGeoPointOnScreen( extractGeoPoint() );
if( mWaypointsCursor.isLast() ) // End of the line, cant move onward
{
cursorReady = false;
}
else if( onscreen ) // Are on screen
{
cursorReady = moveOnScreenWaypoint();
}
else
// Are off screen => accelerate
{
int acceleratedStepsize = mStepSize * ( mWaypointCount / 1000 + 6 );
cursorReady = moveOffscreenWaypoint( acceleratedStepsize );
}
return cursorReady;
}
/**
* Move the cursor to the next waypoint modulo of the step size or less if the screen edge is reached
*
* @param trackCursor
* @return
*/
private boolean moveOnScreenWaypoint()
{
int nextPosition = mStepSize * ( mWaypointsCursor.getPosition() / mStepSize ) + mStepSize;
if( mWaypointsCursor.moveToPosition( nextPosition ) )
{
if( isGeoPointOnScreen( extractGeoPoint() ) ) // Remained on screen
{
return true; // Cursor is pointing to somewhere
}
else
{
mWaypointsCursor.move( -1 * mStepSize ); // Step back
boolean nowOnScreen = true; // onto the screen
while( nowOnScreen ) // while on the screen
{
mWaypointsCursor.moveToNext(); // inch forward to the edge
nowOnScreen = isGeoPointOnScreen( extractGeoPoint() );
}
return true; // with a cursor point to somewhere
}
}
else
{
return mWaypointsCursor.moveToLast(); // No full step can be taken, move to last
}
}
/**
* Previous path GeoPoint was off screen and the next one will be to or the first on screen when the path reaches the projection.
*
* @return
*/
private boolean moveOffscreenWaypoint( int flexStepsize )
{
while( mWaypointsCursor.move( flexStepsize ) )
{
if( mWaypointsCursor.isLast() )
{
return true;
}
GeoPoint evalPoint = extractGeoPoint();
// Do no include log wrong 0.0 lat 0.0 long, skip to next value in while-loop
if( evalPoint.getLatitudeE6() == 0 || evalPoint.getLongitudeE6() == 0 )
{
continue;
}
// Log.d( TAG, String.format( "Evaluate point number %d ", mWaypointsCursor.getPosition() ) );
if( possibleScreenPass( mPrevGeoPoint, evalPoint ) )
{
mPrevGeoPoint = evalPoint;
if( flexStepsize == 1 ) // Just stumbled over a border
{
return true;
}
else
{
mWaypointsCursor.move( -1 * flexStepsize ); // Take 1 step back
return moveOffscreenWaypoint( flexStepsize / 2 ); // Continue at halve accelerated speed
}
}
else
{
moveToGeoPoint( evalPoint );
mPrevGeoPoint = evalPoint;
}
}
return mWaypointsCursor.moveToLast();
}
/**
* If a segment contains more then 500 waypoints and is zoomed out more then twice then some waypoints will not be used to render the line, this speeding things along.
*/
private void calculateStepSize()
{
Cursor waypointsCursor = null;
if( mRequeryFlag || mStepSize < 1 || mWaypointCount < 0 )
{
try
{
waypointsCursor = this.mResolver.query( this.mWaypointsUri, new String[] { Waypoints._ID }, null, null, null );
mWaypointCount = waypointsCursor.getCount();
}
finally
{
if( waypointsCursor != null )
{
waypointsCursor.close();
}
}
}
if( mWaypointCount < 250 )
{
mStepSize = 1;
}
else
{
int zoomLevel = mMapView.getZoomLevel();
int maxZoomLevel = mMapView.getMaxZoomLevel();
if( zoomLevel >= maxZoomLevel - 2 )
{
mStepSize = 1;
}
else
{
mStepSize = maxZoomLevel - zoomLevel;
}
}
}
/**
* Is a given GeoPoint in the current projection of the map.
*
* @param eval
* @return
*/
protected boolean isGeoPointOnScreen(GeoPoint geopoint)
{
boolean onscreen = geopoint != null;
if (geopoint != null && mGeoTopLeft != null && mGeoBottumRight != null)
{
onscreen = onscreen && mGeoTopLeft.getLatitudeE6() > geopoint.getLatitudeE6();
onscreen = onscreen && mGeoBottumRight.getLatitudeE6() < geopoint.getLatitudeE6();
if (mGeoTopLeft.getLongitudeE6() < mGeoBottumRight.getLongitudeE6())
{
onscreen = onscreen && mGeoTopLeft.getLongitudeE6() < geopoint.getLongitudeE6();
onscreen = onscreen && mGeoBottumRight.getLongitudeE6() > geopoint.getLongitudeE6();
}
else
{
onscreen = onscreen
&& (mGeoTopLeft.getLongitudeE6() < geopoint.getLongitudeE6() || mGeoBottumRight
.getLongitudeE6() > geopoint.getLongitudeE6());
}
}
return onscreen;
}
/**
* Is a given coordinates are on the screen
*
* @param eval
* @return
*/
protected boolean isOnScreen( int x, int y )
{
boolean onscreen = x > 0 && y > 0 && x < mWidth && y < mHeight;
return onscreen;
}
/**
* Calculates in which segment opposited to the projecting a geo point resides
*
* @param p1
* @return
*/
private int toSegment( GeoPoint p1 )
{
// Log.d( TAG, String.format( "Comparing %s to points TL %s and BR %s", p1, mTopLeft, mBottumRight ));
int nr;
if( p1.getLongitudeE6() < mGeoTopLeft.getLongitudeE6() ) // left
{
nr = 1;
}
else if( p1.getLongitudeE6() > mGeoBottumRight.getLongitudeE6() ) // right
{
nr = 3;
}
else
// middle
{
nr = 2;
}
if( p1.getLatitudeE6() > mGeoTopLeft.getLatitudeE6() ) // top
{
nr = nr + 0;
}
else if( p1.getLatitudeE6() < mGeoBottumRight.getLatitudeE6() ) // bottom
{
nr = nr + 6;
}
else
// middle
{
nr = nr + 3;
}
return nr;
}
private boolean possibleScreenPass( GeoPoint fromGeo, GeoPoint toGeo )
{
boolean safe = true;
if( fromGeo != null && toGeo != null )
{
int from = toSegment( fromGeo );
int to = toSegment( toGeo );
switch( from )
{
case 1:
safe = to == 1 || to == 2 || to == 3 || to == 4 || to == 7;
break;
case 2:
safe = to == 1 || to == 2 || to == 3;
break;
case 3:
safe = to == 1 || to == 2 || to == 3 || to == 6 || to == 9;
break;
case 4:
safe = to == 1 || to == 4 || to == 7;
break;
case 5:
safe = false;
break;
case 6:
safe = to == 3 || to == 6 || to == 9;
break;
case 7:
safe = to == 1 || to == 4 || to == 7 || to == 8 || to == 9;
break;
case 8:
safe = to == 7 || to == 8 || to == 9;
break;
case 9:
safe = to == 3 || to == 6 || to == 7 || to == 8 || to == 9;
break;
default:
safe = false;
break;
}
// Log.d( TAG, String.format( "From %d to %d is safe: %s", from, to, safe ) );
}
return !safe;
}
public void setTrackColoringMethod( int coloring, double avgspeed )
{
if( mTrackColoringMethod != coloring )
{
this.mTrackColoringMethod = coloring;
mRequeryFlag = true;
calculateTrack();
}
this.mAvgSpeed = avgspeed;
}
/**
* For the current waypoint cursor returns the GeoPoint
*
* @return
*/
private GeoPoint extractGeoPoint()
{
int microLatitude = (int) ( mWaypointsCursor.getDouble( 0 ) * 1E6d );
int microLongitude = (int) ( mWaypointsCursor.getDouble( 1 ) * 1E6d );
return new GeoPoint( microLatitude, microLongitude );
}
/**
* @param startLocation
* @param endLocation
* @return speed in m/s between 2 locations
*/
private static double calculateSpeedBetweenLocations( Location startLocation, Location endLocation )
{
double speed = -1d;
if( startLocation != null && endLocation != null )
{
float distance = startLocation.distanceTo( endLocation );
float seconds = ( endLocation.getTime() - startLocation.getTime() ) / 1000f;
speed = distance / seconds;
// Log.d( TAG, "Found a speed of "+speed+ " over a distance of "+ distance+" in a time of "+seconds);
}
if( speed > 0 )
{
return speed;
}
else
{
return -1d;
}
}
public static int extendPoint( int x1, int x2 )
{
int diff = x2 - x1;
int next = x2 + diff;
return next;
}
private static double distanceInPoints( Point start, Point end )
{
int x = Math.abs( end.x - start.x );
int y = Math.abs( end.y - start.y );
return (double) Math.sqrt( x * x + y * y );
}
private boolean handleMediaTapList( List<Uri> tappedUri )
{
if( tappedUri.size() == 1 )
{
return handleMedia( mLoggerMap, tappedUri.get( 0 ) );
}
else
{
BaseAdapter adapter = new MediaAdapter( mLoggerMap, tappedUri );
mLoggerMap.showDialog( adapter );
return true;
}
}
public static boolean handleMedia( Context ctx, Uri mediaUri )
{
if( mediaUri.getScheme().equals( "file" ) )
{
Intent intent = new Intent( android.content.Intent.ACTION_VIEW );
if( mediaUri.getLastPathSegment().endsWith( "3gp" ) )
{
intent.setDataAndType( mediaUri, "video/3gpp" );
ctx.startActivity( intent );
return true;
}
else if( mediaUri.getLastPathSegment().endsWith( "jpg" ) )
{
//<scheme>://<authority><absolute path>
Uri.Builder builder = new Uri.Builder();
mediaUri = builder.scheme( mediaUri.getScheme() ).authority( mediaUri.getAuthority() ).path( mediaUri.getPath() ).build();
intent.setDataAndType( mediaUri, "image/jpeg" );
ctx.startActivity( intent );
return true;
}
else if( mediaUri.getLastPathSegment().endsWith( "txt" ) )
{
intent.setDataAndType( mediaUri, "text/plain" );
ctx.startActivity( intent );
return true;
}
}
else if( mediaUri.getScheme().equals( "content" ) )
{
if( mediaUri.getAuthority().equals( GPStracking.AUTHORITY + ".string" ) )
{
String text = mediaUri.getLastPathSegment();
Toast toast = Toast.makeText( ctx, text, Toast.LENGTH_LONG );
toast.show();
return true;
}
else if( mediaUri.getAuthority().equals( "media" ) )
{
ctx.startActivity( new Intent( Intent.ACTION_VIEW, mediaUri ) );
return true;
}
}
return false;
}
/*
* (non-Javadoc)
* @see com.google.android.maps.Overlay#onTap(com.google.android.maps.GeoPoint, com.google.android.maps.MapView)
*/
@Override
public boolean onTap( GeoPoint tappedGeoPoint, MapView mapView )
{
return commonOnTap(tappedGeoPoint) ;
}
private boolean commonOnTap(GeoPoint tappedGeoPoint)
{
List<Uri> tappedUri = new Vector<Uri>();
Point tappedPoint = new Point();
mProjection.toPixels( tappedGeoPoint, tappedPoint );
for( MediaVO media : mMediaPath )
{
if( media.x < tappedPoint.x && tappedPoint.x < media.x + media.w && media.y < tappedPoint.y && tappedPoint.y < media.y + media.h )
{
//Log.d( TAG, String.format( "Tapped at a (x,y) (%d,%d)", tappedPoint.x, tappedPoint.y ) );
tappedUri.add( media.uri );
}
}
if( tappedUri.size() > 0 )
{
return handleMediaTapList( tappedUri );
}
else
{
if( mTrackColoringMethod == DRAW_DOTS )
{
DotVO tapped = null;
synchronized (mDotPath) // Switch the fresh path with the old Path object
{
int w = 25;
for( DotVO dot : mDotPath )
{
// Log.d( TAG, "Compare ("+dot.x+","+dot.y+") with tap ("+tappedPoint.x+","+tappedPoint.y+")" );
if( dot.x - w < tappedPoint.x && tappedPoint.x < dot.x + w && dot.y - w < tappedPoint.y && tappedPoint.y < dot.y + w )
{
if( tapped == null )
{
tapped = dot;
}
else
{
tapped = dot.distanceTo(tappedPoint) < tapped.distanceTo(tappedPoint) ? dot : tapped;
}
}
}
}
if( tapped != null )
{
DateFormat timeFormat = android.text.format.DateFormat.getTimeFormat(mLoggerMap.getApplicationContext());
String timetxt = timeFormat.format(new Date(tapped.time));
UnitsI18n units = new UnitsI18n( mLoggerMap, null);
double speed = units.conversionFromMetersPerSecond( tapped.speed );
String speedtxt = String.format( "%.1f %s", speed, units.getSpeedUnit() );
String text = mLoggerMap.getString(R.string.time_and_speed, timetxt, speedtxt );
Toast toast = Toast.makeText(mLoggerMap, text, Toast.LENGTH_SHORT);
toast.show();
}
}
return false;
}
}
private static class MediaVO
{
@Override
public String toString()
{
return "MediaVO [bitmapKey=" + bitmapKey + ", uri=" + uri + ", geopoint=" + geopoint + ", x=" + x + ", y=" + y + ", w=" + w + ", h=" + h + ", waypointId="
+ waypointId + "]";
}
public Integer bitmapKey;
public Uri uri;
public GeoPoint geopoint;
public int x;
public int y;
public int w;
public int h;
public long waypointId;
}
private static class DotVO
{
public long time;
public long speed;
public int x;
public int y;
public float radius;
public int distanceTo(Point tappedPoint)
{
return Math.abs(tappedPoint.x - this.x) + Math.abs(tappedPoint.y - this.y);
}
}
private class MediaAdapter extends BaseAdapter
{
private Context mContext;
private List<Uri> mTappedUri;
private int itemBackground;
public MediaAdapter(Context ctx, List<Uri> tappedUri)
{
mContext = ctx;
mTappedUri = tappedUri;
TypedArray a = mContext.obtainStyledAttributes( R.styleable.gallery );
itemBackground = a.getResourceId( R.styleable.gallery_android_galleryItemBackground, 0 );
a.recycle();
}
public int getCount()
{
return mTappedUri.size();
}
public Object getItem( int position )
{
return mTappedUri.get( position );
}
public long getItemId( int position )
{
return position;
}
public View getView( int position, View convertView, ViewGroup parent )
{
ImageView imageView = new ImageView( mContext );
imageView.setImageBitmap( sBitmapCache.get(getResourceForMedia( mLoggerMap.getResources(), mTappedUri.get( position ) ) ) );
imageView.setScaleType( ImageView.ScaleType.FIT_XY );
imageView.setBackgroundResource( itemBackground );
return imageView;
}
}
public Overlay getGoogleOverlay()
{
return this;
}
public org.osmdroid.views.overlay.Overlay getOSMOverlay()
{
return mOsmOverlay;
}
}