Signed-off-by: hmz007 <hmz007@gmail.com> Change-Id: Ib87fdbe7a0be7dc961af3500fe9ea4589a127f9f
292 lines
9.7 KiB
Java
292 lines
9.7 KiB
Java
/*
|
|
* Copyright 2005 Google Inc.
|
|
*
|
|
* 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 com.google.common.geometry;
|
|
|
|
/**
|
|
* This class represents a point on the unit sphere as a pair of
|
|
* latitude-longitude coordinates. Like the rest of the "geometry" package, the
|
|
* intent is to represent spherical geometry as a mathematical abstraction, so
|
|
* functions that are specifically related to the Earth's geometry (e.g.
|
|
* easting/northing conversions) should be put elsewhere.
|
|
*
|
|
*/
|
|
public strictfp class S2LatLng {
|
|
|
|
/**
|
|
* Approximate "effective" radius of the Earth in meters.
|
|
*/
|
|
public static final double EARTH_RADIUS_METERS = 6367000.0;
|
|
|
|
/** The center point the lat/lng coordinate system. */
|
|
public static final S2LatLng CENTER = new S2LatLng(0.0, 0.0);
|
|
|
|
private final double latRadians;
|
|
private final double lngRadians;
|
|
|
|
public static S2LatLng fromRadians(double latRadians, double lngRadians) {
|
|
return new S2LatLng(latRadians, lngRadians);
|
|
}
|
|
|
|
public static S2LatLng fromDegrees(double latDegrees, double lngDegrees) {
|
|
return new S2LatLng(S1Angle.degrees(latDegrees), S1Angle.degrees(lngDegrees));
|
|
}
|
|
|
|
public static S2LatLng fromE5(long latE5, long lngE5) {
|
|
return new S2LatLng(S1Angle.e5(latE5), S1Angle.e5(lngE5));
|
|
}
|
|
|
|
public static S2LatLng fromE6(long latE6, long lngE6) {
|
|
return new S2LatLng(S1Angle.e6(latE6), S1Angle.e6(lngE6));
|
|
}
|
|
|
|
public static S2LatLng fromE7(long latE7, long lngE7) {
|
|
return new S2LatLng(S1Angle.e7(latE7), S1Angle.e7(lngE7));
|
|
}
|
|
|
|
public static S1Angle latitude(S2Point p) {
|
|
// We use atan2 rather than asin because the input vector is not necessarily
|
|
// unit length, and atan2 is much more accurate than asin near the poles.
|
|
return S1Angle.radians(
|
|
Math.atan2(p.get(2), Math.sqrt(p.get(0) * p.get(0) + p.get(1) * p.get(1))));
|
|
}
|
|
|
|
public static S1Angle longitude(S2Point p) {
|
|
// Note that atan2(0, 0) is defined to be zero.
|
|
return S1Angle.radians(Math.atan2(p.get(1), p.get(0)));
|
|
}
|
|
|
|
/** This is internal to avoid ambiguity about which units are expected. */
|
|
private S2LatLng(double latRadians, double lngRadians) {
|
|
this.latRadians = latRadians;
|
|
this.lngRadians = lngRadians;
|
|
}
|
|
|
|
/**
|
|
* Basic constructor. The latitude and longitude must be within the ranges
|
|
* allowed by is_valid() below.
|
|
*
|
|
* TODO(dbeaumont): Make this a static factory method (fromLatLng() ?).
|
|
*/
|
|
public S2LatLng(S1Angle lat, S1Angle lng) {
|
|
this(lat.radians(), lng.radians());
|
|
}
|
|
|
|
/**
|
|
* Default constructor for convenience when declaring arrays, etc.
|
|
*
|
|
* TODO(dbeaumont): Remove the default constructor (just use CENTER).
|
|
*/
|
|
public S2LatLng() {
|
|
this(0, 0);
|
|
}
|
|
|
|
/**
|
|
* Convert a point (not necessarily normalized) to an S2LatLng.
|
|
*
|
|
* TODO(dbeaumont): Make this a static factory method (fromPoint() ?).
|
|
*/
|
|
public S2LatLng(S2Point p) {
|
|
this(Math.atan2(p.z, Math.sqrt(p.x * p.x + p.y * p.y)), Math.atan2(p.y, p.x));
|
|
// The latitude and longitude are already normalized. We use atan2 to
|
|
// compute the latitude because the input vector is not necessarily unit
|
|
// length, and atan2 is much more accurate than asin near the poles.
|
|
// Note that atan2(0, 0) is defined to be zero.
|
|
}
|
|
|
|
/** Returns the latitude of this point as a new S1Angle. */
|
|
public S1Angle lat() {
|
|
return S1Angle.radians(latRadians);
|
|
}
|
|
|
|
/** Returns the latitude of this point as radians. */
|
|
public double latRadians() {
|
|
return latRadians;
|
|
}
|
|
|
|
/** Returns the latitude of this point as degrees. */
|
|
public double latDegrees() {
|
|
return 180.0 / Math.PI * latRadians;
|
|
}
|
|
|
|
/** Returns the longitude of this point as a new S1Angle. */
|
|
public S1Angle lng() {
|
|
return S1Angle.radians(lngRadians);
|
|
}
|
|
|
|
/** Returns the longitude of this point as radians. */
|
|
public double lngRadians() {
|
|
return lngRadians;
|
|
}
|
|
|
|
/** Returns the longitude of this point as degrees. */
|
|
public double lngDegrees() {
|
|
return 180.0 / Math.PI * lngRadians;
|
|
}
|
|
|
|
/**
|
|
* Return true if the latitude is between -90 and 90 degrees inclusive and the
|
|
* longitude is between -180 and 180 degrees inclusive.
|
|
*/
|
|
public boolean isValid() {
|
|
return Math.abs(lat().radians()) <= S2.M_PI_2 && Math.abs(lng().radians()) <= S2.M_PI;
|
|
}
|
|
|
|
/**
|
|
* Returns a new S2LatLng based on this instance for which {@link #isValid()}
|
|
* will be {@code true}.
|
|
* <ul>
|
|
* <li>Latitude is clipped to the range {@code [-90, 90]}
|
|
* <li>Longitude is normalized to be in the range {@code [-180, 180]}
|
|
* </ul>
|
|
* <p>If the current point is valid then the returned point will have the same
|
|
* coordinates.
|
|
*/
|
|
public S2LatLng normalized() {
|
|
// drem(x, 2 * S2.M_PI) reduces its argument to the range
|
|
// [-S2.M_PI, S2.M_PI] inclusive, which is what we want here.
|
|
return new S2LatLng(Math.max(-S2.M_PI_2, Math.min(S2.M_PI_2, lat().radians())),
|
|
Math.IEEEremainder(lng().radians(), 2 * S2.M_PI));
|
|
}
|
|
|
|
// Clamps the latitude to the range [-90, 90] degrees, and adds or subtracts
|
|
// a multiple of 360 degrees to the longitude if necessary to reduce it to
|
|
// the range [-180, 180].
|
|
|
|
/** Convert an S2LatLng to the equivalent unit-length vector (S2Point). */
|
|
public S2Point toPoint() {
|
|
double phi = lat().radians();
|
|
double theta = lng().radians();
|
|
double cosphi = Math.cos(phi);
|
|
return new S2Point(Math.cos(theta) * cosphi, Math.sin(theta) * cosphi, Math.sin(phi));
|
|
}
|
|
|
|
/**
|
|
* Return the distance (measured along the surface of the sphere) to the given
|
|
* point.
|
|
*/
|
|
public S1Angle getDistance(final S2LatLng o) {
|
|
// This implements the Haversine formula, which is numerically stable for
|
|
// small distances but only gets about 8 digits of precision for very large
|
|
// distances (e.g. antipodal points). Note that 8 digits is still accurate
|
|
// to within about 10cm for a sphere the size of the Earth.
|
|
//
|
|
// This could be fixed with another sin() and cos() below, but at that point
|
|
// you might as well just convert both arguments to S2Points and compute the
|
|
// distance that way (which gives about 15 digits of accuracy for all
|
|
// distances).
|
|
|
|
double lat1 = lat().radians();
|
|
double lat2 = o.lat().radians();
|
|
double lng1 = lng().radians();
|
|
double lng2 = o.lng().radians();
|
|
double dlat = Math.sin(0.5 * (lat2 - lat1));
|
|
double dlng = Math.sin(0.5 * (lng2 - lng1));
|
|
double x = dlat * dlat + dlng * dlng * Math.cos(lat1) * Math.cos(lat2);
|
|
return S1Angle.radians(2 * Math.atan2(Math.sqrt(x), Math.sqrt(Math.max(0.0, 1.0 - x))));
|
|
// Return the distance (measured along the surface of the sphere) to the
|
|
// given S2LatLng. This is mathematically equivalent to:
|
|
//
|
|
// S1Angle::FromRadians(ToPoint().Angle(o.ToPoint())
|
|
//
|
|
// but this implementation is slightly more efficient.
|
|
}
|
|
|
|
/**
|
|
* Returns the surface distance to the given point assuming a constant radius.
|
|
*/
|
|
public double getDistance(final S2LatLng o, double radius) {
|
|
// TODO(dbeaumont): Maybe check that radius >= 0 ?
|
|
return getDistance(o).radians() * radius;
|
|
}
|
|
|
|
/**
|
|
* Returns the surface distance to the given point assuming the default Earth
|
|
* radius of {@link #EARTH_RADIUS_METERS}.
|
|
*/
|
|
public double getEarthDistance(final S2LatLng o) {
|
|
return getDistance(o, EARTH_RADIUS_METERS);
|
|
}
|
|
|
|
/**
|
|
* Adds the given point to this point.
|
|
* Note that there is no guarantee that the new point will be <em>valid</em>.
|
|
*/
|
|
public S2LatLng add(final S2LatLng o) {
|
|
return new S2LatLng(latRadians + o.latRadians, lngRadians + o.lngRadians);
|
|
}
|
|
|
|
/**
|
|
* Subtracts the given point from this point.
|
|
* Note that there is no guarantee that the new point will be <em>valid</em>.
|
|
*/
|
|
public S2LatLng sub(final S2LatLng o) {
|
|
return new S2LatLng(latRadians - o.latRadians, lngRadians - o.lngRadians);
|
|
}
|
|
|
|
/**
|
|
* Scales this point by the given scaling factor.
|
|
* Note that there is no guarantee that the new point will be <em>valid</em>.
|
|
*/
|
|
public S2LatLng mul(final double m) {
|
|
// TODO(dbeaumont): Maybe check that m >= 0 ?
|
|
return new S2LatLng(latRadians * m, lngRadians * m);
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object that) {
|
|
if (that instanceof S2LatLng) {
|
|
S2LatLng o = (S2LatLng) that;
|
|
return (latRadians == o.latRadians) && (lngRadians == o.lngRadians);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
long value = 17;
|
|
value += 37 * value + Double.doubleToLongBits(latRadians);
|
|
value += 37 * value + Double.doubleToLongBits(lngRadians);
|
|
return (int) (value ^ (value >>> 32));
|
|
}
|
|
|
|
/**
|
|
* Returns true if both the latitude and longitude of the given point are
|
|
* within {@code maxError} radians of this point.
|
|
*/
|
|
public boolean approxEquals(S2LatLng o, double maxError) {
|
|
return (Math.abs(latRadians - o.latRadians) < maxError)
|
|
&& (Math.abs(lngRadians - o.lngRadians) < maxError);
|
|
}
|
|
|
|
/**
|
|
* Returns true if the given point is within {@code 1e-9} radians of this
|
|
* point. This corresponds to a distance of less than {@code 1cm} at the
|
|
* surface of the Earth.
|
|
*/
|
|
public boolean approxEquals(S2LatLng o) {
|
|
return approxEquals(o, 1e-9);
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "(" + latRadians + ", " + lngRadians + ")";
|
|
}
|
|
|
|
public String toStringDegrees() {
|
|
return "(" + latDegrees() + ", " + lngDegrees() + ")";
|
|
}
|
|
}
|