ElevationCanvas.java :  » Mobile » mobile-trail-explorer » com » substanceofcode » tracker » view » Java Open Source

Java Open Source » Mobile » mobile trail explorer 
mobile trail explorer » com » substanceofcode » tracker » view » ElevationCanvas.java
/*
 * ElevationCanvas.java
 *
 * Copyright (C) 2005-2008 Tommi Laukkanen
 * http://www.substanceofcode.com
 *
 * This program 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 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */
package com.substanceofcode.tracker.view;

import java.util.Date;

import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;

import com.substanceofcode.gps.GpsPosition;
import com.substanceofcode.tracker.model.Track;
import com.substanceofcode.util.DateTimeUtil;
import com.substanceofcode.util.ImageUtil;
import com.substanceofcode.localization.LocaleManager;
import com.substanceofcode.tracker.model.LengthFormatter;
import com.substanceofcode.tracker.model.UnitConverter;
import com.substanceofcode.tracker.model.RecorderSettings;

/**
 * <p> Elevation canvas shows the change in elevation over the course of the Trail
 * 
 * @author Barry Redmond
 */
public class ElevationCanvas extends BaseCanvas {

    private static final int X_SCALE_TYPE_MASK = 1;
    private static final int X_SCALE_SCALE_MASK = ~X_SCALE_TYPE_MASK;

    private static final int X_SCALE_TIME = 0;
    private static final int X_SCALE_DISTANCE = 1;
    private static final int X_MIN_ZOOM = Integer.MAX_VALUE & X_SCALE_SCALE_MASK;
    private static final int X_MAX_ZOOM = 0;
    //Values for minimum and maximum altitude which appear "reasonable" to humans
    private static final double[] altLevels = {1.0, 5.0, 10.0, 25.0, 50.0,
                                               100.0, 125.0, 250.0, 500.0, 1000.0,
                                               1250.0, 2500.0, 5000.0, 10000.0,
                                               12500.0, 25000.0};
    private static int altitudeZoomValue = 0;

    private final int MARGIN = this.getWidth() > 200 ? 5 : 2;
    //In pixel
    private final int verticalMovementSize, horizontalMovementSize;
    //In meter
    private int verticalMovement, horizontalMovement;
    //Number of increments between minimun and maximum altitude
    private int maxPositions = 5;

    private GpsPosition lastPosition;
    private Image redDotImage;
    //Pixel per meter?
    private int xScale, yScale;
    private boolean gridOn;
    
    //In meter
    private double minAltitude, maxAltitude;
    private double minAltitudeCurUnit, maxAltitudeCurUnit;
    
    private boolean manualZoom = false;

    private RecorderSettings settings;

    public ElevationCanvas(GpsPosition initialPosition) {
        super();
        
        this.lastPosition = initialPosition;
        
        this.verticalMovementSize = this.getHeight() / 8;
        this.horizontalMovementSize = this.getWidth() / 8;
        this.verticalMovement = this.horizontalMovement = 0;
        this.xScale = X_SCALE_TIME | X_MAX_ZOOM;
        this.settings = controller.getSettings();

        this.gridOn = true;

        redDotImage = ImageUtil.loadImage("/images/red-dot.png");

        this.setMinMaxValues(altitudeZoomValue);
    }

    /** Get the minimum and maximum altitude values of the current track
     * 
     */
    private void setMinMaxValues(int altLevelOffset) {
        double altMin , altMax, altDiff;

        Track curTrack = controller.getTrack();
        LengthFormatter lengthFormatter = new LengthFormatter(settings);
        int altitudeUnitType = lengthFormatter.getAltitudeUnitType();


        //Set initial values if the track is still empty
        if(curTrack.getPositionCount() == 0){
            //Initial values in current unit
            minAltitudeCurUnit = 0;
            maxAltitudeCurUnit = 20000;
        } else {
            try {
                //Get maximum altitude in Meter and convert to current Unit
                altMax = UnitConverter.convertLength(curTrack.getMaxAltitude(), UnitConverter.UNITS_METERS, altitudeUnitType);
                //Get minimum altitude in Meter and convert to current Unit
                altMin = UnitConverter.convertLength(curTrack.getMinAltitude(), UnitConverter.UNITS_METERS, altitudeUnitType);
                //Calculate the difference between minium and maximum altitude
                altDiff = calculateAltitudeDiff(altMax - altMin, altLevelOffset);
                //Calculate the minimum altitude
                minAltitudeCurUnit = calculateMinAltitude(altMin, altDiff);
                //Check if we need a bigger range
                if( altMax > minAltitudeCurUnit + altDiff)
                {
                    //Recalculate the difference between minium and maximum
                    //altitude and get the range one index bigger
                    altDiff = calculateAltitudeDiff(altMax - altMin, altLevelOffset + 1);
                }
                maxAltitudeCurUnit = minAltitudeCurUnit + altDiff;
            }
            catch (Exception ex) {
                //ignore it
            }
        }
    }

    protected void paint(Graphics g) {
        g.setColor( Theme.getColor(Theme.TYPE_BACKGROUND) );
        g.fillRect(0, 0, this.getWidth(), this.getHeight());

        // Top position in pixel where to start drawing grid
        final int top = drawTitle(g, 0);

        g.setFont(Font.getFont(Font.FACE_MONOSPACE, Font.STYLE_PLAIN,
                Font.SIZE_SMALL));
        //Bottom position in pixel
        final int bottom = this.getHeight() - (2 * MARGIN);
        //Refresh min/max altitude before repaint
        if (!manualZoom)
        {
            setMinMaxValues(0);
        }
        drawYAxis(g, top, bottom);

        drawXAxis(g, MARGIN, this.getWidth() - 2 * MARGIN, top, bottom);

        final int[] clip = { g.getClipX(), g.getClipY(), g.getClipWidth(),
                g.getClipHeight() };
        g.setClip(MARGIN, top, this.getWidth() - 2 * MARGIN, bottom);
        drawTrail(g, top, bottom);
        g.setClip(clip[0], clip[1], clip[2], clip[3]);
    }

    /**
     * Draws the Title for this screen and returns the yPosition of the bottom
     * of the title.
     * 
     * @param g
     * @param top
     * @return
     */
    private int drawTitle(Graphics g, int yPos) {
        g.setFont(titleFont);
        g.setColor( Theme.getColor(Theme.TYPE_TITLE) );
        final String title = LocaleManager.getMessage("elevation_canvas_title");
        g.drawString(title, this.getWidth() / 2, yPos, Graphics.TOP
                | Graphics.HCENTER);
        return yPos + g.getFont().getHeight();
    }

    private void drawYAxis(Graphics g, final int top, final int bottom) {

        g.setColor( Theme.getColor(Theme.TYPE_LINE) );

        // Draw the vertical Axis
        g.drawLine(MARGIN, top, MARGIN, bottom);

        // Draw the top altitude in current unit
        drawAltitudeBar(g, top, this.maxAltitudeCurUnit);

        // Draw the bottom altitude
        drawAltitudeBar(g, bottom, this.minAltitudeCurUnit);

        // Draw intermediate altitude positions.

        /*
         * We'll try and draw 5 intermediate altitudes, assuming there's room on
         * the screen for this to look OK
         */
        final int availableHeight = bottom - top;
        final int spaceHeight = g.getFont().getHeight() * 2;

        int pixelIncrement = availableHeight / maxPositions;
        //Increment in current unit
        double altitudeIncrement = (this.maxAltitudeCurUnit - this.minAltitudeCurUnit)
                / maxPositions;
        int yPos = bottom - pixelIncrement;
        double yAlt = this.minAltitudeCurUnit + altitudeIncrement;
        for (int i = 1; i < maxPositions; i++, yPos -= pixelIncrement, yAlt += altitudeIncrement) {
            drawAltitudeBar(g, yPos, yAlt);
        }
    }

    private void drawAltitudeBar(Graphics g, int pixel, double altitude) {
        int decimalCount = 0;
        g.drawLine(1, pixel, 2 * (MARGIN - 1) + 1, pixel);

        //Get the altitude as string with unit appended
        LengthFormatter height = new LengthFormatter(settings.getDistanceUnitType());
        //Check if there are decimals after ".". If yes, show them
        if(altitude % 1 != 0)
        {
            decimalCount = 1;
        }

        final String altString = height.getAltitudeString(altitude, true, decimalCount, true);
        
        g.drawString(altString, MARGIN + 2, pixel, Graphics.BOTTOM
                | Graphics.LEFT);
        if (this.gridOn) {
            final int color = g.getColor();
            g.setColor( Theme.getColor(Theme.TYPE_SUBLINE));
            final int right = this.getWidth() - (2 * MARGIN);
            g.drawLine(2 * (MARGIN - 1) + 1, pixel, right, pixel);
            g.setColor(color);
        }
    }

    private void drawXAxis(Graphics g, final int left, final int right,
            final int top, final int bottom) {
        g.setColor(Theme.getColor(Theme.TYPE_LINE));
        g.drawLine(left, bottom, right, bottom);

        String time = null;
        try {
            DateTimeUtil.convertToTimeStamp(this.lastPosition.date, false);// "By_Time";
        } catch (Exception e) {
        }
        if (time == null) {
            time = ""; // "N/A";
        }

        drawTimeDistanceBar(g, right, time, top, bottom);
        // TODO: draw Scale
    }

    private void drawTimeDistanceBar(Graphics g, int pixel, String value,
            final int top, final int bottom) {
        g.drawLine(pixel, this.getHeight() - 2 * (MARGIN - 1), pixel, this
                .getHeight() - 1);

        g.drawString(value, pixel, this.getHeight() - MARGIN, Graphics.BOTTOM
                | Graphics.HCENTER);
        if (this.gridOn) {
            final int color = g.getColor();
            g.setColor(Theme.getColor(Theme.TYPE_SUBLINE));
            g.drawLine(pixel, top, pixel, bottom - MARGIN);
            g.setColor(color);
        }
    }

    private void drawTrail(Graphics g, final int top, final int bottom) {
        try {
            // Exit if we don't have anything to draw
            final GpsPosition temp = controller.getPosition();
            if (temp != null) {
                lastPosition = temp;
            }
            if (lastPosition == null) {
                return;
            }

            double currentLatitude = lastPosition.latitude;
            double currentLongitude = lastPosition.longitude;
            //
            LengthFormatter lengthFormatter = new LengthFormatter(settings);
            int altUnitType = lengthFormatter.getAltitudeUnitType();

            //Convert to current unit
            double currentAltitude = UnitConverter.convertLength(lastPosition.altitude, UnitConverter.UNITS_METERS, altUnitType);
            Date currentTime = lastPosition.date;

            double lastLatitude = currentLatitude;
            double lastLongitude = currentLongitude;
            //In current unit
            double lastAltitude = currentAltitude;
            Date lastTime = currentTime;

            final Track track = controller.getTrack();
            final int numPositions;
            synchronized (track) {
                /*
                 * Synchronized so that no element can be added or removed
                 * between getting the number of elements and getting the
                 * elements themselfs.
                 */
                numPositions = track.getPositionCount();

                // Draw trail with blue color
                g.setColor(Theme.getColor(Theme.TYPE_TRAIL));

                final int numPositionsToDraw = controller.getSettings()
                        .getNumberOfPositionToDraw();

                final int lowerLimit;
                if (numPositions - numPositionsToDraw < 0) {
                    lowerLimit = 0;
                } else {
                    lowerLimit = numPositions - numPositionsToDraw;
                }
                for (int positionIndex = numPositions - 1; positionIndex >= lowerLimit; positionIndex--) {

                    GpsPosition pos = (GpsPosition) track
                            .getPosition(positionIndex);

                    double lat = pos.latitude;
                    double lon = pos.longitude;
                    //Convert altitude from meter in current unit
                    double alt = UnitConverter.convertLength(pos.altitude, UnitConverter.UNITS_METERS, altUnitType);
                    Date time = pos.date;
                    CanvasPoint point1 = convertPosition(lat, lon, alt, time,
                            top, bottom);

                    CanvasPoint point2 = convertPosition(lastLatitude,
                            lastLongitude, lastAltitude, lastTime, top, bottom);

                    g.drawLine(point1.X, point1.Y, point2.X, point2.Y);

                    lastLatitude = pos.latitude;
                    lastLongitude = pos.longitude;
                    lastAltitude = alt;
                    lastTime = pos.date;
                }
            }

            int height = 0;
            if (lastPosition != null) {
                //Pass altitude in current unit
                height = getYPos(currentAltitude, top, bottom);
            }
/*
            if (height < -5000) { //set some reasonable limits
                height = -5000; // This prevents the gui from stalling from wildly bad data
            } // to do: should figure out why the data is bad in the first place.

            if (height > 60000){
                height = 60000;
            }
*/
            int right = this.getWidth() - (2 * MARGIN);
            // Draw red dot on current location
            g.drawImage(redDotImage, right + horizontalMovement, height,
                    Graphics.VCENTER | Graphics.HCENTER);

        } catch (Exception ex) {
            g.setColor( Theme.getColor(Theme.TYPE_ERROR) );
            g.drawString(LocaleManager.getMessage("elevation_canvas_error") +
                    " " + ex.toString(), 1, 120, Graphics.TOP | Graphics.LEFT);

            Logger.error(
                    "Exception occured while drawing elevation: "
                            + ex.toString());
        }
    }

    private CanvasPoint convertPosition(double latitude, double longitude,
            double altitude, Date time, final int top, final int bottom) {
        int xPos;
        if ((this.xScale & X_SCALE_TYPE_MASK) == X_SCALE_TIME) {
            // x-Scale is time based
            int secondsSinceLastPosition = (int) ((this.lastPosition.date
                    .getTime() - time.getTime()) / 1000);

            int scale = (this.xScale & X_SCALE_SCALE_MASK) >> 1;

            if (scale == 0)
                scale = 1;
            xPos = this.getWidth() - (2 * MARGIN)
                    - (secondsSinceLastPosition / scale) + horizontalMovement;

        } else {
            // x-Scale is distance based
            xPos = 0;
            return null;
        }
        //Pass altitude in current unit
        int yPos = getYPos(altitude, top, bottom);
        return new CanvasPoint(xPos, yPos);
    }

    private int getYPos(double altitude, final int top, final int bottom) {
        final double availableHeight = bottom - top;
        final double altitudeDiff = this.maxAltitudeCurUnit - this.minAltitudeCurUnit;
        //Number of pixels for one current unit
        final double oneUnit = availableHeight / altitudeDiff;
        //Height in pixels
        int pixels = (int) ((altitude - minAltitudeCurUnit) * oneUnit);
        return bottom - (MARGIN + pixels) + verticalMovement;
    }

    public void keyPressed(int keyCode) {
        super.keyPressed(keyCode);
        
        /** Handle zooming keys */
        switch (keyCode) {
            case (KEY_NUM1):

                // Zoom in vertically
                manualZoom = true;
                if(altitudeZoomValue <= 0)
                {
                    altitudeZoomValue = 0;
                }
                else
                {
                    altitudeZoomValue = altitudeZoomValue - 1;
                }
                setMinMaxValues(altitudeZoomValue);
                break;

            case (KEY_NUM2):
                // Fix altitude scale
                manualZoom = false;
                setMinMaxValues(0);
                break;

            case (KEY_NUM3):
                // Zoom out vertically
                manualZoom = true;
                if(altitudeZoomValue > altLevels.length - 1)
                {
                    altitudeZoomValue = altLevels.length - 1;
                }
                else
                {
                    altitudeZoomValue = altitudeZoomValue + 1;
                }
                setMinMaxValues(altitudeZoomValue);
                break;

            case (KEY_NUM7):
                // Zoom in horizontally
                int xScaleType = this.xScale & X_SCALE_TYPE_MASK;
                int xScaleScale = this.xScale & X_SCALE_SCALE_MASK;
                if (xScaleScale == X_MIN_ZOOM) {
                    break;
                }
                xScaleScale = ((xScaleScale >> 1) + 1) << 1;
                this.xScale = (byte) (xScaleScale | xScaleType);
                break;

            case (KEY_NUM9):
                // Zoom out horizontally
                xScaleType = this.xScale & X_SCALE_TYPE_MASK;
                xScaleScale = this.xScale & X_SCALE_SCALE_MASK;
                if (xScaleScale == X_MAX_ZOOM) {
                    break;
                }
                xScaleScale = ((xScaleScale >> 1) - 1) << 1;
                this.xScale = (byte) (xScaleScale | xScaleType);
                break;

            default:
        }

        /** Handle panning keys */
        int gameKey = -1;
        try {
            gameKey = getGameAction(keyCode);
        } catch (Exception ex) {
            /**
             * We don't need to handle this error. It is only caught because
             * getGameAction() method generates exceptions on some phones for
             * some buttons.
             */
        }
        if (gameKey == UP || keyCode == KEY_NUM2) {
            // Disable vertical-movement until the scales at the side reflect it
            // properly
            // verticalMovement += verticalMovementSize;
        }
        if (gameKey == DOWN || keyCode == KEY_NUM8) {
            // Disable vertical-movement until the scales at the side reflect it
            // properly
            // verticalMovement -= verticalMovementSize;
        }
        if (gameKey == LEFT || keyCode == KEY_NUM4) {
            horizontalMovement += horizontalMovementSize;
        }
        if (gameKey == RIGHT || keyCode == KEY_NUM6) {
            horizontalMovement -= horizontalMovementSize;
        }
        if (gameKey == FIRE || keyCode == KEY_NUM5) {
            verticalMovement = 0;
            horizontalMovement = 0;
        }
        this.repaint();
    }

    /**
     * Calculate the difference between min and max altitude.
     * Set it to a "reasonable value" stored in altLevels.
     * By passing an zoomValue value other than zero you can zoom in (<0)
     * or zoom out (>0)
     *
     * @param diffAlt Difference between minimum and maximum altitude
     * @param zoomValue
     * @return minimum altitude
     */
    private double calculateAltitudeDiff(double diff, int zoomValue)
    {
        int index = 0;

        //Get a reasonable value bigger than the difference between
        //altMin and altMax;
        for(int i=altLevels.length - 1;i > 0;i--)
        {
            if(diff > altLevels[i])
            {
                index =  i + 1;
                break;
            }
        }

        //Check lower array boundary
        if( index + zoomValue < 0)
        {
            return altLevels[0];
        }
        //Check upper array boundary
        if( index + zoomValue > altLevels.length - 1)
        {
            return altLevels[altLevels.length - 1];
        }
        return altLevels[index + zoomValue];
    }

    /**
     * Calculate the lower altitude boundary. It is set to
     * "reasonable value".
     *
     * @param curAlt Altitude in current unit
     * @param diffAlt Difference between minimum and maximum altitude
     * @return minimum altitude
     */
    private double calculateMinAltitude(double curAlt, double diffAlt)
    {
        //Devide the altitude difference by the number of lines to draw + 1
        int altIncrement = (int) (diffAlt/maxPositions);
        if(altIncrement == 0){
            return (int)curAlt;
        }
        //Get integer devision
        int intDiv = ((int)curAlt / altIncrement);
        return intDiv * altIncrement;
    }

    public void setLastPosition(GpsPosition position) {
        this.lastPosition = position;
        this.setMinMaxValues(altitudeZoomValue);
    }
}
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.