/*------------------------------------------------------------------------------
** Ident: Innovation en Inspiration > Google Android
** Author: rene
** Copyright: (c) Jan 22, 2009 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 nl.sogeti.android.gpstracker.R;
import nl.sogeti.android.gpstracker.db.GPStracking.Waypoints;
import android.content.ContentResolver;
import android.content.Context;
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.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Shader;
import android.graphics.PorterDuff.Mode;
import android.graphics.Shader.TileMode;
import android.location.Location;
import android.net.Uri;
import android.util.Log;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;
import com.google.android.maps.Projection;
/**
* Creates an overlay that can draw a single segment of connected waypoints
*
* @version $Id: TrackingOverlay.java 140 2009-11-04 21:20:12Z rcgroot@gmail.com $
* @author rene (c) Jan 11, 2009, Sogeti B.V.
*/
public class TrackingOverlay extends Overlay
{
public static final int MIDDLE_SEGMENT = 0;
public static final int FIRST_SEGMENT = 1;
public static final int LAST_SEGMENT = 2;
public static final String TAG = TrackingOverlay.class.getName();
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;
public static final String TRACKCOLORING = "trackcoloring";
private int trackColoringMethod = DRAW_CALCULATED;
private ContentResolver mResolver;
private Context mContext;
private Projection mProjection;
private int mPlacement = TrackingOverlay.MIDDLE_SEGMENT;
private Uri mSegmentUri;
private double mAvgSpeed;
private GeoPoint mTopLeft;
private GeoPoint mBottumRight;
private Path mPath;
private Canvas mCanvas;
private Shader mShader;
private GeoPoint mStartPoint;
private GeoPoint mEndPoint;
private int mCalculatedPoints;
private Point mPrevScreenPoint;
private Point mScreenPoint;
private int stepSize = 1;
private int step = 0;
private MapView mMapView;
TrackingOverlay( Context cxt, ContentResolver resolver, Uri segmentUri, int color, double avgSpeed, MapView mapView )
{
super();
this.mContext = cxt;
this.mMapView = mapView;
this.trackColoringMethod = color;
this.mAvgSpeed = avgSpeed;
this.mPath = new Path();
this.mResolver = resolver;
this.mSegmentUri = segmentUri;
}
@Override
public void draw( Canvas canvas, MapView mapView, boolean shadow )
{
switch (trackColoringMethod)
{
case ( DRAW_CALCULATED ):
case ( DRAW_MEASURED ):
case ( DRAW_RED ):
case ( DRAW_GREEN ):
drawPath( canvas, mapView, shadow );
break;
case( DRAW_DOTS ):
drawDots( canvas, mapView, shadow );
break;
}
}
/**
*
* @param canvas
* @param mapView
* @param shadow
*
* @see TrackingOverlay#draw(Canvas, MapView, boolean)
*/
private void drawDots( Canvas canvas, MapView mapView, boolean shadow )
{
this.mCanvas = canvas;
this.mScreenPoint = new Point();
this.mPrevScreenPoint = new Point();
mProjection = mapView.getProjection();
mTopLeft = mProjection.fromPixels( 0, 0 );
mBottumRight = mProjection.fromPixels( this.mCanvas.getWidth(), this.mCanvas.getHeight() );
transformSegmentToCanvasDots();
drawStartStopCircles();
super.draw( this.mCanvas, mapView, shadow );
this.mCanvas = null;
}
/**
*
* @param canvas
* @param mapView
* @param shadow
*
* @see TrackingOverlay#draw(Canvas, MapView, boolean)
*/
public void drawPath( Canvas canvas, MapView mapView, boolean shadow )
{
this.mCanvas = canvas;
this.mScreenPoint = new Point();
this.mPrevScreenPoint = new Point();
mProjection = mapView.getProjection();
mTopLeft = mProjection.fromPixels( 0, 0 );
mBottumRight = mProjection.fromPixels( this.mCanvas.getWidth(), this.mCanvas.getHeight() );
this.mPath.rewind();
this.mShader = null;
transformSegmentToPath();
// Just the rendering bits left to do
Paint routePaint = new Paint();
routePaint.setPathEffect( new CornerPathEffect( 10 ) );
// Log.d( TAG, "Drawing color is "+trackColoringMethod );
switch (trackColoringMethod)
{
case ( DRAW_CALCULATED ):
case ( DRAW_MEASURED ):
routePaint.setShader( this.mShader );
break;
case ( DRAW_RED ):
routePaint.setColor( Color.RED );
break;
case ( DRAW_GREEN ):
default:
routePaint.setColor( Color.GREEN );
break;
}
routePaint.setStyle( Paint.Style.STROKE );
routePaint.setStrokeWidth( 8 );
routePaint.setAntiAlias( true );
this.mCanvas.drawPath( this.mPath, routePaint );
drawStartStopCircles();
super.draw( this.mCanvas, mapView, shadow );
this.mCanvas = null;
}
private void drawStartStopCircles()
{
Bitmap bitmap;
if( (this.mPlacement == FIRST_SEGMENT || this.mPlacement == FIRST_SEGMENT + LAST_SEGMENT) && this.mStartPoint != null)
{
Point out = new Point();
mProjection.toPixels( this.mStartPoint, out ) ;
mCalculatedPoints++;
bitmap = BitmapFactory.decodeResource( this.mContext.getResources(), R.drawable.stip2 );
this.mCanvas.drawBitmap( bitmap, out.x - 8, out.y - 8, new Paint() );
}
if( (this.mPlacement == LAST_SEGMENT || this.mPlacement == FIRST_SEGMENT + LAST_SEGMENT) && this.mEndPoint != null )
{
Point out = new Point();
mProjection.toPixels( this.mEndPoint, out ) ;
mCalculatedPoints++;
bitmap = BitmapFactory.decodeResource( this.mContext.getResources(), R.drawable.stip );
this.mCanvas.drawBitmap( bitmap, out.x - 5, out.y - 5, new Paint() );
}
}
/**
* Set the mPlace to the specified value.
*
* @see TrackingOverlay.FIRST
* @see TrackingOverlay.MIDDLE
* @see TrackingOverlay.LAST
* @param place The placement of this segment in the line.
*/
public void setPlacement( int place )
{
this.mPlacement += place;
}
private void transformSegmentToCanvasDots()
{
Cursor trackCursor = null;
GeoPoint geoPoint;
mCalculatedPoints = 0;
setStepSize();
step = 0;
try
{
trackCursor = this.mResolver.query(
this.mSegmentUri,
new String[] { Waypoints.LATITUDE, Waypoints.LONGITUDE, Waypoints.ACCURACY }
, null, null, null );
if( trackCursor.moveToFirst() )
{
// Start point of the segments, possible a dot
this.mStartPoint = extractGeoPoint( trackCursor );
moveToGeoPoint( this.mStartPoint );
Paint radiusPaint = new Paint();
radiusPaint.setColor( Color.YELLOW );
radiusPaint.setAlpha( 100 );
do
{
geoPoint = extractGeoPoint( trackCursor );
Point out = new Point();
mProjection.toPixels( geoPoint, out ) ;
mCalculatedPoints++;
Bitmap bitmap = BitmapFactory.decodeResource( this.mContext.getResources(), R.drawable.stip2 );
this.mCanvas.drawBitmap( bitmap, out.x - 8, out.y - 8, new Paint() );
float radius = mProjection.metersToEquatorPixels( trackCursor.getFloat( 2 ) );
if( radius > 8f )
{
this.mCanvas.drawCircle( out.x, out.y, radius, radiusPaint );
}
this.mPrevScreenPoint.x = this.mScreenPoint.x;
this.mPrevScreenPoint.y = this.mScreenPoint.y;
}
while( moveToNextWayPoint( trackCursor ) );
// End point of the segments, possible a dot
this.mEndPoint = extractGeoPoint( trackCursor );
}
}
finally
{
if( trackCursor != null )
{
trackCursor.close();
}
}
// Log.d( TAG, "transformSegmentToPath stop: points "+mCalculatedPoints );
}
/**
* Convert the cursor from the GPSTracking provider into Points on the Path
*
* @see Cursor Cursor used as input
* @see Point Point used as transformation target
* @see Path Path used as drawable line
*/
private void transformSegmentToPath()
{
Cursor trackCursor = null;
Location location = null;
Location prevLocation = null;
GeoPoint geoPoint;
mCalculatedPoints = 0;
setStepSize();
step = 0;
try
{
switch (trackColoringMethod)
{
case ( DRAW_CALCULATED ):
case ( DRAW_MEASURED ):
trackCursor = this.mResolver.query( this.mSegmentUri, new String[] { Waypoints.LATITUDE, Waypoints.LONGITUDE, Waypoints.SPEED, Waypoints.TIME }, null, null, null );
break;
case ( DRAW_GREEN ):
case ( DRAW_RED ):
default:
trackCursor = this.mResolver.query( this.mSegmentUri, new String[] { Waypoints.LATITUDE, Waypoints.LONGITUDE }, null, null, null );
break;
}
if( trackCursor.moveToFirst() )
{
// Start point of the segments, possible a dot
this.mStartPoint = extractGeoPoint( trackCursor );
moveToGeoPoint( this.mStartPoint );
do
{
geoPoint = extractGeoPoint( trackCursor );
switch (trackColoringMethod)
{
case DRAW_RED:
lineToGeoPoint( geoPoint, -1d );
break;
case DRAW_MEASURED:
lineToGeoPoint( geoPoint, trackCursor.getDouble( 2 ) );
break;
case DRAW_CALCULATED:
double speed = -1d;
location = new Location( this.getClass().getName() );
location.setLatitude( trackCursor.getDouble( 0 ) );
location.setLongitude( trackCursor.getDouble( 1 ) );
location.setTime( trackCursor.getLong( 3 ) );
float distance = 0;
if( prevLocation != null )
{
distance = prevLocation.distanceTo( location );
}
else
{
prevLocation = location;
}
float seconds = ( ( location.getTime() - prevLocation.getTime() ) / 1000f );
speed = distance / seconds;
prevLocation = location;
lineToGeoPoint( geoPoint, speed );
break;
default:
lineToGeoPoint( geoPoint, -1d );
break;
}
}
while( moveToNextWayPoint( trackCursor ) );
// End point of the segments, possible a dot
this.mEndPoint = extractGeoPoint( trackCursor );
}
}
finally
{
if( trackCursor != null )
{
trackCursor.close();
}
}
// Log.d( TAG, "transformSegmentToPath stop: points "+mCalculatedPoints );
}
private void moveToGeoPoint( GeoPoint geoPoint )
{
this.mProjection.toPixels( geoPoint, this.mScreenPoint );
mCalculatedPoints++;
this.mPath.moveTo( this.mScreenPoint.x, this.mScreenPoint.y );
}
private void lineToGeoPoint( GeoPoint geoPoint, double speed )
{
this.mProjection.toPixels( geoPoint, this.mScreenPoint );
mCalculatedPoints++;
this.mPath.lineTo( this.mScreenPoint.x, this.mScreenPoint.y );
if( speed > 0 )
{
int greenfactor = (int) Math.min( ( 127 * speed ) / mAvgSpeed, 255 );
int redfactor = 255 - greenfactor;
int currentColor = Color.rgb( redfactor, greenfactor, 0 );
Shader s = new LinearGradient( this.mPrevScreenPoint.x, this.mPrevScreenPoint.y, extendPoint( this.mPrevScreenPoint.x, this.mScreenPoint.x ), extendPoint( this.mPrevScreenPoint.y, this.mScreenPoint.y ),
new int[] { Color.TRANSPARENT, currentColor, Color.TRANSPARENT }, new float[] { 0, 0.5f, 1 }, TileMode.CLAMP );
//Log.d( TAG, "Created shader for speed " + speed + " with greenfactor " + greenfactor );
if( this.mShader != null )
{
this.mShader = new ComposeShader( this.mShader, s, Mode.XOR );
}
else
{
this.mShader = s;
}
}
this.mPrevScreenPoint.x = this.mScreenPoint.x;
this.mPrevScreenPoint.y = this.mScreenPoint.y;
}
private float extendPoint( int x1, int x2 )
{
int diff = x2 - x1;
int next = x2 + diff;
return next;
}
private boolean moveToNextWayPoint( Cursor trackCursor )
{
if( trackCursor.isLast() )
{
return false;
}
GeoPoint lastPoint = extractGeoPoint( trackCursor );
boolean onScreen = isOnScreen( lastPoint );
GeoPoint evalPoint;
if( onScreen )
{
while( trackCursor.moveToNext() )
{
step++;
if( trackCursor.isLast() )
{
// Log.d(TAG, "last on screen "+trackCursor.getPosition() );
return true;
}
evalPoint = extractGeoPoint( trackCursor );
if( !isOnScreen( evalPoint ) )
{
// Log.d(TAG, "first out screen "+trackCursor.getPosition() );
return true;
}
if( isGoodDrawable() )
{
return true;
}
}
trackCursor.moveToLast();
return false;
}
else
{
while( trackCursor.moveToNext() )
{
step++;
if( trackCursor.isLast() )
{
// Log.d(TAG, "last off screen "+trackCursor.getPosition() );
return true;
}
evalPoint = extractGeoPoint( trackCursor );
if( isOnScreen( evalPoint ) )
{
moveToGeoPoint( lastPoint );
// Log.d(TAG, "first in screen "+trackCursor.getPosition() );
return true;
}
lastPoint = evalPoint;
}
trackCursor.moveToLast();
return false;
}
}
private GeoPoint extractGeoPoint( Cursor trackCursor )
{
int microLatitude = (int) ( trackCursor.getDouble( 0 ) * 1E6d );
int microLongitude = (int) ( trackCursor.getDouble( 1 ) * 1E6d );
return new GeoPoint( microLatitude, microLongitude );
}
private boolean isGoodDrawable()
{
return step % stepSize == 0;
}
private void setStepSize()
{
int zoomLevel = mMapView.getZoomLevel();
int maxZoomLevel = mMapView.getMaxZoomLevel();
if( mMapView != null && zoomLevel >= maxZoomLevel-1 )
{
stepSize = 1;
}
else
{
stepSize = (maxZoomLevel - zoomLevel)*2;
}
// Log.d( TAG, "Setting stepSize "+stepSize+" on a zoom of "+zoomLevel+"/"+maxZoomLevel );
}
private boolean isOnScreen( GeoPoint eval )
{
boolean under = this.mTopLeft.getLatitudeE6() > eval.getLatitudeE6();
boolean above = this.mBottumRight.getLatitudeE6() < eval.getLatitudeE6();
boolean right = this.mTopLeft.getLongitudeE6() < eval.getLongitudeE6();
boolean left = this.mBottumRight.getLongitudeE6() > eval.getLongitudeE6();
return under && above && right && left;
}
public void setTrackColoringMethod( int coloring )
{
this.trackColoringMethod = coloring;
}
}