package geo;
import gl.GLCamera;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
import java.util.List;
import java.util.Locale;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import listeners.eventManagerListeners.LocationEventListener;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import system.EventManager;
import system.SimpleLocationManager;
import util.Log;
import util.Wrapper;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.location.LocationManager;
import android.net.Uri;
import android.provider.Settings;
/**
* This util class is a collection of all common location related operations
* like enabling GPS, receiving the current position, mapping an address to gps
* coordinates or converting to correct position values
* {@link GeoUtils#convertDegreesMinutesSecondsToDecimalDegrees(double, double, double)}
*
* @author Spobo
*
*/
public class GeoUtils {
private static final String LOG_TAG = "Geo Utils";
private Geocoder myGeoCoder;
private Context myContext;
private SimpleNodeEdgeListener defaultNEListener;
public GeoUtils(Context context, GLCamera glCamera) {
myContext = context;
myGeoCoder = new Geocoder(context, Locale.getDefault());
defaultNEListener = new DefaultNodeEdgeListener(glCamera);
}
/**
* In DroidAR all coordinates have to be decimal degrees. Use this method if
* you have to convert to decimal degrees.
*
* Example usage: <br>
* 16� 19' 28,29" to 16,324525�
*
* @param degree
* 16
* @param minutes
* 19
* @param seconds
* 28,29
* @return 16,324525�
*/
public static double convertDegreesMinutesSecondsToDecimalDegrees(
double degree, double minutes, double seconds) {
return degree + ((minutes + (seconds / 60)) / 60) / 60;
}
/**
* This method returns the best match for a specified position. It could for
* example be used to calculate the closest address to your current
* location.
*
* @param location
* @return the closest address to the {@link GeoObj}
*/
public Address getBestAddressForLocation(GeoObj location) {
try {
List<Address> locations = myGeoCoder.getFromLocation(
location.getLatitude(), location.getLongitude(), 1);
if (locations.size() > 0) {
return locations.get(0);
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* Returns the position of an specified address (Streetname e.g.)
*
* @param address
* @return null if the address could not be found
*/
public GeoObj getBestLocationForAddress(String address) {
try {
List<Address> addresses = myGeoCoder
.getFromLocationName(address, 5);
if (addresses.size() > 0) {
GeoObj g = new GeoObj(addresses.get(0));
g.getInfoObject().setShortDescr(
address + " (" + g.getInfoObject().getShortDescr()
+ ")");
return g;
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* This will search for a specified address and return the found results
*
* @param address
* @param maxResults
* number of results
* @return a {@link GeoGraph} with maxResults many {@link GeoObj}s as
* specified
*/
public GeoGraph getLocationListForAddress(String address, int maxResults) {
try {
List<Address> addresses = myGeoCoder.getFromLocationName(address,
maxResults);
if (addresses.size() > 0) {
GeoGraph result = new GeoGraph();
for (int i = 0; i < addresses.size(); i++) {
result.add(new GeoObj(addresses.get(i)));
}
return result;
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public String getStreetFor(GeoObj geoPos) {
try {
return getBestAddressForLocation(geoPos).getAddressLine(0);
} catch (Exception e) {
}
return null;
}
public String getCityFor(GeoObj currentPos) {
try {
return getBestAddressForLocation(currentPos).getAddressLine(1)
.split(" ")[1];
} catch (Exception e) {
}
return null;
}
/**
* use {@link SimpleLocationManager#getCurrentLocation(Context)} instead
*
* This method will try to get the most accurate position currently
* available. This includes also the last known position of the device if no
* current position sources can't be accessed so the returned position might
* be outdated <br>
* <br>
* If you need permanent location updates better create a
* {@link LocationEventListener} and register it at
* {@link EventManager#addOnLocationChangedAction(LocationEventListener)}
* instead of calling this method here frequently.
*
* @param context
* @return the current location
*/
@Deprecated
public static Location getCurrentLocation(Context context) {
return SimpleLocationManager.getInstance(context).getCurrentLocation();
}
/**
* use {@link SimpleLocationManager#getCurrentLocation(Context)} instead
*
* See {@link GeoUtils#getCurrentLocation(Context)}
*
* @return
*/
@Deprecated
public Location getCurrentLocation() {
return getCurrentLocation(myContext);
}
/**
* Use {@link SimpleLocationManager#getCurrentLocation(int)} instead
*
* @param context
* @param accuracy
* @return
*/
@Deprecated
public static Location getCurrentLocation(Context context, int accuracy) {
return SimpleLocationManager.getInstance(context).getCurrentLocation(
accuracy);
}
/**
* @param startPos
* @param destPos
* @param myResultingPath
* in this Wrapper the resulting path will be stored
* @param byWalk
* @return
*/
public boolean getPathFromAtoB(GeoObj startPos, GeoObj destPos,
Wrapper myResultingPath, boolean byWalk) {
GeoGraph result = getPathFromAtoB(startPos, destPos, byWalk);
if (result != null) {
Log.d(LOG_TAG, "Found way on maps!");
Log.d(LOG_TAG, "Path infos: " + result.toString());
myResultingPath.setTo(result);
return true;
}
Log.d(LOG_TAG, "No way on maps found :(");
return false;
}
public GeoGraph getPathFromAtoB(GeoObj startPos, GeoObj destPos,
boolean byWalk) {
return getPathFromAtoB(startPos, destPos, byWalk, null, null);
}
/**
* Uses google maps to calculate the way from the start pos to the
* destination pos
*
* @param startPos
* @param destPos
* @param byWalk
* @param nodeListener
* @param edgeListener
* @return
*/
public GeoGraph getPathFromAtoB(GeoObj startPos, GeoObj destPos,
boolean byWalk, NodeListener nodeListener, EdgeListener edgeListener) {
if (startPos == null || destPos == null) {
Log.d(LOG_TAG,
"Gmap getPathFromAtoB error: startPoint or target were null");
return null;
}
// try to open the url:
try {
String url = generateUrl(startPos, destPos, byWalk);
//kml does not work anymore, see a solution in http://stackoverflow.com/questions/11745314/why-retrieving-google-directions-for-android-using-kml-data-is-not-working-anymo/11745316#11745316
// Document kml = getDocumentFromUrl(url);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document kml = db.parse( url );
if (kml.getElementsByTagName("GeometryCollection").getLength() > 0) {
String path = kml.getElementsByTagName("GeometryCollection")
.item(0).getFirstChild().getFirstChild()
.getFirstChild().getNodeValue();
final String[] pairs = path.split(" ");
GeoGraph result = new GeoGraph();
result.getInfoObject().setShortDescr(
"Resulting graph for "
+ destPos.getInfoObject().getShortDescr());
result.setIsPath(true);
result.setNonDirectional(false);
if (nodeListener != null) {
nodeListener.addFirstNodeToGraph(result, startPos);
} else {
defaultNEListener.addFirstNodeToGraph(result, startPos);
}
GeoObj lastPoint = startPos;
for (int i = 1; i < pairs.length; i++) {
String[] geoCords = pairs[i].split(",");
GeoObj currentPoint = new GeoObj(
Double.parseDouble(geoCords[1]),
Double.parseDouble(geoCords[0]),
Double.parseDouble(geoCords[2]));
if (!currentPoint.hasSameCoordsAs(lastPoint)) {
if (nodeListener != null) {
nodeListener.addNodeToGraph(result, currentPoint);
} else {
defaultNEListener.addNodeToGraph(result,
currentPoint);
}
if (edgeListener != null) {
edgeListener.addEdgeToGraph(result, lastPoint,
currentPoint);
} else {
defaultNEListener.addEdgeToGraph(result, lastPoint,
currentPoint);
}
Log.d(LOG_TAG, " + adding Waypoint:" + pairs[i]);
lastPoint = currentPoint;
}
}
if (lastPoint != null && !lastPoint.hasSameCoordsAs(destPos)) {
/*
* add the egde to the past point:
*/
if (edgeListener != null) {
edgeListener.addEdgeToGraph(result, lastPoint, destPos);
} else {
defaultNEListener.addEdgeToGraph(result, lastPoint,
destPos);
}
}
if (nodeListener != null) {
nodeListener.addNodeToGraph(result, destPos);
} else {
defaultNEListener.addNodeToGraph(result, destPos);
}
/*
* an alternative for adding the edges would be to call
* result.addEdgesToCreatePath(); but this would be a bit
* slower..
*/
return result;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Deprecated
private Document getDocumentFromUrl(String url) throws IOException,
MalformedURLException, ProtocolException,
FactoryConfigurationError, ParserConfigurationException,
SAXException {
HttpURLConnection urlConnection = (HttpURLConnection) new URL(url)
.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.setDoOutput(true);
urlConnection.setDoInput(true);
urlConnection.connect();
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
// get the kml file. And parse it to get the coordinates(direction
// route):
Document doc = db.parse(urlConnection.getInputStream());
return doc;
}
private String generateUrl(GeoObj startPos, GeoObj destPos, boolean byWalk) {
// build the url string:
StringBuilder urlString = new StringBuilder();
urlString.append("http://maps.google.com/maps?f=d&hl=en");
if (byWalk) {
urlString.append("&dirflg=w");
}
urlString.append("&saddr=");// from
urlString.append(Double.toString(startPos.getLatitude()));
urlString.append(",");
urlString.append(Double.toString(startPos.getLongitude()));
urlString.append("&daddr=");// to
urlString.append(Double.toString(destPos.getLatitude()));
urlString.append(",");
urlString.append(Double.toString(destPos.getLongitude()));
urlString.append("&;ie=UTF8&0&om=0&output=kml");
return urlString.toString();
}
public static boolean isGPSDisabled(Context context) {
return !((LocationManager) context
.getSystemService(Context.LOCATION_SERVICE))
.isProviderEnabled(LocationManager.GPS_PROVIDER);
}
public boolean isGPSDisabled() {
return isGPSDisabled(myContext);
}
/**
* @param activity
* @return true if GPS could be enabled without user interaction, else the
* settings will be started and false is returned
*/
public static boolean enableGPS(Activity activity) {
return switchGPS(activity, true, true);
}
/**
* This method will activate gps if it is disabled
*
* @param activity
*/
public static void enableLocationProvidersIfNeeded(Activity activity) {
try {
if (isGPSDisabled(activity)) {
switchGPS(activity, true, true);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static boolean isWifiDisabled(Activity activity) {
return !((LocationManager) activity
.getSystemService(Context.LOCATION_SERVICE))
.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
}
/**
* @param activity
* @return true if GPS could be disabled without user interaction, else the
* settings will be started and false is returned
*/
public static boolean disableGPS(Activity activity) {
return disableGPS(activity, false);
}
/**
* @param activity
* @return true if GPS could be disabled without user interaction, else the
* settings will be started and false is returned
*/
public static boolean disableGPS(Activity activity,
boolean showSettingsIfAutoSwitchImpossible) {
return switchGPS(activity, false, showSettingsIfAutoSwitchImpossible);
}
/**
* @param activity
* @param enableGPS
* @param showSettingsIfAutoSwitchImpossible
* @return true if GPS could be switched to the desired value without user
* interaction, else the settings will be started and false is
* returned
*/
public static boolean switchGPS(Activity activity, boolean enableGPS,
boolean showSettingsIfAutoSwitchImpossible) {
if (canTurnOnGPSAutomatically(activity)) {
String provider = Settings.Secure.getString(
activity.getContentResolver(),
Settings.Secure.LOCATION_PROVIDERS_ALLOWED);
boolean currentlyEnabled = provider.contains("gps");
if (!currentlyEnabled && enableGPS) {
pokeGPSButton(activity);
} else if (currentlyEnabled && !enableGPS) {
pokeGPSButton(activity);
}
return true;
} else if (showSettingsIfAutoSwitchImpossible) {
Log.d(LOG_TAG, "Can't enable GPS automatically, will start "
+ "settings for manual enabling!");
openLocationSettingsPage(activity);
}
return false;
}
public static void openLocationSettingsPage(Activity activity) {
activity.startActivity(new Intent(
android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS));
}
private static void pokeGPSButton(Activity activity) {
final Intent poke = new Intent();
poke.setClassName("com.android.settings",
"com.android.settings.widget.SettingsAppWidgetProvider");
poke.addCategory(Intent.CATEGORY_ALTERNATIVE);
poke.setData(Uri.parse("3"));
activity.sendBroadcast(poke);
}
/**
* source from
* http://stackoverflow.com/questions/4721449/enable-gps-programatically
* -like-tasker
*/
private static boolean canTurnOnGPSAutomatically(Context c) {
PackageInfo pacInfo = null;
try {
pacInfo = c.getPackageManager().getPackageInfo(
"com.android.settings", PackageManager.GET_RECEIVERS);
} catch (NameNotFoundException e) {
Log.e(LOG_TAG, "com.android.settings package not found");
return false; // package not found
}
if (pacInfo != null) {
for (ActivityInfo actInfo : pacInfo.receivers) {
// test if recevier is exported. if so, we can toggle GPS.
if (actInfo.name
.equals("com.android.settings.widget.SettingsAppWidgetProvider")
&& actInfo.exported) {
return true;
}
}
}
return false; // default
}
}