ParseApplication/ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseGeoPoint.java

337 lines
12 KiB
Java

/*
* Copyright (c) 2015-present, Parse, LLC.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.parse;
import android.location.Criteria;
import android.location.Location;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Locale;
import bolts.Continuation;
import bolts.Task;
/**
* {@code ParseGeoPoint} represents a latitude / longitude point that may be associated with a key
* in a {@link ParseObject} or used as a reference point for geo queries. This allows proximity
* based queries on the key.
* <p/>
* Only one key in a class may contain a {@code ParseGeoPoint}.
* <p/>
* Example:
* <pre>
* ParseGeoPoint point = new ParseGeoPoint(30.0, -20.0);
* ParseObject object = new ParseObject("PlaceObject");
* object.put("location", point);
* object.save();
* </pre>
*/
public class ParseGeoPoint implements Parcelable {
static double EARTH_MEAN_RADIUS_KM = 6371.0;
static double EARTH_MEAN_RADIUS_MILE = 3958.8;
private double latitude = 0.0;
private double longitude = 0.0;
/**
* Creates a new default point with latitude and longitude set to 0.0.
*/
public ParseGeoPoint() {
}
/**
* Creates a new point with the specified latitude and longitude.
*
* @param latitude
* The point's latitude.
* @param longitude
* The point's longitude.
*/
public ParseGeoPoint(double latitude, double longitude) {
setLatitude(latitude);
setLongitude(longitude);
}
/**
* Creates a copy of {@code point};
*
* @param point
* The point to copy.
*/
public ParseGeoPoint(ParseGeoPoint point) {
this(point.getLatitude(), point.getLongitude());
}
/**
* Creates a new point instance from a {@link Parcel} source. This is used when unparceling a
* ParseGeoPoint. Subclasses that need Parcelable behavior should provide their own
* {@link android.os.Parcelable.Creator} and override this constructor.
*
* @param source The recovered parcel.
*/
protected ParseGeoPoint(Parcel source) {
this(source, ParseParcelDecoder.get());
}
/**
* Creates a new point instance from a {@link Parcel} using the given {@link ParseParcelDecoder}.
* The decoder is currently unused, but it might be in the future, plus this is the pattern we
* are using in parcelable classes.
*
* @param source the parcel
* @param decoder the decoder
*/
ParseGeoPoint(Parcel source, ParseParcelDecoder decoder) {
setLatitude(source.readDouble());
setLongitude(source.readDouble());
}
/**
* Set latitude. Valid range is (-90.0, 90.0). Extremes should not be used.
*
* @param latitude
* The point's latitude.
*/
public void setLatitude(double latitude) {
if (latitude > 90.0 || latitude < -90.0) {
throw new IllegalArgumentException("Latitude must be within the range (-90.0, 90.0).");
}
this.latitude = latitude;
}
/**
* Get latitude.
*/
public double getLatitude() {
return latitude;
}
/**
* Set longitude. Valid range is (-180.0, 180.0). Extremes should not be used.
*
* @param longitude
* The point's longitude.
*/
public void setLongitude(double longitude) {
if (longitude > 180.0 || longitude < -180.0) {
throw new IllegalArgumentException("Longitude must be within the range (-180.0, 180.0).");
}
this.longitude = longitude;
}
/**
* Get longitude.
*/
public double getLongitude() {
return longitude;
}
/**
* Get distance in radians between this point and another {@code ParseGeoPoint}. This is the
* smallest angular distance between the two points.
*
* @param point
* {@code ParseGeoPoint} describing the other point being measured against.
*/
public double distanceInRadiansTo(ParseGeoPoint point) {
double d2r = Math.PI / 180.0; // radian conversion factor
double lat1rad = latitude * d2r;
double long1rad = longitude * d2r;
double lat2rad = point.getLatitude() * d2r;
double long2rad = point.getLongitude() * d2r;
double deltaLat = lat1rad - lat2rad;
double deltaLong = long1rad - long2rad;
double sinDeltaLatDiv2 = Math.sin(deltaLat / 2.);
double sinDeltaLongDiv2 = Math.sin(deltaLong / 2.);
// Square of half the straight line chord distance between both points.
// [0.0, 1.0]
double a =
sinDeltaLatDiv2 * sinDeltaLatDiv2 + Math.cos(lat1rad) * Math.cos(lat2rad)
* sinDeltaLongDiv2 * sinDeltaLongDiv2;
a = Math.min(1.0, a);
return 2. * Math.asin(Math.sqrt(a));
}
/**
* Get distance between this point and another {@code ParseGeoPoint} in kilometers.
*
* @param point
* {@code ParseGeoPoint} describing the other point being measured against.
*/
public double distanceInKilometersTo(ParseGeoPoint point) {
return distanceInRadiansTo(point) * EARTH_MEAN_RADIUS_KM;
}
/**
* Get distance between this point and another {@code ParseGeoPoint} in kilometers.
*
* @param point
* {@code ParseGeoPoint} describing the other point being measured against.
*/
public double distanceInMilesTo(ParseGeoPoint point) {
return distanceInRadiansTo(point) * EARTH_MEAN_RADIUS_MILE;
}
/**
* Asynchronously fetches the current location of the device.
*
* This will use a default {@link Criteria} with no accuracy or power requirements, which will
* generally result in slower, but more accurate location fixes.
* <p/>
* <strong>Note:</strong> If GPS is the best provider, it might not be able to locate the device
* at all and timeout.
*
* @param timeout
* The number of milliseconds to allow before timing out.
* @return A Task that is resolved when a location is found.
*
* @see android.location.LocationManager#getBestProvider(android.location.Criteria, boolean)
* @see android.location.LocationManager#requestLocationUpdates(String, long, float, android.location.LocationListener)
*/
public static Task<ParseGeoPoint> getCurrentLocationInBackground(long timeout) {
Criteria criteria = new Criteria();
criteria.setAccuracy(Criteria.NO_REQUIREMENT);
criteria.setPowerRequirement(Criteria.NO_REQUIREMENT);
return LocationNotifier.getCurrentLocationAsync(Parse.getApplicationContext(), timeout, criteria)
.onSuccess(new Continuation<Location, ParseGeoPoint>() {
@Override
public ParseGeoPoint then(Task<Location> task) throws Exception {
Location location = task.getResult();
return new ParseGeoPoint(location.getLatitude(), location.getLongitude());
}
});
}
/**
* Asynchronously fetches the current location of the device.
*
* This will use a default {@link Criteria} with no accuracy or power requirements, which will
* generally result in slower, but more accurate location fixes.
* <p/>
* <strong>Note:</strong> If GPS is the best provider, it might not be able to locate the device
* at all and timeout.
*
* @param timeout
* The number of milliseconds to allow before timing out.
* @param callback
* callback.done(geoPoint, error) is called when a location is found.
*
* @see android.location.LocationManager#getBestProvider(android.location.Criteria, boolean)
* @see android.location.LocationManager#requestLocationUpdates(String, long, float, android.location.LocationListener)
*/
public static void getCurrentLocationInBackground(long timeout, LocationCallback callback) {
ParseTaskUtils.callbackOnMainThreadAsync(getCurrentLocationInBackground(timeout), callback);
}
/**
* Asynchronously fetches the current location of the device.
*
* This will request location updates from the best provider that match the given criteria
* and return the first location received.
*
* You can customize the criteria to meet your specific needs.
* * For higher accuracy, you can set {@link Criteria#setAccuracy(int)}, however result in longer
* times for a fix.
* * For better battery efficiency and faster location fixes, you can set
* {@link Criteria#setPowerRequirement(int)}, however, this will result in lower accuracy.
*
* @param timeout
* The number of milliseconds to allow before timing out.
* @param criteria
* The application criteria for selecting a location provider.
* @return A Task that is resolved when a location is found.
*
* @see android.location.LocationManager#getBestProvider(android.location.Criteria, boolean)
* @see android.location.LocationManager#requestLocationUpdates(String, long, float, android.location.LocationListener)
*/
public static Task<ParseGeoPoint> getCurrentLocationInBackground(long timeout, Criteria criteria) {
return LocationNotifier.getCurrentLocationAsync(Parse.getApplicationContext(), timeout, criteria)
.onSuccess(new Continuation<Location, ParseGeoPoint>() {
@Override
public ParseGeoPoint then(Task<Location> task) throws Exception {
Location location = task.getResult();
return new ParseGeoPoint(location.getLatitude(), location.getLongitude());
}
});
}
/**
* Asynchronously fetches the current location of the device.
*
* This will request location updates from the best provider that match the given criteria
* and return the first location received.
*
* You can customize the criteria to meet your specific needs.
* * For higher accuracy, you can set {@link Criteria#setAccuracy(int)}, however result in longer
* times for a fix.
* * For better battery efficiency and faster location fixes, you can set
* {@link Criteria#setPowerRequirement(int)}, however, this will result in lower accuracy.
*
* @param timeout
* The number of milliseconds to allow before timing out.
* @param criteria
* The application criteria for selecting a location provider.
* @param callback
* callback.done(geoPoint, error) is called when a location is found.
*
* @see android.location.LocationManager#getBestProvider(android.location.Criteria, boolean)
* @see android.location.LocationManager#requestLocationUpdates(String, long, float, android.location.LocationListener)
*/
public static void getCurrentLocationInBackground(long timeout, Criteria criteria,
LocationCallback callback) {
ParseTaskUtils.callbackOnMainThreadAsync(getCurrentLocationInBackground(timeout, criteria), callback);
}
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof ParseGeoPoint)) {
return false;
}
if (obj == this) {
return true;
}
return ((ParseGeoPoint) obj).getLatitude() == latitude &&
((ParseGeoPoint) obj).getLongitude() == longitude;
}
@Override
public String toString() {
return String.format(Locale.US, "ParseGeoPoint[%.6f,%.6f]", latitude, longitude);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
writeToParcel(dest, ParseParcelEncoder.get());
}
void writeToParcel(Parcel dest, ParseParcelEncoder encoder) {
dest.writeDouble(latitude);
dest.writeDouble(longitude);
}
public final static Creator<ParseGeoPoint> CREATOR = new Creator<ParseGeoPoint>() {
@Override
public ParseGeoPoint createFromParcel(Parcel source) {
return new ParseGeoPoint(source, ParseParcelDecoder.get());
}
@Override
public ParseGeoPoint[] newArray(int size) {
return new ParseGeoPoint[size];
}
};
}