Sqlite: select items based on distance (latitude and longitude) - android

I have a table in wich items are stored using the geographic location based on latitude and longitude. At one point I want to retrieve only those items from the database that are in a specific range (distance or radius) of my current position.
Item (latitude, longitude)
So I followed this really nice post and tried to implement the answer with the highest votes. First thing: I had to add some more columns when storing the items. This enables the select-statement later on:
Item (lat, lat_cos, lat_sin, lng, lng_cos, lng_sin)
So far so good, but when I then try to build the query, I go nuts. If I use the less-symbol < then all items are retrieved (even if too far away). But if I use the greater-symbol > no items are retrieved. But I know that there are items in my range.
Question: So what am I doing wrong with that term?
WHERE (coslat * LAT_COS * (LNG_COS * coslng + LNG_SIN * sinlng)) + sinlat * LAT_SIN > cos_allowed_distance
Here's the code that I'm using for querying my ContentProvider. The ContentInfo class has fields for sortOrder, selection and selectionArgs:
public static ContentInfo buildDistanceWhereClause(double latitude, double longitude, double distanceInKilometers) {
final double coslat = Math.cos(Math.toRadians(latitude));
final double sinlat = Math.sin(Math.toRadians(latitude));
final double coslng = Math.cos(Math.toRadians(longitude));
final double sinlng = Math.sin(Math.toRadians(longitude));
// (coslat * LAT_COS * (LNG_COS * coslng + LNG_SIN * sinlng)) + sinlat * LAT_SIN > cos_allowed_distance
final String where = "(? * ? * (? * ? + ? * ?)) + ? * ? > ?";
final String[] whereClause = new String[] {
String.valueOf(coslat),
ItemTable.COLUMN_LATITUDE_COS,
ItemTable.COLUMN_LONGITUDE_COS,
String.valueOf(coslng),
ItemTable.COLUMN_LONGITUDE_SIN,
String.valueOf(sinlng),
String.valueOf(sinlat),
ItemTable.COLUMN_LATITUDE_SIN,
String.valueOf(Math.cos(distanceInKilometers / 6371.0))
};
return new ContentInfo(null, where, whereClause);
}
}

I don't actually see the problem, but I re-engineered the selection/where so it's easier to read. And now it works. You can also checkout this fiddle.
public static String buildDistanceWhereClause(double latitude, double longitude, double distanceInKilometers) {
// see: http://stackoverflow.com/questions/3126830/query-to-get-records-based-on-radius-in-sqlite
final double coslat = Math.cos(Math.toRadians(latitude));
final double sinlat = Math.sin(Math.toRadians(latitude));
final double coslng = Math.cos(Math.toRadians(longitude));
final double sinlng = Math.sin(Math.toRadians(longitude));
final String format = "(%1$s * %2$s * (%3$s * %4$s + %5$s * %6$s) + %7$s * %8$s) > %9$s";
final String selection = String.format(format,
coslat, COLUMN_LATITUDE_COS,
coslng, COLUMN_LONGITUDE_COS,
sinlng, COLUMN_LONGITUDE_SIN,
sinlat, COLUMN_LATITUDE_SIN,
Math.cos(distanceInKilometers / 6371.0)
);
Log.d(TAG, selection);
return selection;
}

Related

I have two latlong values. one for source and other for destination..In which format latlong value should pass to find distance between them

DIRECTION_URL_API = "https://maps.googleapis.com/maps/api/directions/json?"
DIRECTION_URL_API + "origin=" + origin + "&destination=" + destination + "&sensor=true" + "&mode=" +typeOpt+"&key=" + GOOGLE_API_KEY ;
I am using this format but its not working
Please suggest me :)
You can find distance following way
http://maps.googleapis.com/maps/api/directions/json?origin=21.1702,72.8311&destination=21.7051,72.9959&sensor=false&units=metric&mode=driving
origin=lat1,long1
destination=lat2,long2
Please use the below method to calculate the distance between two points
/**
* Returns Distance in kilometers (km)
*/
public static String distance(double startLat, double startLong, double endLat, double endLong) {
Location startPoint = new Location("locationA");
startPoint.setLatitude(startLat);
startPoint.setLongitude(startLong);
Location endPoint = new Location("locationA");
endPoint.setLatitude(endLat);
endPoint.setLongitude(endLong);
return String.format("%.2f", startPoint.distanceTo(endPoint) / 1000); //KMs
}
Method usage -
String mDistance = distance(startLat,
startLong,
endLat,endLng)).concat("km");

Convert degree/minutes/seconds to valid EXIF interface string

If I know the degress, minutes, and seconds of a location, how do I convert them to a valid location for ExifInterface.TAG_GPS_LATITUDE and ExifInterface.TAG_GPS_LONGITUDE?
I found following: https://developer.android.com/reference/android/media/ExifInterface.html#TAG_GPS_LATITUDE
But I'm not sure if I understand the format correctly. There is written following:
String. Format is "num1/denom1,num2/denom2,num3/denom3".
I'm not sure which fractions to use for each values... Always 1? Like in following code example:
String exifLatitude1 = degress+ "/1," + minutes + "/1," + seconds + "/1";
I often see strings with /1000 for the seconds, so I'm not sure if following is correct instead of the example above:
String exifLatitude2 = degress+ "/1," + minutes + "/1," + seconds + "/1000";
Can anyone tell me, which one is correct?
My working solution uses milliseconds/1000
-79.948862 becomes
-79 degrees, 56 minutes, 55903 millisecs (equals 55.903 seconds)
79/1,56/1,55903/1000
I have never checked if 79/1,56/1,56/1 would be ok, too.
I am using this code:
from https://github.com/k3b/APhotoManager/blob/FDroid/app/src/main/java/de/k3b/android/util/ExifGps.java
public static boolean saveLatLon(File filePath, double latitude, double longitude) {
exif = new ExifInterface(filePath.getAbsolutePath());
debugExif(sb, "old", exif, filePath);
exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE, convert(latitude));
exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF, latitudeRef(latitude));
exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, convert(longitude));
exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF, longitudeRef(longitude));
exif.saveAttributes();
}
/**
* convert latitude into DMS (degree minute second) format. For instance<br/>
* -79.948862 becomes<br/>
* 79/1,56/1,55903/1000<br/>
* It works for latitude and longitude<br/>
* #param latitude could be longitude.
* #return
*/
private static final String convert(double latitude) {
latitude=Math.abs(latitude);
int degree = (int) latitude;
latitude *= 60;
latitude -= (degree * 60.0d);
int minute = (int) latitude;
latitude *= 60;
latitude -= (minute * 60.0d);
int second = (int) (latitude*1000.0d);
StringBuilder sb = new StringBuilder(20);
sb.append(degree);
sb.append("/1,");
sb.append(minute);
sb.append("/1,");
sb.append(second);
sb.append("/1000");
return sb.toString();
}
private static StringBuilder createDebugStringBuilder(File filePath) {
return new StringBuilder("Set Exif to file='").append(filePath.getAbsolutePath()).append("'\n\t");
}
private static String latitudeRef(double latitude) {
return latitude<0.0d?"S":"N";
}

How to customize x axis value in horizontal mpandroidchart?

It may be dumb question am using horizontal mpandroid barchart am having numbers in x axis like 6,000,000 so x axis getting collapsed so what is need to do is convert the number into indian rupees like 6 cr how can i do this so far what i have tried is:
This is where am setting value for horizontal barchart:
ArrayList<BarEntry> entries = new ArrayList<>();
ArrayList<String> labels = new ArrayList<String>();
Format format = NumberFormat.getCurrencyInstance(new Locale("en", "in"));
// System.out.println(format.format(new BigDecimal("100000000")));
for(int i=0;i<listobj.size();i++){
String s= format.format(listobj.get(i).getData());
// Log.e("ss", s);
// Integer numberformat=Integer.parseInt(s);
entries.add(new BarEntry(listobj.get(i).getData(),i));
labels.add(listobj.get(i).getLabel());
}
XAxis axis=barChart.getXAxis();
BarDataSet dataset = new BarDataSet(entries, "# of Calls");
//dataset.setValueFormatter(new LargeValueFormatter());
BarData datas = new BarData(labels, dataset);
/// datas.setValueFormatter(new LargeValueFormatter());
barChart.setData(datas);
barChart.setDescription("Description");
barChart.getLegend().setWordWrapEnabled(true);
This is value Formatter:
/**
* Created by 4264 on 30-05-2016.
*/
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.formatter.ValueFormatter;
import com.github.mikephil.charting.utils.ViewPortHandler;
import java.text.DecimalFormat;
/**
* Predefined value-formatter that formats large numbers in a pretty way.
* Outputs: 856 = 856; 1000 = 1k; 5821 = 5.8k; 10500 = 10k; 101800 = 102k;
* 2000000 = 2m; 7800000 = 7.8m; 92150000 = 92m; 123200000 = 123m; 9999999 =
* 10m; 1000000000 = 1b; Special thanks to Roman Gromov
* (https://github.com/romangromov) for this piece of code.
*
* #author Philipp Jahoda
* #author Oleksandr Tyshkovets <olexandr.tyshkovets#gmail.com>
*/
class LargeValueFormatter implements ValueFormatter {
private static String[] SUFFIX = new String[]{
"", "k", "m", "b", "t"
};
private static final int MAX_LENGTH = 4;
private DecimalFormat mFormat;
private String mText = "";
public LargeValueFormatter() {
mFormat = new DecimalFormat("###E0");
}
/**
* Creates a formatter that appends a specified text to the result string
*
* #param appendix a text that will be appended
*/
public LargeValueFormatter(String appendix) {
this();
mText = appendix;
}
// ValueFormatter
#Override
public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) {
return makePretty(value) + mText;
}
// YAxisValueFormatter
/**
* Set an appendix text to be added at the end of the formatted value.
*
* #param appendix
*/
public void setAppendix(String appendix) {
this.mText = appendix;
}
/**
* Set custom suffix to be appended after the values.
* Default suffix: ["", "k", "m", "b", "t"]
*
* #param suff new suffix
*/
public void setSuffix(String[] suff) {
if (suff.length == 5) {
SUFFIX = suff;
}
}
/**
* Formats each number properly. Special thanks to Roman Gromov
* (https://github.com/romangromov) for this piece of code.
*/
private String makePretty(double number) {
String r = mFormat.format(number);
r = r.replaceAll("E[0-9]", SUFFIX[Character.getNumericValue(r.charAt(r.length() - 1)) / 3]);
while (r.length() > MAX_LENGTH || r.matches("[0-9]+\\.[a-z]")) {
r = r.substring(0, r.length() - 2) + r.substring(r.length() - 1);
}
return r;
}
}
I don't know how to customize the x axis value how can i do this can anyone help me out thanks in advance!!
You have 2 options that you can try to solve your issue
1) While inserting the value you can divide the value by 1000,000 and change your label to 6Cr . I have a chart that has values from 0 to 30 and one value is 4000. While storing the value in database I divide it by 100 and while displaying I change the label to show the correct value
Sample SQL insert value :
Float.valueOf(LEUCO_COUNT.getText().toString()) / 1000,
2) Option 2 is to divide the value by 1000,000 before displaying it and also modify your babel to show Cr. You can go throught he API to find how to set a custom label
Hope this helps.
You need to replace existing function with below function.It working for me.
private String makePretty(double number) {
double Cr = 10000000;
double Lac = 100000;
String result = mFormat.format(number);
try {
double LAmt = Double.parseDouble(result);
String Append = "";
if (LAmt > Cr) {//1Crore
LAmt /= Cr;
Append = "Cr";
} else {
LAmt /= Lac;
Append = "L";
}
result = CommonUtils.GetFormattedString("" + LAmt) + Append;
} catch (NumberFormatException e) {
Log.d("", "");
}
return result;
}

Android Converting String Variable of Coordinates to Point

If I am retrieving a string variable of something like this (34872.1297,41551.7292), so it would be "(34872.1297,41551.7292)", how do I convert this string variable to Point(Geolocation) ?
For example, this sets the point value, but I want the values to be retrieved
Point point = new Point(34872.1297,41551.7292);
What you are looking for is how to split a string, and there are a few excellent examples on SO for you to peruse.
In your case, this will work:
String yourString = "(34872.1297,41551.7292)";
// Strip out parentheses and split the string on ","
String[] items = yourString.replaceAll("[()]", "").split("\\s*,\\s*"));
// Now you have a String[] (items) with the values "34872.1297" and "41551.7292"
// Get the x and y values as Floats
Float x = Float.parseFloat(items[0]);
Float y = Float.parseFloat(items[1]);
// Do with them what you like (I think you mean LatLng instead of Point)
LatLng latLng = new LatLng(x, y);
Add checks for null values and parse exceptions etc.
An issue with storing the geo coordinates as a Point object is that Point actually requires the two values to be of integer type. So you would lose information.
So you could extract and type cast the coordinates to be integers (but lose information):
String geo = "(34872.1297,41551.7292)";
// REMOVE BRACKETS, AND WHITE SPACES
geo = geo.replace(")", "");
geo = geo.replace("(", "");
geo = geo.replace(" ", "");
// SEPARATE THE LONGITUDE AND LATITUDE
String[] split = geo.split(",");
// ASSIGN LONGITUDE AND LATITUDE TO POINT AS INTEGERS
Point point = new Point((int) split[0], (int) split[1]);
Alternatively, you could extract them as floats, and store them in some other data type off your choice.
String geo = "(34872.1297,41551.7292)";
// REMOVE BRACKETS, AND WHITE SPACES
geo = geo.replace(")", "");
geo = geo.replace("(", "");
geo = geo.replace(" ", "");
// SEPARATE THE LONGITUDE AND LATITUDE
String[] split = geo.split(",");
// ASSIGN LONGITUDE AND LATITUDE TO POINT AS INTEGERS
Float long = (float) split[0];
Float lat = (float) split[1];
EDITED: changed geo.split(":") to geo.split(",") in the code () (Thanks Jitesh)

How to save GPS coordinates in exif data on Android?

I am writing GPS coordinates to my JPEG image, and the coordinates are correct (as demonstrated by my logcat output) but it appears that it's being corrupted somehow. Reading the exif data results in either null values or, in the case of my GPS: 512.976698 degrees, 512.976698 degrees. Can anyone shed some light on this problem?
writing it:
try {
ExifInterface exif = new ExifInterface(filename);
exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE, latitude);
exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, longitude);
exif.saveAttributes();
Log.e("LATITUDE: ", latitude);
Log.e("LONGITUDE: ", longitude);
} catch (IOException e) {
e.printStackTrace();
}
and reading it:
try {
ExifInterface exif = new ExifInterface("/sdcard/globetrotter/mytags/"+ TAGS[position]);
Log.e("LATITUDE EXTRACTED", exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE));
Log.e("LONGITUDE EXTRACTED", exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE));
} catch (IOException e) {
e.printStackTrace();
}
It goes in (for example) 37.715183, -117.260489 and comes out 33619970/65540, 14811136/3368550, 33619970/65540, 14811136/3368550. Am I doing it wrong?
EDIT:
So, the problem is I am not encoding it in the properly defined format, which is something like you see here:
Can anyone explain what this format is? Obviously the first number is 22/1 = 22 degrees, but I can't figure out how to compute the decimal there.
GPSLatitude
Indicates the latitude. The latitude is expressed as three
RATIONAL values giving the degrees,
minutes, and seconds, respectively.
If latitude is expressed as degrees,
minutes and seconds, a typical format
would be dd/1,mm/1,ss/1. When degrees
and minutes are used and, for
example, fractions of minutes are
given up to two decimal places, the
format would be dd/1,mmmm/100,0/1.
https://docs.google.com/viewer?url=http%3A%2F%2Fwww.exif.org%2FExif2-2.PDF
The Android docs specify this without explanation: http://developer.android.com/reference/android/media/ExifInterface.html#TAG_GPS_LATITUDE
Exif data is standardized, and GPS data must be encoded using geographical coordinates (minutes, seconds, etc) described above instead of a fraction. Unless it's encoded in that format in the exif tag, it won't stick.
How to encode: http://en.wikipedia.org/wiki/Geographic_coordinate_conversion
How to decode: http://android-er.blogspot.com/2010/01/convert-exif-gps-info-to-degree-format.html
Here is some code I've done to geotag my pictures. It's not heavily tested yet, but it seems to be ok (JOSM editor and exiftool read location).
ExifInterface exif = new ExifInterface(filePath.getAbsolutePath());
exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE, GPS.convert(latitude));
exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF, GPS.latitudeRef(latitude));
exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, GPS.convert(longitude));
exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF, GPS.longitudeRef(longitude));
exif.saveAttributes();
And class GPS is here. (method could be shorter, but it's readable at least)
/*
* #author fabien
*/
public class GPS {
private static StringBuilder sb = new StringBuilder(20);
/**
* returns ref for latitude which is S or N.
* #param latitude
* #return S or N
*/
public static String latitudeRef(double latitude) {
return latitude<0.0d?"S":"N";
}
/**
* returns ref for latitude which is S or N.
* #param latitude
* #return S or N
*/
public static String longitudeRef(double longitude) {
return longitude<0.0d?"W":"E";
}
/**
* convert latitude into DMS (degree minute second) format. For instance<br/>
* -79.948862 becomes<br/>
* 79/1,56/1,55903/1000<br/>
* It works for latitude and longitude<br/>
* #param latitude could be longitude.
* #return
*/
synchronized public static final String convert(double latitude) {
latitude=Math.abs(latitude);
int degree = (int) latitude;
latitude *= 60;
latitude -= (degree * 60.0d);
int minute = (int) latitude;
latitude *= 60;
latitude -= (minute * 60.0d);
int second = (int) (latitude*1000.0d);
sb.setLength(0);
sb.append(degree);
sb.append("/1,");
sb.append(minute);
sb.append("/1,");
sb.append(second);
sb.append("/1000");
return sb.toString();
}
}
Other answers delivered nice background info and even an example. This is not a direct answer to the question but I would like to add an even simpler example without the need to do any math. The Location class delivers a nice convert function:
public String getLonGeoCoordinates(Location location) {
if (location == null) return "0/1,0/1,0/1000";
// You can adapt this to latitude very easily by passing location.getLatitude()
String[] degMinSec = Location.convert(location.getLongitude(), Location.FORMAT_SECONDS).split(":");
return degMinSec[0] + "/1," + degMinSec[1] + "/1," + degMinSec[2] + "/1000";
}
I stored the return value in my image and the tag is parsed fine. You can check your image and the geocoordinates inside here: http://regex.info/exif.cgi
Edit
#ratanas comment translated to code:
public boolean storeGeoCoordsToImage(File imagePath, Location location) {
// Avoid NullPointer
if (imagePath == null || location == null) return false;
// If we use Location.convert(), we do not have to worry about absolute values.
try {
// c&p and adapted from #Fabyen (sorry for being lazy)
ExifInterface exif = new ExifInterface(imagePath.getAbsolutePath());
exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE, getLatGeoCoordinates(location));
exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF, location.getLatitude() < 0 ? "S" : "N");
exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, getLonGeoCoordinates(location));
exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF, location.getLongitude() < 0 ? "W" : "E");
exif.saveAttributes();
} catch (IOException e) {
// do something
return false;
}
// Data was likely written. For sure no NullPointer.
return true;
}
Here are some nice LatLong converter: latlong.net
ExifInterface exif = new ExifInterface(compressedImage.getPath());
exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE,gpsTracker.dec2DMS(gpsTracker.getLatitude()));
exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE,gpsTracker.dec2DMS(gpsTracker.getLongitude()));
Convertor double to String
String dec2DMS(double coord) {
coord = coord > 0 ? coord : -coord;
String sOut = Integer.toString((int)coord) + "/1,";
coord = (coord % 1) * 60;
sOut = sOut + Integer.toString((int)coord) + "/1,";
coord = (coord % 1) * 60000;
sOut = sOut + Integer.toString((int)coord) + "/1000";
return sOut;
}
The most modern and shortest solution (with AndroidX) is using ExifInterface.setGpsInfo(Location), for example:
ExifInterface exif = new ExifInterface(filename);
Location location = new Location(""); //may be empty
location.setLatitude(latitude); //double value
location.setLongitude(longitude); //double value
exif.setGpsInfo(location)
exif.saveAttributes();
Sources: one and two
check android source code: https://android.googlesource.com/platform/frameworks/base/+/android-4.4.2_r2/core/java/android/hardware/Camera.java
/**
* Sets GPS longitude coordinate. This will be stored in JPEG EXIF
* header.
*
* #param longitude GPS longitude coordinate.
*/
public void setGpsLongitude(double longitude) {
set(KEY_GPS_LONGITUDE, Double.toString(longitude));
}
So it's a direct print, my log supports it as well: ExifInterface.TAG_GPS_LONGITUDE : -121.0553966
My conclusion is setting it as direct print is fine.

Categories

Resources