/*******************************************************************************
* Gaggle is Copyright 2010 by Geeksville Industries LLC, a California limited liability corporation.
*
* Gaggle is distributed under a dual license. We've chosen this approach because within Gaggle we've used a number
* of components that Geeksville Industries LLC might reuse for commercial products. Gaggle can be distributed under
* either of the two licenses listed below.
*
* 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.
*
* Commercial Distribution License
* If you would like to distribute Gaggle (or portions thereof) under a license other than
* the "GNU General Public License, version 2", contact Geeksville Industries. Geeksville Industries reserves
* the right to release Gaggle source code under a commercial license of its choice.
*
* GNU Public License, version 2
* All other distribution of Gaggle must conform to the terms of the GNU Public License, version 2. The full
* text of this license is included in the Gaggle source, see assets/manual/gpl-2.0.txt.
******************************************************************************/
package com.geeksville.location;
import java.util.HashMap;
import java.util.Observable;
import java.util.Observer;
import java.util.SortedMap;
import java.util.TreeMap;
import org.andnav.osm.util.GeoPoint;
import android.content.ComponentName;
import android.content.Context;
import android.content.ServiceConnection;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.location.Location;
import android.location.LocationListener;
import android.os.Bundle;
import android.os.IBinder;
import com.geeksville.android.PreferenceUtil;
import com.geeksville.gaggle.GagglePrefs;
import com.geeksville.gaggle.R;
/**
* Provide high level API to find nearby waypoints
*
* @author kevinh
*
* This class keeps a cache in memory for high speed queries about
* waypoints
*
* We will notify observers whenever the database has been updated and
* waypoint relative distances may have changed
*/
public class WaypointDB extends Observable implements LocationListener, ServiceConnection {
private LocationLogDbAdapter db;
private HashMap<Long, ExtendedWaypoint> wptsById = new HashMap<Long, ExtendedWaypoint>();
private SortedMap<String, ExtendedWaypoint> wptsByName;
private Context context;
private IGPSClient gps;
/**
* One row for each Waypoint type, one col for each WaypointColor
*/
Drawable[][] markers;
/**
* One row for each Waypoint type, one col for each WaypointColor
*/
private static final int[][] markerIds = {
{ R.drawable.blue, R.drawable.yellow, R.drawable.red },
{ R.drawable.lz_blue, R.drawable.lz_yellow, R.drawable.lz_red },
{ R.drawable.plane_blue, R.drawable.plane_yellow, R.drawable.plane_red },
{ R.drawable.flag, R.drawable.flag, R.drawable.flag } };
/**
* The ids for colors used when viewing text waypoint names
*/
private static final int[] markerColorIds = { R.color.glide_safe, R.color.glide_warn,
R.color.glide_danger };
int[] markerColors = new int[markerColorIds.length];
int pilotAlt;
/**
* Or null for unknown
*/
public GeoPoint pilotLoc;
/**
* Read from glideratio_pref but we reread each pilot update to get the
* latest value
*
*/
public float typicalGlideRatio, minAltMargin, extraAltMargin;
private long minTimeMs = 30 * 1000;
private float minDistMeters = 100.0f;
public WaypointDB(Context context, LocationLogDbAdapter db) {
this.db = db;
this.context = context;
GagglePrefs prefs = new GagglePrefs(context);
minTimeMs = prefs.getLogTimeInterval() * 1000;
minDistMeters = prefs.getLogDistanceInterval();
// FIXME - reread when prefs change
typicalGlideRatio = PreferenceUtil.getFloat(context, "glideratio_pref", 7.0f);
minAltMargin = PreferenceUtil.getFloat(context, "altmargin_min_pref", 0.0f);
extraAltMargin = PreferenceUtil.getFloat(context, "altmargin_extra_pref", 100f);
markers = new Drawable[Waypoint.Type.values().length][ExtendedWaypoint.Color.values().length];
for (int wpType = 0; wpType < Waypoint.Type.values().length; wpType++)
for (int wpColor = 0; wpColor < ExtendedWaypoint.Color.values().length; wpColor++)
markers[wpType][wpColor] = context.getResources().getDrawable(
markerIds[wpType][wpColor]);
for (int wpColor = 0; wpColor < ExtendedWaypoint.Color.values().length; wpColor++)
markerColors[wpColor] = context.getResources().getColor(markerColorIds[wpColor]);
// FIXME - use a better data structure
readDB(db);
}
/*
* (non-Javadoc)
*
* @see java.util.Observable#addObserver(java.util.Observer)
*/
@Override
public synchronized void addObserver(Observer observer) {
super.addObserver(observer);
if (countObservers() == 1) {
GPSClient.bindTo(context, this);
}
}
/*
* (non-Javadoc)
*
* @see java.util.Observable#deleteObserver(java.util.Observer)
*/
@Override
public synchronized void deleteObserver(Observer observer) {
// TODO Auto-generated method stub
super.deleteObserver(observer);
if (countObservers() == 0) {
if (gps != null)
gps.removeLocationListener(this);
GPSClient.unbindFrom(context, this);
}
}
/*
* (non-Javadoc)
*
* @see
* android.content.ServiceConnection#onServiceConnected(android.content.
* ComponentName, android.os.IBinder)
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
gps = (IGPSClient) service;
gps.addLocationListener(minTimeMs, minDistMeters, this);
}
/*
* (non-Javadoc)
*
* @see
* android.content.ServiceConnection#onServiceDisconnected(android.content
* .ComponentName)
*/
@Override
public void onServiceDisconnected(ComponentName name) {
gps = null;
}
/**
* Update the pilots location - used to calculate distances etc...
*
* @param l
* FIXME - call this function only rarely
*/
private void setPilotLocation(Location l) {
pilotAlt = (int) l.getAltitude();
pilotLoc = new GeoPoint((int) (l.getLatitude() * 1e6), (int) (l.getLongitude() * 1e6));
synchronized (wptsById) {
for (ExtendedWaypoint w : wptsById.values()) {
w.recalculate();
}
}
// Waypoint distances might have changed (FIXME, only publish if there
// has been an appreciable change in
// position)
setChanged();
notifyObservers();
}
public ExtendedWaypoint getNearestLZ() {
// FIXME - use some sort of tree or heap that we can update
// inexpensively. - also keep lzs only in that heap
int dist = Integer.MAX_VALUE;
ExtendedWaypoint closest = null;
synchronized (wptsById) {
for (ExtendedWaypoint w : wptsById.values()) {
if (w.type == Waypoint.Type.Landing && w.distanceFromPilotX < dist) {
dist = w.distanceFromPilotX;
closest = w;
}
}
}
return closest;
}
/**
* Add a new waypoint to the DB & cache
*
* @param w
*/
public long add(ExtendedWaypoint w) {
w.id = db.addWaypoint(w.name, w.description, w.latitude, w.longitude, w.altitude, w.type
.ordinal());
addToCache(w);
return w.id;
}
/**
* Return our name cache (generating if necessary)
*
* @return
*/
public SortedMap<String, ExtendedWaypoint> getNameCache() {
SortedMap<String, ExtendedWaypoint> map = wptsByName;
if (map == null) {
wptsByName = map = new TreeMap<String, ExtendedWaypoint>();
synchronized (wptsById) {
for (ExtendedWaypoint w : wptsById.values())
map.put(w.name, w);
}
}
return map;
}
public ExtendedWaypoint findByName(String name) {
return getNameCache().get(name);
}
/**
* Update our various flavors of cache
*
* @param w
*/
private void addToCache(ExtendedWaypoint w) {
w.setOwner(this);
synchronized (wptsById) {
wptsById.put(w.id, w);
}
wptsByName = null; // flush our by name cache
}
/**
* Read all the waypoints from the db (possibly move elsewhere and/or
* restrict the set of read waypoints)
*
* @param db
* @return
*/
private void readDB(LocationLogDbAdapter db) {
// First get all the flight info
Cursor pts = db.fetchWaypoints();
int numPts = pts.getCount();
for (int i = 0; i < numPts; i++) {
ExtendedWaypoint w = new ExtendedWaypoint(pts);
addToCache(w);
pts.moveToNext();
}
pts.close();
}
/**
* Get all the waypoints but sorted by distance
*
* @return
*/
public WaypointCursor fetchWaypointsByDistance() {
return new WaypointCursor(wptsById.values());
}
@Override
public void onLocationChanged(Location location) {
// We can only use this report if it has altitude
if (location.hasAltitude())
setPilotLocation(location);
}
@Override
public void onProviderDisabled(String provider) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
public void deleteAllWaypoints() {
db.deleteAllWaypoints();
// FIXME - use proper thread safety prims for these containers
wptsById.clear();
wptsByName = null;
}
public void deleteWaypoint(long id) {
// TODO Auto-generated method stub
db.deleteWaypoint(id);
wptsById.remove(id);
wptsByName = null;
}
/**
* Callback from waypoint
*
* @param w
*/
void updateWaypoint(ExtendedWaypoint w) {
db.updateWaypoint(w.id, w.name, w.description, w.latitude, w.longitude, w.altitude, w.type.ordinal());
wptsByName = null;
}
}