/*
* Copyright 2012 University of South Florida
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package edu.usf.cutr.opentripplanner.android.util;
import com.google.android.gms.maps.model.LatLng;
import com.google.maps.android.PolyUtil;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.preference.PreferenceManager;
import android.util.Log;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import edu.usf.cutr.opentripplanner.android.OTPApp;
import edu.usf.cutr.opentripplanner.android.R;
import edu.usf.cutr.opentripplanner.android.model.Server;
import edu.usf.cutr.opentripplanner.android.pois.GooglePlaces;
import edu.usf.cutr.opentripplanner.android.pois.Nominatim;
import edu.usf.cutr.opentripplanner.android.pois.POI;
import edu.usf.cutr.opentripplanner.android.pois.Places;
/**
* Various utilities related to location data
*
* @author Khoa Tran
*/
public class LocationUtil {
// Borrowed from
// http://jeffreysambells.com/posts/2010/05/27/decoding-polylines-from-google-maps-direction-api-with-java/
/**
* Decode a set of GeoPoints from an EncodedPolylineBean object from the OTP
* server project
*
* @param encoded string from EncodedPolylineBean
* @return set of GeoPoints represented by the EncodedPolylineBean string
*/
public static List<LatLng> decodePoly(String encoded) {
List<LatLng> poly = new ArrayList<LatLng>();
int index = 0, len = encoded.length();
int lat = 0, lng = 0;
while (index < len) {
int b, shift = 0, result = 0;
do {
b = encoded.charAt(index++) - 63;
result |= (b & 0x1f) << shift;
shift += 5;
} while (b >= 0x20);
int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
lat += dlat;
shift = 0;
result = 0;
do {
b = encoded.charAt(index++) - 63;
result |= (b & 0x1f) << shift;
shift += 5;
} while (b >= 0x20);
int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
lng += dlng;
LatLng ll = new LatLng(((double) lat / 1E5),
(((double) lng / 1E5)));
poly.add(ll);
}
return poly;
}
/**
* Compares the current location of the user against a bounding box for a OTP server
*
* @param location current location of the user
* @param selectedServer OTP server being compared to the current location
* @return true if the location of the user is within the bounding box of the selectedServer,
* false if it is not
*/
public static boolean checkPointInBoundingBox(LatLng location, Server selectedServer) {
LatLng lowerLeft = new LatLng(selectedServer.getLowerLeftLatitude(),
selectedServer.getLowerLeftLongitude());
LatLng upperLeft = new LatLng(selectedServer.getUpperRightLatitude(),
selectedServer.getLowerLeftLongitude());
LatLng upperRight = new LatLng(selectedServer.getUpperRightLatitude(),
selectedServer.getUpperRightLongitude());
LatLng lowerRight = new LatLng(selectedServer.getLowerLeftLatitude(),
selectedServer.getUpperRightLongitude());
List<LatLng> rectangle = new ArrayList<LatLng>(2);
rectangle.add(lowerLeft);
rectangle.add(upperLeft);
rectangle.add(upperRight);
rectangle.add(lowerRight);
return PolyUtil.containsLocation(location, rectangle, true);
}
public static ArrayList<CustomAddress> processGeocoding(Context context, Server selectedServer,
String... reqs) {
return processGeocoding(context, selectedServer, false, reqs);
}
public static ArrayList<CustomAddress> processGeocoding(Context context, Server selectedServer, boolean geocodingForMarker, String... reqs) {
ArrayList<CustomAddress> addressesReturn = new ArrayList<CustomAddress>();
String address = reqs[0];
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
if (address == null || address.equalsIgnoreCase("")) {
return null;
}
double latitude = 0, longitude = 0;
boolean latLngSet = false;
try{
if (reqs.length >= 3) {
latitude = Double.parseDouble(reqs[1]);
longitude = Double.parseDouble(reqs[2]);
latLngSet = true;
}
}
catch(Exception e){
Log.d(OTPApp.TAG, "Geocoding without reference latitude/longitude");
}
if (address.equalsIgnoreCase(context.getString(R.string.text_box_my_location))) {
if (latLngSet){
CustomAddress addressReturn = new CustomAddress(context.getResources().getConfiguration().locale);
addressReturn.setLatitude(latitude);
addressReturn.setLongitude(longitude);
addressReturn.setAddressLine(addressReturn.getMaxAddressLineIndex() + 1,
context.getString(R.string.text_box_my_location));
addressesReturn.add(addressReturn);
return addressesReturn;
}
return null;
}
List<CustomAddress> addresses = new ArrayList<CustomAddress>();
if (prefs.getBoolean(OTPApp.PREFERENCE_KEY_USE_ANDROID_GEOCODER, true)) {
Geocoder gc = new Geocoder(context);
try {
List<Address> androidTypeAddresses;
if (selectedServer != null) {
androidTypeAddresses = gc.getFromLocationName(address,
context.getResources().getInteger(R.integer.geocoder_max_results),
selectedServer.getLowerLeftLatitude(),
selectedServer.getLowerLeftLongitude(),
selectedServer.getUpperRightLatitude(),
selectedServer.getUpperRightLongitude());
} else {
androidTypeAddresses = gc.getFromLocationName(address,
context.getResources().getInteger(R.integer.geocoder_max_results));
}
for (Address androidTypeAddress : androidTypeAddresses){
addresses.add(new CustomAddress(androidTypeAddress));
}
} catch (IOException e) {
e.printStackTrace();
}
}
addresses = filterAddressesBBox(selectedServer, addresses);
boolean resultsCloseEnough = true;
if (geocodingForMarker && latLngSet){
float results[] = new float[1];
resultsCloseEnough = false;
for (CustomAddress addressToCheck : addresses){
Location.distanceBetween(latitude, longitude,
addressToCheck.getLatitude(), addressToCheck.getLongitude(), results);
if (results[0] < OTPApp.GEOCODING_MAX_ERROR) {
resultsCloseEnough = true;
break;
}
}
}
if ((addresses == null) || addresses.isEmpty() || !resultsCloseEnough) {
if (addresses == null){
addresses = new ArrayList<CustomAddress>();
}
addresses.addAll(searchPlaces(context, selectedServer, address));
for (CustomAddress addressRetrieved : addresses) {
String str = addressRetrieved.getAddressLine(0);
List<String> addressLines = Arrays.asList(str.split(", "));
for (int j = 0; j < addressLines.size(); j++) {
addressRetrieved.setAddressLine(j, addressLines.get(j));
}
}
}
addresses = filterAddressesBBox(selectedServer, addresses);
if (geocodingForMarker && latLngSet && addresses != null && !addresses.isEmpty()){
float results[] = new float[1];
float minDistanceToOriginalLatLon = Float.MAX_VALUE;
CustomAddress closestAddress = addresses.get(0);
for (CustomAddress addressToCheck : addresses){
Location.distanceBetween(latitude, longitude,
addressToCheck.getLatitude(), addressToCheck.getLongitude(), results);
if (results[0] < minDistanceToOriginalLatLon){
closestAddress = addressToCheck;
minDistanceToOriginalLatLon = results[0];
}
}
addressesReturn.add(closestAddress);
}
else{
addressesReturn.addAll(addresses);
}
return addressesReturn;
}
/**
* Filters the addresses obtained in geocoding process, removing the
* results outside server limits.
*
* @param addresses list of addresses to filter
* @return a new list filtered
*/
private static List<CustomAddress> filterAddressesBBox(Server selectedServer, List<CustomAddress> addresses) {
if ((!(addresses == null || addresses.isEmpty())) && selectedServer != null) {
for (Iterator<CustomAddress> it=addresses.iterator(); it.hasNext();) {
CustomAddress address = it.next();
if (!LocationUtil.checkPointInBoundingBox(
new LatLng(address.getLatitude(), address.getLongitude()),
selectedServer)) {
it.remove();
}
}
}
return addresses;
}
/**
* Try to grab the developer key from an unversioned resource file, if it exists
*
* @param context
* @param apiKeyResourceId Resource ID of the raw file containing the API key
* @return the developer key from a resource file, or empty string if it doesn't
* exist
*/
private static String getKeyFromResource(Context context, int apiKeyResourceId) {
String strKey = "";
try {
InputStream in = context.getResources().openRawResource(apiKeyResourceId);
BufferedReader r = new BufferedReader(new InputStreamReader(in));
StringBuilder total = new StringBuilder();
while ((strKey = r.readLine()) != null) {
total.append(strKey);
}
strKey = total.toString();
strKey = strKey.trim(); //Remove any whitespace
} catch (Resources.NotFoundException e) {
Log.w(OTPApp.TAG, "Warning - didn't find the google places key file:" + e);
} catch (IOException e) {
Log.w(OTPApp.TAG, "Error reading the developer key file:" + e);
}
return strKey;
}
private static List<CustomAddress> searchPlaces(Context context, Server selectedServer, String name) {
HashMap<String, String> params = new HashMap<String, String>();
Places p;
SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(
context);
String placesService = mPrefs.getString(
OTPApp.PREFERENCE_KEY_GEOCODER_PROVIDER,
context.getResources().getString(R.string.geocoder_nominatim));
if (placesService
.equals(context.getResources().getString(R.string.geocoder_google_places))) {
params.put(GooglePlaces.PARAM_NAME, name);
if (selectedServer != null) {
params.put(GooglePlaces.PARAM_LOCATION,
Double.toString(selectedServer.getGeometricalCenterLatitude()) + ","
+ Double.toString(selectedServer.getGeometricalCenterLongitude())
);
params.put(GooglePlaces.PARAM_RADIUS, Double.toString(selectedServer.getRadius()));
}
p = new GooglePlaces(getKeyFromResource(context, R.raw.googleplaceskey));
Log.d(OTPApp.TAG, "Using Google Places!");
} else {
params.put(Nominatim.PARAM_NAME, name);
if (selectedServer != null) {
params.put(Nominatim.PARAM_LEFT,
Double.toString(selectedServer.getLowerLeftLongitude()));
params.put(Nominatim.PARAM_TOP,
Double.toString(selectedServer.getLowerLeftLatitude()));
params.put(Nominatim.PARAM_RIGHT,
Double.toString(selectedServer.getUpperRightLongitude()));
params.put(Nominatim.PARAM_BOTTOM,
Double.toString(selectedServer.getUpperRightLatitude()));
}
p = new Nominatim(getKeyFromResource(context, R.raw.mapquestgeocoderkey));
Log.d(OTPApp.TAG, "Using Nominatim!");
}
ArrayList<POI> pois = new ArrayList<POI>();
pois.addAll(p.getPlaces(params));
List<CustomAddress> addresses = new ArrayList<CustomAddress>();
for (POI poi : pois) {
Log.d(OTPApp.TAG, poi.getName() + " " + poi.getLatitude() + "," + poi.getLongitude());
CustomAddress address = new CustomAddress(context.getResources().getConfiguration().locale);
address.setLatitude(poi.getLatitude());
address.setLongitude(poi.getLongitude());
String addressLine;
if (poi.getAddress() != null) {
if (!poi.getAddress().contains(poi.getName())) {
addressLine = (poi.getName() + ", " + poi.getAddress());
} else {
addressLine = poi.getAddress();
}
} else {
addressLine = poi.getName();
}
address.setAddressLine(address.getMaxAddressLineIndex() + 1, addressLine);
addresses.add(address);
}
return addresses;
}
}