/** Copyright 2015 Tim Engler, Rareventure LLC This file is part of Tiny Travel Tracker. Tiny Travel Tracker 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. Tiny Travel Tracker 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 Tiny Travel Tracker. If not, see <http://www.gnu.org/licenses/>. */ package com.rareventure.gps2.reviewer.map; import java.util.HashMap; import java.util.List; import java.util.Map; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Point; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.location.Criteria; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.Toast; import com.mapzen.tangram.LngLat; import com.mapzen.tangram.MapController; import com.mapzen.tangram.MapData; import com.rareventure.gps2.R; import com.rareventure.android.Util; import com.rareventure.android.AndroidPreferenceSet.AndroidPreferences; import com.rareventure.gps2.database.cache.AreaPanel; import com.rareventure.gps2.database.cache.AreaPanelCache; import com.rareventure.gps2.database.cache.AreaPanelSpaceTimeBox; //TODO 3: make this use gps service to determine the current location public class GpsLocationOverlay implements GpsOverlay, LocationListener { private OsmMapGpsTrailerReviewerMapActivity activity; private LocationManager lm; private long lastLocationReadingMs = 0; private float lastLocationAccuracy; private MapController mapController; private MapData mapData; private LngLat lastLoc = new LngLat(); private Map<String,String> props = new HashMap<>(); public GpsLocationOverlay(OsmMapGpsTrailerReviewerMapActivity activity) { this.activity = activity; //TODO 2.5: gps reader should piggyback when any other program starts //reading from the gps (if possible) //TODO 4: gps reader should start up all location updates, and continue // to run until it gets the best location (from the most accurate provider) // turning off each provider as soon as it gets a location lm = (LocationManager) activity.getSystemService(Context.LOCATION_SERVICE); // locationAnim = AnimationUtils.loadAnimation(activity, R.drawable.location_anim); } public LngLat getLastLoc() { return lastLoc; } public double getAbsPixelX2(long zoom8BitPrec) { double apX = AreaPanel.convertLonToXDouble(lastLoc.longitude); return AreaPanel.convertApXToAbsPixelX2(apX, zoom8BitPrec); } @Override public void onLocationChanged(Location location) { //if this location is a poorer reading // or the location provider doesn't keep track of accuracy, in case we'll // just say screw it and replace it //TODO 3: is this the right thing to do? if(location.getAccuracy() > lastLocationAccuracy || lastLocationAccuracy == 0) { //ignore it if it's within a certain threshold of time if(location.getTime() - lastLocationReadingMs <= prefs.maxTimeToKeepBetterAccuracyLocationReadingMs) return; if(location.getLatitude() == 0 && location.getLongitude() == 0) return; } boolean notifyWeHaveGps = lastLocationReadingMs == 0; //NOTE: this may return 0 is the gps provider doesn't keep track of accuracy lastLocationAccuracy = location.getAccuracy(); lastLocationReadingMs = location.getTime(); lastLoc.latitude = location.getLatitude(); lastLoc.longitude = location.getLongitude(); //TODO 2.1 turn off location known when unknown if(notifyWeHaveGps) { activity.notifyLocationKnown(); notifyWeHaveGps = false; } resetMapData(); } private void resetMapData() { synchronized(this) { if(mapData == null) return; } props.clear(); props.put("rotation", Double.toString(lastCompass)); //mapData.beginChangeBlock(); mapData.clear(); mapData.addPoint(lastLoc, props); //mapData.endChangeBlock(); mapController.requestRender(); } @Override public void onProviderDisabled(String provider) { //TODO 3: handle this gps location stuff better } @Override public void onProviderEnabled(String provider) { //TODO 3: handle this gps location stuff better } @Override public void onStatusChanged(String provider, int status, Bundle extras) { //TODO 3: handle this gps location stuff better } private Preferences prefs = new Preferences(); private float lastCompass; private long lastCompassReadingMs; @Override public void notifyScreenChanged(AreaPanelSpaceTimeBox newStBox) { } @Override public boolean onTap(float x, float y) { return false; } @Override public boolean onLongPressMove(float startX, float startY, float endX, float endY) { return false; } @Override public boolean onLongPressEnd(float startX, float startY, float endX, float endY) { return false; } @Override public void onPause() { lm.removeUpdates(this); } @Override public void onResume() { Criteria criteria = new Criteria(); criteria.setSpeedRequired(false); criteria.setAccuracy(Criteria.ACCURACY_FINE); criteria.setAltitudeRequired(false); criteria.setBearingRequired(false); criteria.setCostAllowed(false); String providerName = lm.getBestProvider(criteria, true); lm.requestLocationUpdates(providerName, 0, 0, this, activity.getMainLooper()); } @Override public void startTask(MapController mapController) { synchronized(this) { this.mapController = mapController; mapData = mapController.addDataLayer("mz_current_location"); } } public static class Preferences implements AndroidPreferences { /** * When polling the gps devices, we may get a poorer accuracy reading * after a reading with better accuracy. In this case, if the poorer * accuracy reading happens within the time threshold specified here * (as compared to the more accurate one, we keep the more accurate one) */ public int maxTimeToKeepBetterAccuracyLocationReadingMs = 30 * 1000; public float minChangeForRedraw = (float) (5*Math.PI / 180); public int minTimeForRedrawMs = 2000; } }