/* * Copyright 2010, 2011, 2012 mapsforge.org * * This program is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free Software * Foundation, either version 3 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License along with * this program. If not, see <http://www.gnu.org/licenses/>. */ package org.mapsforge.android.maps.overlay; import org.mapsforge.android.maps.MapView; import org.mapsforge.core.model.BoundingBox; import org.mapsforge.core.model.GeoPoint; import org.mapsforge.core.model.Point; import org.mapsforge.core.util.MercatorProjection; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.drawable.Drawable; import android.location.Criteria; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.location.LocationProvider; import android.os.Bundle; /** * A thread-safe {@link Overlay} implementation to display a {@link Circle} and a {@link Drawable} at the user's current * location. */ public class MyLocationOverlay implements LocationListener, Overlay { private static final int UPDATE_DISTANCE = 0; private static final int UPDATE_INTERVAL = 1000; /** * @param location * the location whose geographical coordinates should be converted. * @return a new GeoPoint with the geographical coordinates taken from the given location. */ public static GeoPoint locationToGeoPoint(Location location) { return new GeoPoint(location.getLatitude(), location.getLongitude()); } private static Paint getDefaultCircleFill() { return getPaint(Style.FILL, Color.BLUE, 48); } private static Paint getDefaultCircleStroke() { Paint paint = getPaint(Style.STROKE, Color.BLUE, 128); paint.setStrokeWidth(2); return paint; } private static Paint getPaint(Style style, int color, int alpha) { Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setStyle(style); paint.setColor(color); paint.setAlpha(alpha); return paint; } private boolean centerAtNextFix; private final Circle circle; private Location lastLocation; private final LocationManager locationManager; private final MapView mapView; private final Marker marker; private boolean myLocationEnabled; private boolean snapToLocationEnabled; /** * Constructs a new {@code MyLocationOverlay} with the given drawable and the default circle paints. * * @param context * a reference to the application context. * @param mapView * the {@code MapView} on which the location will be displayed. * @param drawable * a drawable to display at the current location (might be null). */ public MyLocationOverlay(Context context, MapView mapView, Drawable drawable) { this(context, mapView, drawable, getDefaultCircleFill(), getDefaultCircleStroke()); } /** * Constructs a new {@code MyLocationOverlay} with the given drawable and circle paints. * * @param context * a reference to the application context. * @param mapView * the {@code MapView} on which the location will be displayed. * @param drawable * a drawable to display at the current location (might be null). * @param circleFill * the {@code Paint} used to fill the circle that represents the current location (might be null). * @param circleStroke * the {@code Paint} used to stroke the circle that represents the current location (might be null). */ public MyLocationOverlay(Context context, MapView mapView, Drawable drawable, Paint circleFill, Paint circleStroke) { this.mapView = mapView; this.locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); this.marker = new Marker(null, drawable); this.circle = new Circle(null, 0, circleFill, circleStroke); } /** * Stops the receiving of location updates. Has no effect if location updates are already disabled. */ public synchronized void disableMyLocation() { if (this.myLocationEnabled) { this.myLocationEnabled = false; this.locationManager.removeUpdates(this); this.mapView.getOverlayController().redrawOverlays(); } } @Override public synchronized void draw(BoundingBox boundingBox, byte zoomLevel, Canvas canvas) { if (!this.myLocationEnabled) { return; } double canvasPixelLeft = MercatorProjection.longitudeToPixelX(boundingBox.minLongitude, zoomLevel); double canvasPixelTop = MercatorProjection.latitudeToPixelY(boundingBox.maxLatitude, zoomLevel); Point canvasPosition = new Point(canvasPixelLeft, canvasPixelTop); this.circle.draw(boundingBox, zoomLevel, canvas, canvasPosition); this.marker.draw(boundingBox, zoomLevel, canvas, canvasPosition); } /** * Enables the receiving of location updates from the most accurate {@link LocationProvider} available. * * @param centerAtFirstFix * whether the map should be centered to the first received location fix. * @return true if at least one location provider was found, false otherwise. */ public synchronized boolean enableMyLocation(boolean centerAtFirstFix) { if (!enableBestAvailableProvider()) { return false; } this.centerAtNextFix = centerAtFirstFix; return true; } /** * @return the most-recently received location fix (might be null). */ public synchronized Location getLastLocation() { return this.lastLocation; } /** * @return true if the map will be centered at the next received location fix, false otherwise. */ public synchronized boolean isCenterAtNextFix() { return this.centerAtNextFix; } /** * @return true if the receiving of location updates is currently enabled, false otherwise. */ public synchronized boolean isMyLocationEnabled() { return this.myLocationEnabled; } /** * @return true if the snap-to-location mode is enabled, false otherwise. */ public synchronized boolean isSnapToLocationEnabled() { return this.snapToLocationEnabled; } @Override public void onLocationChanged(Location location) { synchronized (this) { this.lastLocation = location; GeoPoint geoPoint = locationToGeoPoint(location); this.marker.setGeoPoint(geoPoint); this.circle.setGeoPoint(geoPoint); this.circle.setRadius(location.getAccuracy()); if (this.centerAtNextFix || this.snapToLocationEnabled) { this.centerAtNextFix = false; this.mapView.getMapViewPosition().setCenter(geoPoint); } } this.mapView.getOverlayController().redrawOverlays(); } @Override public void onProviderDisabled(String provider) { enableBestAvailableProvider(); } @Override public void onProviderEnabled(String provider) { enableBestAvailableProvider(); } @Override public void onStatusChanged(String provider, int status, Bundle extras) { // do nothing } /** * @param snapToLocationEnabled * whether the map should be centered at each received location fix. */ public synchronized void setSnapToLocationEnabled(boolean snapToLocationEnabled) { this.snapToLocationEnabled = snapToLocationEnabled; } private synchronized boolean enableBestAvailableProvider() { disableMyLocation(); Criteria criteria = new Criteria(); criteria.setAccuracy(Criteria.ACCURACY_FINE); String bestAvailableProvider = this.locationManager.getBestProvider(criteria, true); if (bestAvailableProvider == null) { return false; } this.locationManager.requestLocationUpdates(bestAvailableProvider, UPDATE_INTERVAL, UPDATE_DISTANCE, this); this.myLocationEnabled = true; return true; } }