GraphCanvas.java :  » GPS » open-gpstracker » nl » sogeti » android » gpstracker » actions » utils » Android Open Source

Android Open Source » GPS » open gpstracker 
open gpstracker » nl » sogeti » android » gpstracker » actions » utils » GraphCanvas.java
/*------------------------------------------------------------------------------
 **     Ident: Delivery Center Java
 **    Author: rene
 ** Copyright: (c) Jan 21, 2010 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.actions.utils;

import java.text.DateFormat;
import java.util.Date;

import nl.sogeti.android.gpstracker.R;
import nl.sogeti.android.gpstracker.db.GPStracking.Segments;
import nl.sogeti.android.gpstracker.db.GPStracking.Waypoints;
import nl.sogeti.android.gpstracker.util.Constants;
import nl.sogeti.android.gpstracker.util.UnitsI18n;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.CornerPathEffect;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Typeface;
import android.location.Location;
import android.net.Uri;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

/**
 * Calculate and draw graphs of track data
 * 
 * @version $Id: GraphCanvas.java 888 2011-03-14 19:44:44Z rcgroot@gmail.com $
 * @author rene (c) Mar 22, 2009, Sogeti B.V.
 */
public class GraphCanvas extends View
{
   @SuppressWarnings("unused")
   private static final String TAG = "OGT.GraphCanvas";
   public static final int TIMESPEEDGRAPH = 0;
   public static final int DISTANCESPEEDGRAPH = 1;
   public static final int TIMEALTITUDEGRAPH = 2;
   public static final int DISTANCEALTITUDEGRAPH = 3;
   private Uri mUri;
   private Bitmap mRenderBuffer;
   private Canvas mRenderCanvas;
   private Context mContext;
   private UnitsI18n mUnits;
   private int mGraphType = -1;
   private long mEndTime;
   private long mStartTime;
   private double mDistance;
   private int mHeight;
   private int mWidth;
   private int mMinAxis;
   private int mMaxAxis;
   private double mMinAlititude;
   private double mMaxAlititude;
   private double mHighestSpeedNumber;
   private double mDistanceDrawn;
   private long mStartTimeDrawn;
   private long mEndTimeDrawn;
   float density = Resources.getSystem().getDisplayMetrics().density;
   
   private Paint whiteText ;
   private Paint ltgreyMatrixDashed;
   private Paint greenGraphLine;
   private Paint dkgreyMatrixLine;
   private Paint whiteCenteredText;
   private Paint dkgrayLargeType;
      
   public GraphCanvas( Context context, AttributeSet attrs )
   {
      this(context, attrs, 0);
   }
   
   public GraphCanvas( Context context, AttributeSet attrs, int defStyle )
   {
      super(context, attrs, defStyle);
      
      mContext = context;
      
      whiteText = new Paint();
      whiteText.setColor( Color.WHITE );
      whiteText.setAntiAlias( true );
      whiteText.setTextSize( (int)(density * 12)  );
      
      whiteCenteredText = new Paint();
      whiteCenteredText.setColor( Color.WHITE );
      whiteCenteredText.setAntiAlias( true );
      whiteCenteredText.setTextAlign( Paint.Align.CENTER );
      whiteCenteredText.setTextSize( (int)(density * 12)  );
      
      ltgreyMatrixDashed = new Paint();
      ltgreyMatrixDashed.setColor( Color.LTGRAY );
      ltgreyMatrixDashed.setStrokeWidth( 1 );
      ltgreyMatrixDashed.setPathEffect( new DashPathEffect( new float[]{2,4}, 0 ) );
      
      greenGraphLine = new Paint();
      greenGraphLine.setPathEffect( new CornerPathEffect( 8 ) );
      greenGraphLine.setStyle( Paint.Style.STROKE );
      greenGraphLine.setStrokeWidth( 4 );
      greenGraphLine.setAntiAlias( true );
      greenGraphLine.setColor(Color.GREEN);
      
      dkgreyMatrixLine = new Paint();
      dkgreyMatrixLine.setColor( Color.DKGRAY );
      dkgreyMatrixLine.setStrokeWidth( 2 );
      
      dkgrayLargeType = new Paint();
      dkgrayLargeType.setColor( Color.LTGRAY );
      dkgrayLargeType.setAntiAlias( true );
      dkgrayLargeType.setTextAlign( Paint.Align.CENTER );
      dkgrayLargeType.setTextSize( (int)(density * 21) );
      dkgrayLargeType.setTypeface( Typeface.DEFAULT_BOLD );
   }
   
   /**
    * Set the dataset for which to draw data. Also provide hints and helpers.
    * 
    * @param uri
    * @param startTime
    * @param endTime
    * @param distance
    * @param minAlititude
    * @param maxAlititude
    * @param maxSpeed
    * @param units
    */
   public void setData( Uri uri, StatisticsCalulator calc )
   { 
      boolean rerender = false;
      if( uri.equals( mUri ) )
      {
         double distanceDrawnPercentage = mDistanceDrawn / mDistance;
         double duractionDrawnPercentage = (double)((1d+mEndTimeDrawn-mStartTimeDrawn) / (1d+mEndTime-mStartTime));
         rerender = distanceDrawnPercentage < 0.99d || duractionDrawnPercentage < 0.99d;
      }
      else
      {
         if( mRenderBuffer == null && super.getWidth() > 0 && super.getHeight() > 0 )
         {
            initRenderBuffer(super.getWidth(), super.getHeight());
         }
         rerender = true;
      }
      
      mUri          = uri;
      mUnits        = calc.getUnits();
      
      mMinAlititude = mUnits.conversionFromMeterToHeight( calc.getMinAltitude() );
      mMaxAlititude = mUnits.conversionFromMeterToHeight( calc.getMaxAltitude() );
      if( mUnits.isUnitFlipped() )
      {
         mHighestSpeedNumber     = 1.5 * mUnits.conversionFromMetersPerSecond( calc.getAverageStatisicsSpeed() );
      }
      else
      {
         mHighestSpeedNumber     = mUnits.conversionFromMetersPerSecond( calc.getMaxSpeed() );
      }
      mStartTime    = calc.getStarttime();
      mEndTime      = calc.getEndtime();
      mDistance     = calc.getDistanceTraveled();
      
      if( rerender )
      {
         renderGraph();
      }
   }
   
   public synchronized void clearData()
   {
      mUri = null;
      mUnits = null;
      mRenderBuffer = null;
   }

   public void setType( int graphType)
   {
      if( mGraphType != graphType )
      {
         mGraphType = graphType;
         renderGraph();
      }
   }
   
   public int getType()
   {
      return mGraphType;
   }

   @Override
   protected synchronized void onSizeChanged( int w, int h, int oldw, int oldh )
   {
      super.onSizeChanged( w, h, oldw, oldh );
      initRenderBuffer(w, h);

      renderGraph();
   }

   private void initRenderBuffer(int w, int h)
   {
      mRenderBuffer = Bitmap.createBitmap( w, h, Config.ARGB_8888 );
      mRenderCanvas = new Canvas( mRenderBuffer );
   }

   @Override
   protected synchronized void onDraw( Canvas canvas )
   {
      super.onDraw(canvas);
      if( mRenderBuffer != null )
      {
         canvas.drawBitmap( mRenderBuffer, 0, 0, null );
      }
   }

   private synchronized void renderGraph()
   {
      if( mRenderBuffer != null && mUri != null )
      {
         mRenderBuffer.eraseColor( Color.TRANSPARENT );
         switch( mGraphType )
         {
            case( TIMESPEEDGRAPH ):
               setupSpeedAxis();
               drawGraphType();
               drawTimeAxisGraphOnCanvas( new String[] { Waypoints.TIME, Waypoints.SPEED }, Constants.MIN_STATISTICS_SPEED );
               drawSpeedsTexts();
               drawTimeTexts();
               break;
            case( DISTANCESPEEDGRAPH ):
               setupSpeedAxis();
               drawGraphType();
               drawDistanceAxisGraphOnCanvas(  new String[] { Waypoints.LONGITUDE, Waypoints.LATITUDE, Waypoints.SPEED }, Constants.MIN_STATISTICS_SPEED );
               drawSpeedsTexts();
               drawDistanceTexts();
               break;
            case( TIMEALTITUDEGRAPH ):
               setupAltitudeAxis();
               drawGraphType();
               drawTimeAxisGraphOnCanvas( new String[] { Waypoints.TIME, Waypoints.ALTITUDE }, -1000d );
               drawAltitudesTexts();
               drawTimeTexts();
               break;
            case( DISTANCEALTITUDEGRAPH ):
               setupAltitudeAxis();
               drawGraphType();
               drawDistanceAxisGraphOnCanvas( new String[] { Waypoints.LONGITUDE, Waypoints.LATITUDE, Waypoints.ALTITUDE }, -1000d );
               drawAltitudesTexts();
               drawDistanceTexts();
               break;
            default:
               break;
         }
         mDistanceDrawn = mDistance;
         mStartTimeDrawn = mStartTime;
         mEndTimeDrawn = mEndTime;
      }

      postInvalidate();
   }
      
   /**
    * 
    * @param params
    * @param minValue Minimum value of params[1] that will be drawn
    */
   private void drawDistanceAxisGraphOnCanvas( String[] params, double minValue )
   {
      ContentResolver resolver = mContext.getContentResolver();
      Uri segmentsUri = Uri.withAppendedPath( mUri, "segments" );
      Uri waypointsUri = null;
      Cursor segments = null;
      Cursor waypoints = null;
      double[][] values ;
      int[][] valueDepth;
      double distance = 1;
      try 
      {
         segments = resolver.query( 
               segmentsUri, 
               new String[]{ Segments._ID }, 
               null, null, null );
         int segmentCount = segments.getCount();
         values = new double[segmentCount][mWidth];
         valueDepth = new int[segmentCount][mWidth];
         if( segments.moveToFirst() )
         {
            for(int segment=0;segment<segmentCount;segment++)
            {
               segments.moveToPosition( segment );
               long segmentId = segments.getLong( 0 );
               waypointsUri = Uri.withAppendedPath( segmentsUri, segmentId+"/waypoints" );
               try
               {
                  waypoints = resolver.query( 
                     waypointsUri, 
                     params, 
                     null, null, null );
                  if( waypoints.moveToFirst() )
                  {                    
                     Location lastLocation = null;
                     Location currentLocation = null;
                     do 
                     {
                        currentLocation = new Location( this.getClass().getName() );
                        currentLocation.setLongitude( waypoints.getDouble( 0 ) );
                        currentLocation.setLatitude( waypoints.getDouble( 1 ) );
                        if( lastLocation != null )
                        {
                           distance += lastLocation.distanceTo( currentLocation );
                        }
                        lastLocation = currentLocation;
                        double value = waypoints.getDouble( 2 );
                        if( value > minValue && segment < values.length )
                        {
                           int x = (int) ((distance)*(mWidth-1) / mDistance);
                           if( x < valueDepth[segment].length )
                           {
                              valueDepth[segment][x]++;
                              values[segment][x] = values[segment][x]+((value-values[segment][x])/valueDepth[segment][x]);
                           }
                        }
                     }
                     while( waypoints.moveToNext() );
                  }
               }
               finally
               {
                  if( waypoints != null )
                  {
                     waypoints.close();
                  }
               }
            }
         }
      }
      finally
      {
         if( segments != null )
         {
            segments.close();
         }
      }
      for( int segment=0;segment<values.length;segment++)
      {
         for( int x=0;x<values[segment].length;x++)
         {
            if( valueDepth[segment][x] > 0 )
            {
               values[segment][x] = translateValue( values[segment][x] );               
            }
         }
      }
      drawGraph( values, valueDepth );      
   }
   
   private void drawTimeAxisGraphOnCanvas( String[] params, double minValue )
   {
      ContentResolver resolver = mContext.getContentResolver();
      Uri segmentsUri = Uri.withAppendedPath( mUri, "segments" );
      Uri waypointsUri = null;
      Cursor segments = null;
      Cursor waypoints = null;
      long duration = 1+mEndTime - mStartTime;
      double[][] values ;
      int[][] valueDepth;
      try 
      {
         segments = resolver.query( 
               segmentsUri, 
               new String[]{ Segments._ID }, 
               null, null, null );
         int segmentCount = segments.getCount();
         values = new double[segmentCount][mWidth];
         valueDepth = new int[segmentCount][mWidth];
         if( segments.moveToFirst() )
         {
            for(int segment=0;segment<segmentCount;segment++)
            {
               segments.moveToPosition( segment );
               long segmentId = segments.getLong( 0 );
               waypointsUri = Uri.withAppendedPath( segmentsUri, segmentId+"/waypoints" );
               try
               {
                  waypoints = resolver.query( 
                     waypointsUri, 
                     params, 
                     null, null, null );
                  if( waypoints.moveToFirst() )
                  {
                     do 
                     {
                        long time = waypoints.getLong( 0 );
                        double value = waypoints.getDouble( 1 );
                        if( value > minValue && segment < values.length )
                        {
                           int x = (int) ((time-mStartTime)*(mWidth-1) / duration);
                           if( x < valueDepth[segment].length )
                           {
                              valueDepth[segment][x]++;
                              values[segment][x] = values[segment][x]+((value-values[segment][x])/valueDepth[segment][x]);
                           }
                        }
                     }
                     while( waypoints.moveToNext() );
                  }
               }
               finally
               {
                  if( waypoints != null )
                  {
                     waypoints.close();
                  }
               }
            }
            
         }
      }
      finally
      {
         if( segments != null )
         {
            segments.close();
         }
      }
      for( int p=0;p<values.length;p++)
      {
         for( int x=0;x<values[p].length;x++)
         {
            if( valueDepth[p][x] > 0 )
            {
               values[p][x] = translateValue( values[p][x] );
            }
         }
      }      
      drawGraph( values, valueDepth );
   }

   private void setupAltitudeAxis()
   {
      mMinAxis = -4 + 4 * (int)(mMinAlititude / 4);
      mMaxAxis =  4 + 4 * (int)(mMaxAlititude / 4);

      mWidth = mRenderCanvas.getWidth()-5;
      mHeight = mRenderCanvas.getHeight()-10;
   }

   private void setupSpeedAxis()
   {
      mMinAxis = 0;
      mMaxAxis = 4 + 4 * (int)( mHighestSpeedNumber / 4);

      mWidth = mRenderCanvas.getWidth()-5;
      mHeight = mRenderCanvas.getHeight()-10;
   }

   private void drawAltitudesTexts()
   {
      mRenderCanvas.drawText( String.format( "%d %s", mMinAxis, mUnits.getHeightUnit() )  , 8,  mHeight, whiteText );
      mRenderCanvas.drawText( String.format( "%d %s", (mMaxAxis+mMinAxis)/2, mUnits.getHeightUnit() ) , 8,  5+mHeight/2, whiteText );
      mRenderCanvas.drawText( String.format( "%d %s", mMaxAxis, mUnits.getHeightUnit() ), 8,  15, whiteText );
   }

   private void drawSpeedsTexts()
   {
      mRenderCanvas.drawText( String.format( "%d %s", mMinAxis, mUnits.getSpeedUnit() )              , 8,  mHeight, whiteText );
      mRenderCanvas.drawText( String.format( "%d %s", (mMaxAxis+mMinAxis)/2, mUnits.getSpeedUnit() ) , 8,  3+mHeight/2, whiteText );
      mRenderCanvas.drawText( String.format( "%d %s", mMaxAxis, mUnits.getSpeedUnit() )              , 8,  7+whiteText.getTextSize(), whiteText );
   }
   
   private void drawGraphType()
   {
      //float density = Resources.getSystem().getDisplayMetrics().density;
      String text;
      switch( mGraphType )
      {
         case( TIMESPEEDGRAPH ):
            text = mContext.getResources().getString( R.string.graphtype_timespeed );
            break;
         case( DISTANCESPEEDGRAPH ):
            text = mContext.getResources().getString( R.string.graphtype_distancespeed );
            break;
         case( TIMEALTITUDEGRAPH ):
            text = mContext.getResources().getString( R.string.graphtype_timealtitude );
            break;
         case( DISTANCEALTITUDEGRAPH ):
            text = mContext.getResources().getString( R.string.graphtype_distancealtitude );
            break;
         default:
            text = "UNKNOWN GRAPH TYPE";
            break;
      }
      mRenderCanvas.drawText( text, 5+mWidth/2, 5+mHeight/8, dkgrayLargeType );
      
   }
   private void drawTimeTexts()
   {
      DateFormat timeInstance = DateFormat.getTimeInstance( DateFormat.SHORT );
      String start = timeInstance.format( new Date( mStartTime ) );
      String half  = timeInstance.format( new Date( (mEndTime+mStartTime)/2 ) );
      String end   = timeInstance.format( new Date( mEndTime ) );
           
      Path yAxis;
      yAxis = new Path();
      yAxis.moveTo( 5, 5+mHeight/2 );
      yAxis.lineTo( 5, 5 );
      mRenderCanvas.drawTextOnPath( String.format( start ), yAxis, 0, whiteCenteredText.getTextSize(), whiteCenteredText );
      yAxis = new Path();
      yAxis.moveTo( 5+mWidth/2  , 5+mHeight/2 );
      yAxis.lineTo( 5+mWidth/2  , 5 );
      mRenderCanvas.drawTextOnPath( String.format( half  ), yAxis, 0, -3, whiteCenteredText );
      yAxis = new Path();
      yAxis.moveTo( 5+mWidth-1  , 5+mHeight/2  );
      yAxis.lineTo( 5+mWidth-1  , 5 );
      mRenderCanvas.drawTextOnPath( String.format( end   ), yAxis, 0, -3, whiteCenteredText );
   }

   private void drawDistanceTexts()
   {
      String start = String.format( "%.0f %s", mUnits.conversionFromMeter(0), mUnits.getDistanceUnit() ) ;
      String half  = String.format( "%.0f %s", mUnits.conversionFromMeter(mDistance)/2, mUnits.getDistanceUnit() ) ;
      String end   = String.format( "%.0f %s", mUnits.conversionFromMeter(mDistance)  , mUnits.getDistanceUnit() ) ;
      
      Path yAxis;
      yAxis = new Path();
      yAxis.moveTo( 5, 5+mHeight/2 );
      yAxis.lineTo( 5, 5 );
      mRenderCanvas.drawTextOnPath( String.format( start ), yAxis, 0, whiteText.getTextSize(), whiteCenteredText );
      yAxis = new Path();
      yAxis.moveTo( 5+mWidth/2  , 5+mHeight/2 );
      yAxis.lineTo( 5+mWidth/2  , 5 );
      mRenderCanvas.drawTextOnPath( String.format( half  ), yAxis, 0, -3, whiteCenteredText );
      yAxis = new Path();
      yAxis.moveTo( 5+mWidth-1  , 5+mHeight/2  );
      yAxis.lineTo( 5+mWidth-1  , 5 );
      mRenderCanvas.drawTextOnPath( String.format( end   ), yAxis, 0, -3, whiteCenteredText );
   }

   private double translateValue( double val )
   {
      switch( mGraphType )
      {
         case( TIMESPEEDGRAPH ):
         case( DISTANCESPEEDGRAPH ):
            val = mUnits.conversionFromMetersPerSecond( val );
            break;
         case( TIMEALTITUDEGRAPH ):
         case( DISTANCEALTITUDEGRAPH ):
            val = mUnits.conversionFromMeterToHeight( val );
            break;
         default:
            break;
      }
      return val;

   }
   
   private void drawGraph( double[][] values, int[][] valueDepth )
   {
      // Matrix
      // Horizontals
      mRenderCanvas.drawLine( 5, 5            , 5+mWidth, 5            , ltgreyMatrixDashed ); // top
      mRenderCanvas.drawLine( 5, 5+mHeight/4  , 5+mWidth, 5+mHeight/4  , ltgreyMatrixDashed ); // 2nd
      mRenderCanvas.drawLine( 5, 5+mHeight/2  , 5+mWidth, 5+mHeight/2  , ltgreyMatrixDashed ); // middle
      mRenderCanvas.drawLine( 5, 5+mHeight/4*3, 5+mWidth, 5+mHeight/4*3, ltgreyMatrixDashed ); // 3rd
      // Verticals
      mRenderCanvas.drawLine( 5+mWidth/4  , 5, 5+mWidth/4  , 5+mHeight, ltgreyMatrixDashed ); // 2nd
      mRenderCanvas.drawLine( 5+mWidth/2  , 5, 5+mWidth/2  , 5+mHeight, ltgreyMatrixDashed ); // middle
      mRenderCanvas.drawLine( 5+mWidth/4*3, 5, 5+mWidth/4*3, 5+mHeight, ltgreyMatrixDashed ); // 3rd
      mRenderCanvas.drawLine( 5+mWidth-1  , 5, 5+mWidth-1  , 5+mHeight, ltgreyMatrixDashed ); // right
      
      // The line
      Path mPath;
      int emptyValues = 0;
      mPath = new Path();
      for( int p=0;p<values.length;p++)
      {
         int start = 0;
         while( valueDepth[p][start] == 0 && start < values[p].length-1 )
         {
            start++;
         }
         mPath.moveTo( (float)start+5, 5f+ (float) ( mHeight - ( ( values[p][start]-mMinAxis )*mHeight ) / ( mMaxAxis-mMinAxis ) ) );
         for( int x=start;x<values[p].length;x++)
         {
            double y =   mHeight - ( ( values[p][x]-mMinAxis )*mHeight ) / ( mMaxAxis-mMinAxis ) ;
            if( valueDepth[p][x] > 0 )
            {
               if( emptyValues > mWidth/10 )
               {
                  mPath.moveTo( (float)x+5, (float) y+5 );
               }
               else
               {
                  mPath.lineTo( (float)x+5, (float) y+5 );
               }
               emptyValues = 0;
            }
            else
            {
               emptyValues++;
            }
         }
      }
      mRenderCanvas.drawPath( mPath, greenGraphLine );
      
      // Axis's
      mRenderCanvas.drawLine( 5, 5       , 5       , 5+mHeight, dkgreyMatrixLine );
      mRenderCanvas.drawLine( 5, 5+mHeight, 5+mWidth, 5+mHeight, dkgreyMatrixLine );
   }

}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.