package org.microg.networklocation.data;
import android.location.Location;
import android.os.Bundle;
import android.util.Log;
import org.microg.networklocation.MainService;
import org.microg.networklocation.database.LocationDatabase;
import org.microg.networklocation.retriever.CellSpecRetriever;
import org.microg.networklocation.retriever.WifiSpecRetriever;
import java.util.*;
public class LocationCalculator {
public static final int MAX_WIFI_RADIUS = 500;
private static final String TAG = "nlp.LocationCalculator";
private final LocationDatabase locationDatabase;
private final LocationRetriever locationRetriever;
private final CellSpecRetriever cellSpecRetriever;
private final WifiSpecRetriever wifiSpecRetriever;
public LocationCalculator(LocationDatabase locationDatabase, LocationRetriever locationRetriever,
CellSpecRetriever cellSpecRetriever, WifiSpecRetriever wifiSpecRetriever) {
this.locationDatabase = locationDatabase;
this.locationRetriever = locationRetriever;
this.cellSpecRetriever = cellSpecRetriever;
this.wifiSpecRetriever = wifiSpecRetriever;
}
private static <T extends PropSpec> Collection<Collection<LocationSpec<T>>> divideInClasses(
Collection<LocationSpec<T>> locationSpecs, double accuracy) {
Collection<Collection<LocationSpec<T>>> classes = new ArrayList<Collection<LocationSpec<T>>>();
for (LocationSpec<T> locationSpec : locationSpecs) {
boolean used = false;
for (Collection<LocationSpec<T>> locClass : classes) {
if (locationCompatibleWithClass(locationSpec, locClass, accuracy)) {
locClass.add(locationSpec);
used = true;
}
}
if (!used) {
Collection<LocationSpec<T>> locClass = new ArrayList<LocationSpec<T>>();
locClass.add(locationSpec);
classes.add(locClass);
}
}
return classes;
}
private static <T extends PropSpec> boolean locationCompatibleWithClass(LocationSpec<T> locationSpec,
Collection<LocationSpec<T>> locClass,
double accuracy) {
for (LocationSpec<T> spec : locClass) {
if ((locationSpec.distanceTo(spec) - locationSpec.getAccuracy() - spec.getAccuracy() -
accuracy) < 0) {
return true;
}
}
return false;
}
private static <T extends PropSpec> boolean locationCompatibleWithClass(Location location,
Collection<LocationSpec<T>> locClass) {
for (LocationSpec<T> spec : locClass) {
if (MainService.DEBUG) {
Log.d(TAG, "location: " + location + ", spec: " + spec + " => dist:" + spec.distanceTo(location) + "m");
}
if ((spec.distanceTo(location) - location.getAccuracy() - spec.getAccuracy()) < 0) {
return true;
}
}
return false;
}
private <T extends PropSpec> Location getAverageLocation(Collection<LocationSpec<T>> locationSpecs) {
// TODO: This is a stupid way to do this, we could do better by using the signal strength and triangulation
double latSum = 0, lonSum = 0, accSum = 0;
for (LocationSpec<T> locationSpec : locationSpecs) {
latSum += locationSpec.getLatitude();
lonSum += locationSpec.getLongitude();
accSum += locationSpec.getAccuracy();
}
Location location = new Location("network");
location.setAccuracy((float) (accSum / locationSpecs.size()));
location.setLatitude(latSum / locationSpecs.size());
location.setLongitude(lonSum / locationSpecs.size());
return location;
}
public Location getCurrentCellLocation() {
Collection<LocationSpec<CellSpec>> cellLocationSpecs = getLocation(getCurrentCells());
if ((cellLocationSpecs == null) || cellLocationSpecs.isEmpty()) {
return null;
}
Location location = getAverageLocation(cellLocationSpecs);
Bundle b = new Bundle();
b.putString("networkLocationType", "cell");
location.setExtras(b);
return location;
}
private Collection<CellSpec> getCurrentCells() {
return cellSpecRetriever.retrieveCellSpecs();
}
public Location getCurrentLocation() {
Location cellLocation = getCurrentCellLocation();
Location wifiLocation = getCurrentWifiLocation(cellLocation);
if (wifiLocation != null) {
return wifiLocation;
}
return cellLocation;
}
public Location getCurrentWifiLocation(Location cellLocation) {
Collection<LocationSpec<WifiSpec>> wifiLocationSpecs = getLocation(getCurrentWifis());
if (wifiLocationSpecs.isEmpty() || ((cellLocation == null) && (wifiLocationSpecs.size() < 2))) {
return null;
}
Location location = null;
if (cellLocation == null) {
List<Collection<LocationSpec<WifiSpec>>> classes = new ArrayList<Collection<LocationSpec<WifiSpec>>>(
divideInClasses(wifiLocationSpecs, MAX_WIFI_RADIUS));
Collections.sort(classes, CollectionSizeComparator.INSTANCE);
location = getAverageLocation(classes.get(0));
} else {
List<Collection<LocationSpec<WifiSpec>>> classes = new ArrayList<Collection<LocationSpec<WifiSpec>>>(
divideInClasses(wifiLocationSpecs, cellLocation.getAccuracy()));
Collections.sort(classes, CollectionSizeComparator.INSTANCE);
for (Collection<LocationSpec<WifiSpec>> locClass : classes) {
if (MainService.DEBUG) {
Log.d(TAG, "Test location class with "+locClass.size()+" entries");
}
if (locationCompatibleWithClass(cellLocation, locClass)) {
if (MainService.DEBUG) {
Log.d(TAG, "Location class matches, using its average");
}
location = getAverageLocation(locClass);
break;
}
}
}
if (location != null) {
Bundle b = new Bundle();
b.putString("networkLocationType", "wifi");
location.setExtras(b);
}
return location;
}
private Collection<WifiSpec> getCurrentWifis() {
return wifiSpecRetriever.retrieveWifiSpecs();
}
private <T extends PropSpec> Collection<LocationSpec<T>> getLocation(Collection<T> specs) {
Collection<LocationSpec<T>> locationSpecs = new HashSet<LocationSpec<T>>();
for (T spec : specs) {
LocationSpec<T> locationSpec = locationDatabase.get(spec);
if (locationSpec == null) {
locationRetriever.queueLocationRetrieval(spec);
} else if (!locationSpec.isUndefined()){
locationSpecs.add(locationSpec);
}
}
return locationSpecs;
}
public static class CollectionSizeComparator implements Comparator<Collection<LocationSpec<WifiSpec>>> {
public static CollectionSizeComparator INSTANCE = new CollectionSizeComparator();
@Override
public int compare(Collection<LocationSpec<WifiSpec>> left, Collection<LocationSpec<WifiSpec>> right) {
return (left.size() < right.size()) ? -1 : ((left.size() > right.size()) ? 1 : 0);
}
}
}