/*------------------------------------------------------------------------------ ** 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 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 75 2009-10-18 13:57:09Z rcgroot@gmail.com $ * @author rene (c) Jan 11, 2009, Sogeti B.V. */ public class TrackingOverlay extends Overlay { public static final int MIDDLE = 0; public static final int FIRST = 1; public static final int LAST = 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 String TRACKCOLORING = "trackcoloring"; private int trackColoringMethod = DRAW_CALCULATED; private ContentResolver mResolver; private Point mStartPoint; private Point mEndPoint; private Point mRecylcePoint; private Location mLocation; private Point mPrevPoint; private int mPrevPosition; private int mPrevXcol; private int mPrevYcol; private Location mPrevLocation; private Path mPath; private int mPlacement = TrackingOverlay.MIDDLE; private Projection mProjection; private Uri mSegmentUri; private Context mContext; private int mListPosition; private int mStepSize; private int mCalculatedPoints; private Canvas mCanvas; private Shader mShader; private double mAvgSpeed; TrackingOverlay( Context cxt, ContentResolver resolver, Uri segmentUri, int color, double avgSpeed ) { super(); this.mContext = cxt; this.trackColoringMethod = color; this.mAvgSpeed = avgSpeed; this.mPath = new Path(); this.mResolver = resolver; this.mSegmentUri = segmentUri; } /** * (non-Javadoc) * * @see com.google.android.maps.Overlay#draw(android.graphics.Canvas, com.google.android.maps.MapView, boolean) */ @Override public void draw( Canvas canvas, MapView mapView, boolean shadow ) { // Holder of the onscreen Points are reset this.mStartPoint = new Point(); this.mEndPoint = new Point(); this.mRecylcePoint = new Point(); this.mPrevPoint = new Point(); this.mPath.rewind(); this.mListPosition = 0; this.mStepSize = 10; mPrevXcol = 2; mPrevYcol = 2; this.mCanvas = canvas; this.mShader = null; // The current state with all the Points must be recalculated // because the projecting of the map me be different then // the last call (the map moved, redraw the route to move along) this.mProjection = mapView.getProjection(); transformAllWaypointsToPath(); this.mProjection = null; // 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 ); Bitmap bitmap; if( this.mPlacement == FIRST || this.mPlacement == FIRST + LAST ) { bitmap = BitmapFactory.decodeResource( this.mContext.getResources(), R.drawable.stip2 ); this.mCanvas.drawBitmap( bitmap, this.mStartPoint.x - 8, this.mStartPoint.y - 8, new Paint() ); } if( this.mPlacement == LAST || this.mPlacement == FIRST + LAST ) { bitmap = BitmapFactory.decodeResource( this.mContext.getResources(), R.drawable.stip ); this.mCanvas.drawBitmap( bitmap, this.mEndPoint.x - 5, this.mEndPoint.y - 5, new Paint() ); } // Log.d( TAG, this.mTrackUri+" transformerd number of points: " + mCalculatedPoints ); super.draw( this.mCanvas, mapView, shadow ); this.mCanvas = null; } /** * 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; } /** * 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 transformAllWaypointsToPath() { Cursor trackCursor = null; mCalculatedPoints = 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() ) { transformSingleWaypointToCurrentPoint( trackCursor.getDouble( 0 ), trackCursor.getDouble( 1 ) ); this.mStartPoint.set( this.mRecylcePoint.x, this.mRecylcePoint.y ); this.mPath.moveTo( this.mRecylcePoint.x, this.mRecylcePoint.y ); while (moveToNextWayPoint( trackCursor )) { transformSingleWaypointToCurrentPoint( trackCursor.getDouble( 0 ), trackCursor.getDouble( 1 ) ); switch (trackColoringMethod) { case DRAW_RED: drawPointToPath( -1d ); break; case DRAW_MEASURED: drawPointToPath( trackCursor.getDouble( 2 ) ); break; case DRAW_CALCULATED: double speed = -1d; this.mLocation = new Location( this.getClass().getName() ); this.mLocation.setLatitude( trackCursor.getDouble( 0 ) ); this.mLocation.setLongitude( trackCursor.getDouble( 1 ) ); this.mLocation.setTime( trackCursor.getLong( 3 ) ); if( this.mPrevLocation != null ) { float distance = this.mPrevLocation.distanceTo( this.mLocation ); float seconds = ( ( this.mLocation.getTime() - this.mPrevLocation.getTime() ) / 1000f ); speed = distance / seconds; //Log.d( TAG, "Calculated speed:"+speed+" for seconds: "+seconds+" over distance "+distance ); } drawPointToPath( speed ); break; default: drawPointToPath( -1d ); break; } } this.mEndPoint.set( this.mRecylcePoint.x, this.mRecylcePoint.y ); } } finally { if( trackCursor != null ) { trackCursor.close(); } } } /** * The the waypoint in the cursor is converted into the the point based on the projection */ private void transformSingleWaypointToCurrentPoint( double lat, double lon ) { int microLatitude = (int) ( lat * 1E6d ); int microLongitude = (int) ( lon * 1E6d ); this.mProjection.toPixels( new GeoPoint( microLatitude, microLongitude ), this.mRecylcePoint ); mCalculatedPoints++; } private void drawPointToPath( double speed ) { if( correctCurrentPoint() ) { return; } this.mPath.lineTo( this.mRecylcePoint.x, this.mRecylcePoint.y ); if( speed > 0 && ( inFrame( this.mRecylcePoint.x, this.mRecylcePoint.y ) || inFrame( this.mPrevPoint.x, this.mPrevPoint.y ) ) ) { 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.mPrevPoint.x, this.mPrevPoint.y, extendPoint( this.mPrevPoint.x, this.mRecylcePoint.x ), extendPoint( this.mPrevPoint.y, this.mRecylcePoint.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; } } // Determine how much line this new point adds int diff = Math.abs( this.mRecylcePoint.x - this.mPrevPoint.x ) + Math.abs( this.mRecylcePoint.y - this.mPrevPoint.y ); adjustStepSize( diff ); this.mPrevLocation = this.mLocation; this.mPrevPoint.x = this.mRecylcePoint.x; this.mPrevPoint.y = this.mRecylcePoint.y; } private float extendPoint( int x1, int x2 ) { int diff = x2 - x1; int next = x2 + diff; return next; } private boolean inFrame( int x, int y ) { return x > 0 && y > 0 && x < mCanvas.getWidth() && y < mCanvas.getHeight(); } private boolean moveToNextWayPoint( Cursor trackCursor ) { if( mListPosition > trackCursor.getCount() ) { return false; } mPrevPosition = mListPosition; mListPosition += mStepSize; if( mListPosition > trackCursor.getCount() ) { return trackCursor.moveToLast(); } else { return trackCursor.moveToPosition( mListPosition ); } } private boolean correctCurrentPoint() { int currentXcol, currentYcol; // Determine whether this new point lies compared to the viewing frame if( this.mRecylcePoint.x > this.mCanvas.getWidth() ) { currentXcol = 3; } else if( this.mRecylcePoint.x >= 0 ) { currentXcol = 2; } else if( this.mRecylcePoint.x < 0 ) { currentXcol = 1; } else { currentXcol = -1; } if( this.mRecylcePoint.y > this.mCanvas.getHeight() ) { currentYcol = 3; } else if( this.mRecylcePoint.y >= 0 ) { currentYcol = 2; } else if( this.mRecylcePoint.y < 0 ) { currentYcol = 1; } else { currentYcol = -1; } if( mPrevXcol != 2 || mPrevYcol != 2 ) // Outside the frame { // Log.d( TAG, "Outside: Picking up the step size from "+mStepSize+" to "+(mStepSize+5) ); mStepSize += 5; } if( ( mPrevXcol != currentXcol || mPrevYcol != currentYcol ) ) { boolean crossed = false; switch (mPrevXcol) { case ( 1 ): if( currentXcol > 1 ) { switch (mPrevYcol) { case ( 1 ): crossed = currentYcol > 1; break; case ( 2 ): crossed = true; break; case ( 3 ): crossed = currentYcol < 3; break; } } ; break; case ( 2 ): switch (mPrevYcol) { case ( 1 ): crossed = currentYcol > 1; break; case ( 2 ): crossed = true; break; case ( 3 ): crossed = currentYcol < 3; break; } break; case ( 3 ): if( currentXcol < 3 ) { switch (mPrevYcol) { case ( 1 ): crossed = currentYcol > 1; break; case ( 2 ): crossed = true; break; case ( 3 ): crossed = currentYcol < 3; break; } } ; break; } //Log.d( TAG, "Switching kwadrant from ("+mPrevXcol+","+mPrevYcol+") to ("+currentXcol+","+currentYcol+") + and crossed: "+crossed); if( crossed && mListPosition > ( mPrevPosition + 1 ) ) { mListPosition = mPrevPosition; // int smallerstep = Math.max( 1, mStepSize / 2 ); // Log.d( TAG, "Crossing: Taking back the step size from "+mStepSize+" to "+1 ); mStepSize = 1; return true; } } //Log.d( TAG, "Point "+ mListPosition +" remained in quadrant ("+currentXcol+","+currentYcol+")" ); mPrevXcol = currentXcol; mPrevYcol = currentYcol; return false; } private void adjustStepSize( int diff ) { if( diff > 20 && mStepSize > 1 ) { // Log.d( TAG, "Big steps: Taking back the step size from "+mStepSize+" to "+(mStepSize-1) ); mStepSize--; } else if( diff < 10 ) { // Log.d( TAG, "Small steps: Picking up the step size from "+mStepSize+" to "+(mStepSize*2) ); mStepSize *= 2; } } public void setTrackColoringMethod( int coloring ) { this.trackColoringMethod = coloring; } }