/* Copyright 2012 Nik Cain nik@showmehills.com This file is part of ShowMeHills. ShowMeHills 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. ShowMeHills 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 ShowMeHills. If not, see <http://www.gnu.org/licenses/>. */ package com.showmehills; /* * Main Activity. Implements a camera preview with an overlay. Listens for orientation changes and * asks HillDatabase for hills that are in view. It then draws HillOverlayItem in the appropriate place. * The 'appropriate place' is dependent on two calibration factors - the first gives the field of view * and the second adjusts the compass direction. * Tapping on the HillOverlayItem opens the HillInfo activity. * * The field of view calibration seemed necessary as the hardware parameters of the phone didn't match * reality. This approach seemed more failsafe for any camera, even though it introduces the element of * human error. * * The compass adjustment is necessary since the compass on my HTC Desire HD is temperamental to the point * of unusable. Even with the adjustment it rarely works well. */ import java.util.ArrayList; import java.util.Iterator; import java.util.Timer; import java.util.TimerTask; import java.util.UUID; import com.google.analytics.tracking.android.EasyTracker; import com.showmehills.R; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.database.SQLException; import android.graphics.*; import android.hardware.*; import android.location.*; import android.os.Bundle; import android.os.PowerManager; import android.preference.PreferenceManager; import android.util.FloatMath; import android.util.Log; import android.view.*; import android.view.View.OnTouchListener; import android.view.ViewGroup.LayoutParams; import android.widget.FrameLayout; public class ShowMeHillsActivity extends Activity implements IShowMeHillsActivity, SensorEventListener, OnTouchListener { public float hfov = (float) 50.2; public float vfov = (float) 20.0; private SensorManager mSensorManager; private RapidGPSLock mGPS; private PowerManager.WakeLock wl; Sensor accelerometer; Sensor magnetometer; float[] mGravity; float[] mGeomagnetic; Timer timer; private int GPSretryTime = 60; private int CompassSmoothingWindow = 50; //private Location curLocation; private String acc = ""; private boolean badsensor = false; private boolean isCalibrated = false; private double calibrationStep = -1; private float compassAdjustment = 0; private ArrayList<HillMarker> mMarkers = new ArrayList<HillMarker>(); private float pinchdist = 0; float mRotationMatrixA[] = new float[9]; float mRotationMatrixB[] = new float[9]; float mOrientationVector[] = new float[9]; float mAzimuthVector[] = new float[4]; float mDeclination = 0; private boolean mHasAccurateGravity = false; private boolean mHasAccurateAccelerometer = false; public int scrwidth = 10; public int scrheight = 10; private int mMainTextSize = 20; public static CameraPreviewSurface cv; public DrawOnTop mDraw; private HillDatabase myDbHelper; private filteredDirection fd = new filteredDirection(); private filteredElevation fe = new filteredElevation(); // preferences Float maxdistance = 30f; Float textsize = 25f; boolean showdir = false; boolean showdist = false; boolean typeunits = false; // true for metric, false for imperial boolean showheight = false; boolean showhelp = true; String uniqueID = "nothere"; // constants private static final float TEXT_SIZE_DECREMENT = 1; private static final float TEXT_SIZE_MIN = 7; private static final int ALPHA_LABEL_MAX = 255; private static final int ALPHA_LINE_MAX = 205; private static final int ALPHA_DECREMENT = 10; private static final int ALPHA_STROKE_MIN = 200; private static final int ALPHA_LABEL_MIN = 180; private static final int ALPHA_LINE_MIN = 50; public class HillMarker { public HillMarker(int id, Rect loc) { location = loc; hillid=id; } public Rect location; public int hillid; } public int GetRotation() { Display display = getWindowManager().getDefaultDisplay(); int rot = display.getRotation(); return rot; } private void getPrefs() { // Get the xml/preferences.xml preferences SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext()); String md = prefs.getString("distance", ""+maxdistance); if (md == "") md = "30.0"; maxdistance = Float.parseFloat(md); String ts = prefs.getString("textsize", ""+textsize); if (ts == "") ts = "25.0"; textsize = Float.parseFloat(ts); showdir = prefs.getBoolean("showdir", false); showdist = prefs.getBoolean("showdist", false); showheight = prefs.getBoolean("showalt", false); typeunits = prefs.getString("distunits", "metric").equalsIgnoreCase("metric"); isCalibrated = prefs.getBoolean("isCalibrated", false); hfov = prefs.getFloat("hfov", (float) 50.2); compassAdjustment = prefs.getFloat("compassAdjustment", 0); showhelp = prefs.getBoolean("showhelp", true); CompassSmoothingWindow = Integer.parseInt(prefs.getString("smoothing", "50")); uniqueID = prefs.getString("uniqueID", "nothere"); if (uniqueID == "nothere") { uniqueID = UUID.randomUUID().toString(); SharedPreferences.Editor editor = prefs.edit(); editor.putString("uniqueID", uniqueID); editor.commit(); } } @Override protected void onResume() { Log.d("showmehills", "onResume"); getPrefs(); fd = new filteredDirection(); fe = new filteredElevation(); super.onResume(); mSensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_GAME); mSensorManager.registerListener(this, magnetometer, SensorManager.SENSOR_DELAY_GAME); mGPS.switchOn(); wl.acquire(); if (timer != null) { timer.cancel(); timer = null; } timer = new Timer(); timer.scheduleAtFixedRate(new LocationTimerTask(),GPSretryTime* 1000,GPSretryTime* 1000); UpdateMarkers(); try { myDbHelper.checkDataBase(); }catch(SQLException sqle){ throw sqle; } } @Override protected void onPause() { Log.d("showmehills", "onPause"); timer.cancel(); timer = null; mGPS.switchOff(); mSensorManager.unregisterListener(this); wl.release(); super.onPause(); try { myDbHelper.close(); }catch(SQLException sqle){ throw sqle; } } @Override protected void onStop() { try { mGPS.switchOff(); if (timer != null) { timer.cancel(); timer = null; } mSensorManager.unregisterListener(this); //wl.release(); myDbHelper.close(); }catch(SQLException sqle){ throw sqle; } super.onStop(); EasyTracker.getInstance().activityStop(this); // Add this method. } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); EasyTracker.getInstance().activityStart(this); // Add this method. requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); wl = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, "My Tag"); //wl.acquire(); mGPS = new RapidGPSLock(this); mGPS.switchOn(); mGPS.findLocation(); if (timer != null) { timer.cancel(); timer = null; } timer = new Timer(); timer.scheduleAtFixedRate(new LocationTimerTask(),GPSretryTime* 1000,GPSretryTime* 1000); mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE); accelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); magnetometer = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD); myDbHelper = new HillDatabase(this, getString(R.string.dbname), getString(R.string.dbpath)); /*try { myDbHelper.createDataBase(); } catch (IOException ioe) { throw new Error("Unable to create database"); }*/ Display display = getWindowManager().getDefaultDisplay(); scrwidth = display.getWidth(); scrheight = display.getHeight(); cv = new CameraPreviewSurface( this.getApplicationContext(), this); FrameLayout rl = new FrameLayout( this.getApplicationContext()); setContentView(rl); mDraw = new DrawOnTop(this); addContentView(mDraw, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); rl.addView(cv); cv.setOnTouchListener((OnTouchListener) this); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext()); if (prefs.getBoolean("showhelp", true)) { Intent myHelpIntent = new Intent(getBaseContext(), Help.class); startActivityForResult(myHelpIntent, 0); } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate menu from XML resource MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.preferences_menu, menu); return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle all of the possible menu actions. SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext()); SharedPreferences.Editor editor = prefs.edit(); if (item.getItemId() == R.id.preferences_menutitem) { Intent settingsActivity = new Intent(getBaseContext(),AppPreferences.class); startActivity(settingsActivity); } else if (item.getItemId() == R.id.mapoverlay) { Location curLocation = mGPS.getCurrentLocation(); if (curLocation != null) { myDbHelper.SetDirections(curLocation); editor.putFloat("longitude", (float)curLocation.getLongitude()); editor.putFloat("latitude", (float)curLocation.getLatitude()); editor.commit(); } Intent myIntent = new Intent(getBaseContext(), MapOverlay.class); startActivityForResult(myIntent, 0); } else if (item.getItemId() == R.id.help) { Intent myHelpIntent = new Intent(getBaseContext(), Help.class); startActivityForResult(myHelpIntent, 0); } else if (item.getItemId() == R.id.about) { Intent myAboutIntent = new Intent(getBaseContext(), About.class); startActivityForResult(myAboutIntent, 0); } else if (item.getItemId() == R.id.exit) { finish(); } else if (item.getItemId() == R.id.fovcalibrate) { calibrationStep = -1; isCalibrated = false; editor.putBoolean("isCalibrated", false); editor.commit(); } return super.onOptionsItemSelected(item); } public void UpdateMarkers() { Location curLocation = mGPS.getCurrentLocation(); if (curLocation != null) { myDbHelper.SetDirections(curLocation); } } class filteredDirection { double dir; double sinevalues[] = new double[CompassSmoothingWindow]; double cosvalues[] = new double[CompassSmoothingWindow]; int index = 0; int outlierCount = 0; void AddLatest( double d ) { sinevalues[index] = Math.sin(d); cosvalues[index] = Math.cos(d); index++; if (index > CompassSmoothingWindow - 1) index = 0; double sumc = 0; double sums = 0; for (int a = 0; a < CompassSmoothingWindow; a++) { sumc += cosvalues[a]; sums += sinevalues[a]; } dir = Math.atan2(sums/CompassSmoothingWindow,sumc/CompassSmoothingWindow); } double getDirection() { // Allow for (possibly large) negative direction and/or compass adjustment by adding // two full circles before applying modulus to force a value between 0 and 360. return (Math.toDegrees(dir) + compassAdjustment + 720) % 360; } int GetVariation() { double Q = 0; double sumc = 0; double sums = 0; for (int a = 0; a < CompassSmoothingWindow; a++) { sumc += cosvalues[a]; sums += sinevalues[a]; } double avgc = sumc/CompassSmoothingWindow; double avgs = sums/CompassSmoothingWindow; sumc = 0; sums = 0; for (int a = 0; a < CompassSmoothingWindow; a++) { sumc += Math.pow(cosvalues[a] - avgc, 2); sums += Math.pow(sinevalues[a] - avgs, 2); } Q = (sumc/(CompassSmoothingWindow-1)) + (sums/(CompassSmoothingWindow-1)); return (int)(Q*1000); } } class filteredElevation { int AVERAGINGWINDOW = 10; double dir; double sinevalues[] = new double[AVERAGINGWINDOW]; double cosvalues[] = new double[AVERAGINGWINDOW]; int index = 0; void AddLatest( double d ) { sinevalues[index] = Math.sin(d); cosvalues[index] = Math.cos(d); index++; if (index > AVERAGINGWINDOW - 1) index = 0; double sumc = 0; double sums = 0; for (int a = 0; a < AVERAGINGWINDOW; a++) { sumc += cosvalues[a]; sums += sinevalues[a]; } dir = Math.atan2(sums/AVERAGINGWINDOW,sumc/AVERAGINGWINDOW); } double getDirection() { return dir; } } class tmpHill { Hills h; double ratio; int toppt; }; class DrawOnTop extends View { private Paint strokePaint = new Paint(); private Paint textPaint = new Paint(); private Paint paint = new Paint(); private Paint transpRedPaint = new Paint(); private Paint variationPaint = new Paint(); private Paint settingPaint = new Paint(); private Paint settingPaint2 = new Paint(); int subwidth; int subheight; int gap; int txtgap; int vtxtgap; RectF fovrect; ArrayList<tmpHill> hillsToPlot; public DrawOnTop(Context context) { super(context); textPaint.setTextAlign(Paint.Align.CENTER); textPaint.setTypeface(Typeface.DEFAULT_BOLD); strokePaint.setTextAlign(Paint.Align.CENTER); strokePaint.setTypeface(Typeface.DEFAULT_BOLD); strokePaint.setStyle(Paint.Style.STROKE); strokePaint.setStrokeWidth(2); paint.setARGB(255, 255, 255, 255); transpRedPaint.setARGB(100,255,0,0); subwidth = (int)(scrwidth*0.7); subheight = (int)(scrheight*0.7); gap = (scrwidth - subwidth) / 2; txtgap = gap+(subwidth/30); vtxtgap = (int)(subheight / 10); hillsToPlot = new ArrayList<tmpHill>(); fovrect = new RectF(gap,vtxtgap,scrwidth-gap,vtxtgap*11); } @Override protected void onDraw(Canvas canvas) { if (!isCalibrated) { drawCalibrationInstructions(canvas); return; } ArrayList<Hills> localhills = myDbHelper.localhills; int topPt = calculateHillsCanFitOnCanvas((int)(scrheight/1.6), localhills); drawHillLabelLines(canvas, topPt); drawHillLabelText(canvas, topPt); drawLocationAndOrientationStatus(canvas); drawSettingsButton(canvas); super.onDraw(canvas); } private int calculateHillsCanFitOnCanvas(int topPt, ArrayList<Hills> localhills) { Float drawtextsize = textsize; hillsToPlot.clear(); mMarkers.clear(); for (int h = 0; h < localhills.size() && topPt > 0; h++) { Hills h1 = localhills.get(h); // this is the angle of the peak from our line of sight double offset = fd.getDirection() - h1.direction; double offset2 = fd.getDirection() - (360+h1.direction); double offset3 = 360+fd.getDirection() - (h1.direction); double ratio = 0; // is it in our line of sight boolean inlineofsight=false; if (Math.abs(offset) * 2 < hfov) { ratio = offset / hfov * -1; inlineofsight = true; } if (Math.abs(offset2) * 2 < hfov) { ratio = offset2 / hfov * -1; inlineofsight = true; } if (Math.abs(offset3) * 2 < hfov) { ratio = offset3 / hfov * -1; inlineofsight = true; } if (inlineofsight) { tmpHill th = new tmpHill(); th.h = h1; th.ratio = ratio; th.toppt = topPt; hillsToPlot.add(th); topPt -= (showdir || showdist || showheight && th.h.height > 0)?(1 + drawtextsize*2):drawtextsize; if (drawtextsize - TEXT_SIZE_DECREMENT >= TEXT_SIZE_MIN) { drawtextsize -= TEXT_SIZE_DECREMENT; } } } // Fudge-factor because we don't know exactly how high label text will display until we draw it later. // A tiny font at the top needs to be moved down slightly to avoid being clipped; larger fonts seem OK. topPt -= Math.max(0, 13 - drawtextsize); return topPt; } private void drawHillLabelLines(Canvas canvas, int toppt) { int alpha = ALPHA_LINE_MAX; // draw lines first for (int i = 0; i < hillsToPlot.size(); i++) { textPaint.setARGB(alpha, 255, 255, 255); strokePaint.setARGB(alpha, 0, 0, 0); tmpHill th = hillsToPlot.get(i); double vratio = Math.toDegrees(th.h.visualElevation - fe.getDirection()); int yloc = (int)((scrheight * vratio / vfov) + (scrheight/2)); int xloc = ((int)(scrwidth * th.ratio) + (scrwidth/2)); canvas.drawLine(xloc, yloc, xloc, th.toppt - toppt, strokePaint); canvas.drawLine(xloc, yloc, xloc, th.toppt - toppt, textPaint); canvas.drawLine(xloc-20, th.toppt - toppt, xloc+20, th.toppt - toppt, strokePaint); canvas.drawLine(xloc-20, th.toppt - toppt, xloc+20, th.toppt - toppt, textPaint); if (alpha - ALPHA_DECREMENT >= ALPHA_LINE_MIN) { alpha -= ALPHA_DECREMENT; } } } private void drawHillLabelText(Canvas canvas, int toppt) { boolean moreinfo; Float drawtextsize = textsize; int alpha = ALPHA_LABEL_MAX; // draw text over top for (int i = 0; i < hillsToPlot.size(); i++) { textPaint.setARGB(alpha, 255, 255, 255); strokePaint.setARGB(Math.min(alpha, ALPHA_STROKE_MIN), 0, 0, 0); textPaint.setTextSize(drawtextsize); strokePaint.setTextSize(drawtextsize); tmpHill th = hillsToPlot.get(i); moreinfo = (showdir || showdist || showheight && th.h.height > 0); int xloc = ((int)(scrwidth * th.ratio) + (scrwidth/2)); Rect bnds = new Rect(); strokePaint.getTextBounds(th.h.hillname,0,th.h.hillname.length(),bnds); bnds.left += xloc - (textPaint.measureText(th.h.hillname) / 2.0); bnds.right += xloc - (textPaint.measureText(th.h.hillname) / 2.0); bnds.top += th.toppt - 5 - toppt; if (moreinfo) bnds.top -= drawtextsize; bnds.bottom += th.toppt-5 - toppt; // draws bounding box of touch region to select hill //canvas.drawRect(bnds, strokePaint); mMarkers.add(new HillMarker(th.h.id, bnds)); canvas.drawText(th.h.hillname, xloc, th.toppt - ((moreinfo)?drawtextsize:0) - 5 - toppt, strokePaint); canvas.drawText(th.h.hillname, xloc, th.toppt - ((moreinfo)?drawtextsize:0) - 5 - toppt, textPaint); if (showdir || showdist || showheight) { boolean hascontents = false; String marker = " ("; if (showdir) { hascontents = true; marker += Math.floor(10*th.h.direction)/10 + "\u00B0"; } if (showdist) { hascontents = true; double multip = (typeunits)?1:0.621371; marker += (showdir ? " " : "") + Math.floor(10*th.h.distance*multip)/10; if (typeunits) marker += "km"; else marker += "miles"; } if (showheight) { if (th.h.height > 0) { hascontents = true; marker += ((showdir || showdist) ? " " : "") + distanceAsImperialOrMetric(th.h.height); } } marker += ")"; if (hascontents) { canvas.drawText(marker, xloc, th.toppt-5 - toppt, strokePaint); canvas.drawText(marker, xloc, th.toppt-5 - toppt, textPaint); } } if (alpha - ALPHA_DECREMENT >= ALPHA_LABEL_MIN) { alpha -= ALPHA_DECREMENT; } if (drawtextsize - TEXT_SIZE_DECREMENT >= TEXT_SIZE_MIN) { drawtextsize -= TEXT_SIZE_DECREMENT; } } } private void drawLocationAndOrientationStatus(Canvas canvas) { textPaint.setTextSize(mMainTextSize); strokePaint.setTextSize(mMainTextSize); textPaint.setARGB(255, 255, 255, 255); strokePaint.setARGB(255, 0, 0, 0); String compadj = (compassAdjustment>=0)?"+":""; compadj += String.format("%.01f", compassAdjustment); String basetext = "" + (int)fd.getDirection() + (char)0x00B0; basetext +=" (adj:"+compadj+")"; basetext +=" FOV: "+String.format("%.01f", hfov); if (badsensor) { canvas.drawText( "Recalibrate sensor!", 10, 80, paint); } Location curLocation = mGPS.getCurrentLocation(); if (curLocation != null) { acc = "+/- " + distanceAsImperialOrMetric(curLocation.getAccuracy()); } else { acc = "?"; } basetext +=" Location " + acc; canvas.drawText( basetext, scrwidth/2, scrheight-70, strokePaint); canvas.drawText( basetext, scrwidth/2, scrheight-70, textPaint); basetext = ""; if (curLocation == null) basetext = "No GPS position yet"; else if (curLocation.getAccuracy() > 200) basetext = "Warning - GPS position too inaccurate"; if (basetext != "") { canvas.drawText( basetext, scrwidth/2, scrheight/2, strokePaint); canvas.drawText( basetext, scrwidth/2, scrheight/2, textPaint); } int va = fd.GetVariation(); variationPaint.setARGB(255, 255, 0, 0); variationPaint.setStrokeWidth(4); int dashlength = scrheight / 10; for (int i = 0; i < 360; i+=15) { if (i > va) variationPaint.setARGB(255, 0, 255, 0); canvas.drawLine((scrwidth/10)+(dashlength/5*(float)Math.sin( Math.toRadians(i))), scrheight-(scrheight/5)-(dashlength/5*(float)Math.cos( Math.toRadians(i))), (scrwidth/10)+(dashlength*(float)Math.sin( Math.toRadians(i))), scrheight-(scrheight/5)-(dashlength*(float)Math.cos( Math.toRadians(i))), variationPaint); } } private void drawSettingsButton(Canvas canvas) { //settingPaint.setStyle(Paint.Style.STROKE); settingPaint2.setStyle(Paint.Style.STROKE); settingPaint.setAntiAlias(true); settingPaint2.setAntiAlias(true); settingPaint2.setStrokeWidth((int)(scrwidth/100.0)); //settingPaint.setStrokeWidth((int)(scrwidth/80.0)); settingPaint2.setARGB(255, 255, 255, 255); settingPaint.setARGB(255, 0, 0, 0); float barwidth = scrwidth/12.0f; int startPtw = scrwidth/60; int startPth = scrheight/60; int baroffset = scrwidth/50; canvas.drawRect(0.0f, 0.0f, barwidth + (startPtw*2), baroffset * 3.3f, settingPaint); canvas.drawLine(startPtw, startPth, startPtw + barwidth, startPth, settingPaint2); canvas.drawLine(startPtw, startPth+baroffset, startPtw + barwidth, startPth+baroffset, settingPaint2); baroffset += baroffset; canvas.drawLine(startPtw, startPth+baroffset, startPtw + barwidth, startPth+baroffset, settingPaint2); } private void drawCalibrationInstructions(Canvas canvas) { // adjust text to fit any screen - lol, so hacky :-D boolean happyWithSize = false; do { textPaint.setTextSize(mMainTextSize); float sz = textPaint.measureText("screen, wait for stabilisation, and tap again."); if (sz > scrwidth*0.7 ) { mMainTextSize--; } else if (sz < scrwidth*0.6) { mMainTextSize++; } else { happyWithSize = true; } } while (!happyWithSize); textPaint.setTextAlign(Paint.Align.LEFT); textPaint.setARGB(255, 255, 255, 255); paint.setARGB(100, 0, 0, 0); // left, top, right, bottom canvas.drawRoundRect(fovrect, 50,50,paint); canvas.drawText( "To calibrate, view an object at the very", txtgap, vtxtgap*3, textPaint); canvas.drawText( "left edge of the screen, and wait for", txtgap, vtxtgap*4, textPaint); canvas.drawText( "the direction sensor to stabilise. Then", txtgap, vtxtgap*5, textPaint); canvas.drawText( "tap the screen (gently, so you don't move", txtgap, vtxtgap*6, textPaint); canvas.drawText( "the view!). Then turn around until the ", txtgap, vtxtgap*7, textPaint); canvas.drawText( "object is at the very right edge of the ", txtgap, vtxtgap*8, textPaint); canvas.drawText( "screen, wait for stabilisation, and tap again.", txtgap, vtxtgap*9, textPaint); canvas.drawText( "Dir: " + (int)fd.getDirection() + (char)0x00B0 + " SD: "+fd.GetVariation(), scrwidth/2, scrheight-(vtxtgap*2), textPaint); textPaint.setTextAlign(Paint.Align.CENTER); if (calibrationStep == -1) { canvas.drawRect(0,0, 10, scrheight, transpRedPaint); } else { canvas.drawRect(scrwidth-10,0, scrwidth, scrheight, transpRedPaint); } int va = fd.GetVariation(); variationPaint.setARGB(255, 255, 0, 0); variationPaint.setStrokeWidth(4); int dashlength = scrheight / 10; for (int i = 0; i < 360; i+=15) { if (i > va) variationPaint.setARGB(255, 0, 255, 0); canvas.drawLine((scrwidth/10)+(dashlength/5*(float)Math.sin( Math.toRadians(i))), scrheight-(scrheight/5)-(dashlength/5*(float)Math.cos( Math.toRadians(i))), (scrwidth/10)+(dashlength*(float)Math.sin( Math.toRadians(i))), scrheight-(scrheight/5)-(dashlength*(float)Math.cos( Math.toRadians(i))), variationPaint); } } } public void onAccuracyChanged(Sensor sensor, int accuracy) {} public void onSensorChanged(SensorEvent event) { // some phones never set the sensormanager as reliable, even when readings are ok // That means if we try to block it, those phones will never get a compass reading. // So we let any readings through until we know we can get accurate readings. Once We know that // we'll block the inaccurate ones if (event.accuracy == SensorManager.SENSOR_STATUS_UNRELIABLE) { if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER && mHasAccurateAccelerometer) return; if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD && mHasAccurateGravity) return; } else { if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) mHasAccurateAccelerometer = true; if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) mHasAccurateGravity = true; } if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) mGravity = event.values; if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) mGeomagnetic = event.values; if (mGravity != null && mGeomagnetic != null) { float[] rotationMatrixA = mRotationMatrixA; if (SensorManager.getRotationMatrix(rotationMatrixA, null, mGravity, mGeomagnetic)) { Matrix tmpA = new Matrix(); tmpA.setValues(rotationMatrixA); tmpA.postRotate( -mDeclination ); tmpA.getValues(rotationMatrixA); float[] rotationMatrixB = mRotationMatrixB; switch (GetRotation()) { // portrait - normal case Surface.ROTATION_0: SensorManager.remapCoordinateSystem(rotationMatrixA, SensorManager.AXIS_X, SensorManager.AXIS_Z, rotationMatrixB); break; // rotated left (landscape) case Surface.ROTATION_90: SensorManager.remapCoordinateSystem(rotationMatrixA, //SensorManager.AXIS_Z, SensorManager.AXIS_MINUS_X, SensorManager.AXIS_X, SensorManager.AXIS_Z, rotationMatrixB); break; // upside down case Surface.ROTATION_180: SensorManager.remapCoordinateSystem(rotationMatrixA, SensorManager.AXIS_X, SensorManager.AXIS_Z, rotationMatrixB); break; // rotated right (landscape) case Surface.ROTATION_270: SensorManager.remapCoordinateSystem(rotationMatrixA, SensorManager.AXIS_MINUS_Z, SensorManager.AXIS_X, rotationMatrixB); break; default: break; } float[] dv = new float[3]; SensorManager.getOrientation(rotationMatrixB, dv); fd.AddLatest(dv[0]); fe.AddLatest((double)dv[1]); } mDraw.invalidate(); } } public boolean onTouch(View v, MotionEvent event) { if (!isCalibrated) { // this is the standard FOV calibration if (calibrationStep == -1) { calibrationStep = fd.getDirection(); Log.d("showmehills", "1st cal pt="+calibrationStep); } else { double curdir = fd.getDirection(); if (calibrationStep - curdir < 0) calibrationStep += 360; hfov = (float)(calibrationStep - curdir); Log.d("showmehills", "2nd cal pt="+curdir); Log.d("showmehills", "Setting hfov calibration="+hfov); isCalibrated = true; calibrationStep = 0; SharedPreferences customSharedPreference = PreferenceManager.getDefaultSharedPreferences(getBaseContext()); SharedPreferences.Editor editor = customSharedPreference.edit(); editor.putFloat("hfov", hfov); editor.putBoolean("isCalibrated", true); editor.commit(); } return false; } // check if it's multi-touch for pinch control of FOV /* if (event.getPointerCount() > 1) { // multi-touch float x = event.getX(0) - event.getX(1); float y = event.getY(0) - event.getY(1); if (pinchdist > 0) { float delta = pinchdist - FloatMath.sqrt(x * x + y * y); hfov += (delta > 0) ? 1 : -1; } pinchdist = FloatMath.sqrt(x * x + y * y); } else { pinchdist = 0; } */ if (event.getX() < scrwidth / 8 && event.getY() < scrheight / 8) { openOptionsMenu(); } else { Iterator<HillMarker> itr = mMarkers.iterator(); while (itr.hasNext()) { HillMarker m = itr.next(); if (m.location.contains((int)event.getX(), (int)event.getY())) { Intent infoActivity = new Intent(getBaseContext(),HillInfo.class); Bundle b = new Bundle(); b.putInt("key", m.hillid); infoActivity.putExtras(b); startActivity(infoActivity); } } } return false; } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { super.onKeyDown(keyCode, event); switch(keyCode) { case KeyEvent.KEYCODE_VOLUME_UP: compassAdjustment+=0.1; return true; case KeyEvent.KEYCODE_VOLUME_DOWN: compassAdjustment-=0.1; return true; } return super.onKeyDown(keyCode, event); } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ) { SharedPreferences customSharedPreference = PreferenceManager.getDefaultSharedPreferences(getBaseContext()); SharedPreferences.Editor editor = customSharedPreference.edit(); editor.putFloat("compassAdjustment", compassAdjustment); editor.commit(); return true; } return super.onKeyUp(keyCode, event); } class LocationTimerTask extends TimerTask { @Override public void run() { Log.d("showmehills", "renew GPS search"); runOnUiThread(new Runnable() { public void run() { mGPS.RenewLocation(); } }); } } public LocationManager GetLocationManager() { return (LocationManager)getSystemService(Context.LOCATION_SERVICE); } private String distanceAsImperialOrMetric(double distance) { if (typeunits) return (int)distance + "m"; else return (int)(distance*3.2808399) + "ft"; } }