package me.guillaumin.android.osmtracker.view;
import java.text.DecimalFormat;
import me.guillaumin.android.osmtracker.R;
import me.guillaumin.android.osmtracker.db.TrackContentProvider;
import me.guillaumin.android.osmtracker.db.TrackContentProvider.Schema;
import me.guillaumin.android.osmtracker.util.ArrayUtils;
import me.guillaumin.android.osmtracker.util.MercatorProjection;
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.os.Handler;
import android.util.Log;
import android.widget.TextView;
public class DisplayTrackView extends TextView {
private static final String TAG = DisplayTrackView.class.getSimpleName();
/**
* Padding (in pixels) for drawing track, to prevent touching the borders.
*/
private static final int PADDING = 5;
/**
* Width of the scale bar, in pixels.
*/
private static final int SCALE_WIDTH = 50;
/**
* Height of left & right small lines to delimit scale (pixels)
*/
private static final int SCALE_DELIM_HEIGHT = 10;
/**
* Formatter for scale information
*/
private static final DecimalFormat SCALE_FORMAT = new DecimalFormat("0");
/**
* Coordinates to draw (before projection)
*/
private double[][] coords;
/**
* Array of pixels coordinates to display track
*/
private int[][] pixels;
/**
* Coordinates of waypoints
*/
private double[][] wayPointsCoords;
/**
* Pixels coordinates to display waypoints
*/
private int[][] wayPointsPixels;
/**
* The projection used to convert coordinates to pixels.
*/
private MercatorProjection projection;
/**
* Paint used for drawing track.
*/
private Paint trackPaint = new Paint();
/**
* Compass bitmap
*/
private Bitmap compass;
/**
* Position marker bitmap
*/
private Bitmap marker;
/**
* Way point marker Bitmap
*/
private Bitmap wayPointMarker;
/**
* Letter to use for meter unit (taken from resources)
*/
private String meterLabel;
/**
* Letter to use for indicating North (taken from resources)
*/
private String northLabel;
/**
* Current track id
*/
private long currentTrackId;
/**
* ContentObserver to be notified about any new trackpoint and
* redraw screen
*/
private class TrackPointContentObserver extends ContentObserver {
public TrackPointContentObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
// width & height could be = 0 if the view had
// not been attached to window & measured when onChange()
// is fired.
if (getWidth() > 0 && getHeight() > 0) {
// Populate new data, and recompute projection
populateCoords();
projectData(getWidth(), getHeight());
// Force view redraw
invalidate();
}
}
}
/**
* Instance of TrackpointContentObserver
*/
private TrackPointContentObserver trackpointContentObserver;
public DisplayTrackView(Context context) {
super(context);
}
public DisplayTrackView(Context context, long trackId) {
super(context);
currentTrackId = trackId;
// Set text align to center
getPaint().setTextAlign(Align.CENTER);
// Setup track drawing paint
trackPaint.setColor(getCurrentTextColor());
trackPaint.setStyle(Paint.Style.FILL_AND_STROKE);
// Retrieve some resources that will be used in drawing
meterLabel = getResources().getString(R.string.various_unit_meters);
northLabel = getResources().getString(R.string.displaytrack_north);
marker = BitmapFactory.decodeResource(getResources(), R.drawable.marker);
compass = BitmapFactory.decodeResource(getResources(), android.R.drawable.ic_menu_compass);
wayPointMarker = BitmapFactory.decodeResource(getResources(), R.drawable.star);
trackpointContentObserver = new TrackPointContentObserver(new Handler());
context.getContentResolver().registerContentObserver(
TrackContentProvider.trackPointsUri(currentTrackId),
true, trackpointContentObserver);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
Log.v(TAG, "onSizeChanged: " + w + "," + h + ". Old: " + oldw + "," + oldh);
// Populate data from content provider
populateCoords();
// Project coordinates into 2D screen
projectData(w, h);
super.onSizeChanged(w, h, oldw, oldh);
}
@Override
protected void onDetachedFromWindow() {
// Unregister content observer
getContext().getContentResolver().unregisterContentObserver(trackpointContentObserver);
super.onDetachedFromWindow();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// If we have data to paint
if (pixels != null && pixels.length > 0) {
int length = pixels.length;
for (int i = 1; i < length; i++) {
// Draw a line between each point
canvas.drawLine(
PADDING + pixels[i - 1][MercatorProjection.X],
PADDING + pixels[i - 1][MercatorProjection.Y],
PADDING + pixels[i][MercatorProjection.X],
PADDING + pixels[i][MercatorProjection.Y], trackPaint);
}
// Draw a marker for each waypoint
if (wayPointsPixels != null && wayPointsPixels.length > 0) {
int wpLength = wayPointsPixels.length;
for (int i = 0; i < wpLength; i++) {
canvas.drawBitmap(wayPointMarker,
PADDING + wayPointsPixels[i][MercatorProjection.X],
PADDING + wayPointsPixels[i][MercatorProjection.Y],
this.getPaint());
}
}
// Draw current position marker
canvas.drawBitmap(marker, pixels[length - 1][MercatorProjection.X],
pixels[length - 1][MercatorProjection.Y], this.getPaint());
// Draw scale information
drawScale(canvas);
}
// Draw static resources
drawStatic(canvas);
}
/**
* Draw scale information.
*
* @param canvas
* Canvas used to draw
*/
private void drawScale(Canvas canvas) {
double scale = projection.getScale();
Log.v(TAG, "Scale is: " + scale);
// Draw horizontal line
canvas.drawLine(getWidth() - PADDING - SCALE_WIDTH, PADDING+SCALE_DELIM_HEIGHT/2, getWidth() - PADDING, PADDING+SCALE_DELIM_HEIGHT/2, this.getPaint());
// Draw 2 small vertical lines for the bounds
canvas.drawLine(getWidth() - PADDING - SCALE_WIDTH, PADDING, getWidth() - PADDING - SCALE_WIDTH,
PADDING + SCALE_DELIM_HEIGHT, this.getPaint());
canvas.drawLine(getWidth() - PADDING, PADDING, getWidth() - PADDING, PADDING + SCALE_DELIM_HEIGHT, this.getPaint());
// Draw scale
canvas.drawText(SCALE_FORMAT.format(100*1000*scale*SCALE_WIDTH) + meterLabel, getWidth() - PADDING - SCALE_WIDTH / 2,
PADDING + SCALE_DELIM_HEIGHT + getPaint().getTextSize(), this.getPaint());
}
/**
* Draw various static gfx (Compass ...)
*
* @param canvas
* Canvas used to draw
*/
private void drawStatic(Canvas canvas) {
canvas.drawBitmap(compass, PADDING, getHeight() - PADDING - compass.getHeight(), null);
canvas.drawText(northLabel, PADDING + compass.getWidth() / 2, getHeight() - PADDING - compass.getHeight() - 5,
this.getPaint());
}
/**
* Populate coordinates from a cursor to current track Database
*/
public void populateCoords() {
Cursor c = getContext().getContentResolver().query(
TrackContentProvider.trackPointsUri(currentTrackId),
null, null, null, TrackContentProvider.Schema.COL_TIMESTAMP + " asc");
coords = new double[c.getCount()][2];
int i=0;
for(c.moveToFirst(); !c.isAfterLast(); c.moveToNext(), i++) {
coords[i][MercatorProjection.LONGITUDE] = c.getDouble(c.getColumnIndex(Schema.COL_LONGITUDE));
coords[i][MercatorProjection.LATITUDE] = c.getDouble(c.getColumnIndex(Schema.COL_LATITUDE));
}
c.close();
Log.v(TAG, "Extracted " + coords.length + " track points from DB.");
c = getContext().getContentResolver().query(
TrackContentProvider.waypointsUri(currentTrackId),
null, null, null, TrackContentProvider.Schema.COL_TIMESTAMP + " asc");
wayPointsCoords = new double[c.getCount()][2];
for(c.moveToFirst(), i=0; !c.isAfterLast(); c.moveToNext(), i++) {
wayPointsCoords[i][MercatorProjection.LONGITUDE] = c.getDouble(c.getColumnIndex(Schema.COL_LONGITUDE));
wayPointsCoords[i][MercatorProjection.LATITUDE] = c.getDouble(c.getColumnIndex(Schema.COL_LATITUDE));
}
c.close();
Log.v(TAG, "Extracted " + wayPointsCoords.length + " way points from DB.");
}
/**
* Project current coordinates into a 2D screen
* @param width Width of the display screen
* @param height Height of the display screen
*/
public void projectData(int width, int height) {
// If we got coordinates, start projecting.
if (coords != null && coords.length > 0) {
projection = new MercatorProjection(
ArrayUtils.findMin(coords, MercatorProjection.LATITUDE),
ArrayUtils.findMin(coords, MercatorProjection.LONGITUDE),
ArrayUtils.findMax(coords, MercatorProjection.LATITUDE),
ArrayUtils.findMax(coords, MercatorProjection.LONGITUDE),
width - PADDING * 2, height - PADDING * 2);
// Project each coordinate into pixels.
pixels = new int[coords.length][2];
int length = pixels.length;
for (int i = 0; i < length; i++) {
pixels[i] = projection.project(coords[i][MercatorProjection.LONGITUDE],
coords[i][MercatorProjection.LATITUDE]);
}
// Same thing for way points, using same projection
if (wayPointsCoords != null && wayPointsCoords.length > 0) {
// Project each coordinate into pixels.
wayPointsPixels = new int[wayPointsCoords.length][2];
length = wayPointsPixels.length;
for (int i = 0; i < length; i++) {
wayPointsPixels[i] = projection.project(wayPointsCoords[i][MercatorProjection.LONGITUDE],
wayPointsCoords[i][MercatorProjection.LATITUDE]);
}
}
}
}
}