/* * 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. *
* Only one key in a class may contain a {@code ParseGeoPoint}. * * Example: *
* ParseGeoPoint point = new ParseGeoPoint(30.0, -20.0);
* ParseObject object = new ParseObject("PlaceObject");
* object.put("location", point);
* object.save();
*
*/
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.
*
* Note: 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