blob: e058fe7bb6449e24738b0ee61fbb551206d49376 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.lucene.spatial3d.geom;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
/**
* This class represents a point on the surface of a sphere or ellipsoid.
*
* @lucene.experimental
*/
public class GeoPoint extends Vector implements SerializableObject {
// By making lazily-evaluated variables be "volatile", we guarantee atomicity when they
// are updated. This is necessary if we are using these classes in a multi-thread fashion,
// because we don't try to synchronize for the lazy computation.
/** This is the lazily-evaluated magnitude. Some constructors include it, but others don't, and
* we try not to create extra computation by always computing it. Does not need to be
* synchronized for thread safety, because depends wholly on immutable variables of this class. */
protected volatile double magnitude = Double.NEGATIVE_INFINITY;
/** Lazily-evaluated latitude. Does not need to be
* synchronized for thread safety, because depends wholly on immutable variables of this class. */
protected volatile double latitude = Double.NEGATIVE_INFINITY;
/** Lazily-evaluated longitude. Does not need to be
* synchronized for thread safety, because depends wholly on immutable variables of this class. */
protected volatile double longitude = Double.NEGATIVE_INFINITY;
/** Construct a GeoPoint from the trig functions of a lat and lon pair.
* @param planetModel is the planetModel to put the point on.
* @param sinLat is the sin of the latitude.
* @param sinLon is the sin of the longitude.
* @param cosLat is the cos of the latitude.
* @param cosLon is the cos of the longitude.
* @param lat is the latitude.
* @param lon is the longitude.
*/
public GeoPoint(final PlanetModel planetModel, final double sinLat, final double sinLon, final double cosLat, final double cosLon, final double lat, final double lon) {
this(computeDesiredEllipsoidMagnitude(planetModel, cosLat * cosLon, cosLat * sinLon, sinLat),
cosLat * cosLon, cosLat * sinLon, sinLat, lat, lon);
}
/** Construct a GeoPoint from the trig functions of a lat and lon pair.
* @param planetModel is the planetModel to put the point on.
* @param sinLat is the sin of the latitude.
* @param sinLon is the sin of the longitude.
* @param cosLat is the cos of the latitude.
* @param cosLon is the cos of the longitude.
*/
public GeoPoint(final PlanetModel planetModel, final double sinLat, final double sinLon, final double cosLat, final double cosLon) {
this(computeDesiredEllipsoidMagnitude(planetModel, cosLat * cosLon, cosLat * sinLon, sinLat),
cosLat * cosLon, cosLat * sinLon, sinLat);
}
/** Construct a GeoPoint from a latitude/longitude pair.
* @param planetModel is the planetModel to put the point on.
* @param lat is the latitude.
* @param lon is the longitude.
*/
public GeoPoint(final PlanetModel planetModel, final double lat, final double lon) {
this(planetModel, Math.sin(lat), Math.sin(lon), Math.cos(lat), Math.cos(lon), lat, lon);
}
/** Construct a GeoPoint from an input stream.
* @param planetModel is the planet model
* @param inputStream is the input stream
*/
public GeoPoint(final PlanetModel planetModel, final InputStream inputStream) throws IOException {
this(inputStream);
}
/** Construct a GeoPoint from an input stream with no planet model.
* @param inputStream is the input stream
*/
public GeoPoint(final InputStream inputStream) throws IOException {
// Note: this relies on left-right parameter execution order!! Much code depends on that though and
// it is apparently in a java spec: https://stackoverflow.com/questions/2201688/order-of-execution-of-parameters-guarantees-in-java
this(SerializableObject.readDouble(inputStream),
SerializableObject.readDouble(inputStream),
SerializableObject.readDouble(inputStream),
SerializableObject.readDouble(inputStream),
SerializableObject.readDouble(inputStream));
}
/** Construct a GeoPoint from five unchecked parameters: lat, lon, x, y, z. This is primarily used for deserialization,
* but can also be used to fully initialize a point externally.
* @param lat is the latitude in radians
* @param lon is the longitude in radians
* @param x is the unit x value
* @param y is the unit y value
* @param z is the unit z value
*/
public GeoPoint(final double lat, final double lon, final double x, final double y, final double z) {
super(x, y, z);
this.latitude = lat;
this.longitude = lon;
}
/** Construct a GeoPoint from a unit (x,y,z) vector and a magnitude.
* @param magnitude is the desired magnitude, provided to put the point on the ellipsoid.
* @param x is the unit x value.
* @param y is the unit y value.
* @param z is the unit z value.
* @param lat is the latitude.
* @param lon is the longitude.
*/
public GeoPoint(final double magnitude, final double x, final double y, final double z, double lat, double lon) {
super(x * magnitude, y * magnitude, z * magnitude);
this.magnitude = magnitude;
if (lat > Math.PI * 0.5 || lat < -Math.PI * 0.5) {
throw new IllegalArgumentException("Latitude " + lat + " is out of range: must range from -Math.PI/2 to Math.PI/2");
}
if (lon < -Math.PI || lon > Math.PI) {
throw new IllegalArgumentException("Longitude " + lon + " is out of range: must range from -Math.PI to Math.PI");
}
this.latitude = lat;
this.longitude = lon;
}
/** Construct a GeoPoint from a unit (x,y,z) vector and a magnitude.
* @param magnitude is the desired magnitude, provided to put the point on the ellipsoid.
* @param x is the unit x value.
* @param y is the unit y value.
* @param z is the unit z value.
*/
public GeoPoint(final double magnitude, final double x, final double y, final double z) {
super(x * magnitude, y * magnitude, z * magnitude);
this.magnitude = magnitude;
}
/** Construct a GeoPoint from an (x,y,z) value.
* The (x,y,z) tuple must be on the desired ellipsoid.
* @param x is the ellipsoid point x value.
* @param y is the ellipsoid point y value.
* @param z is the ellipsoid point z value.
*/
public GeoPoint(final double x, final double y, final double z) {
super(x, y, z);
}
@Override
public void write(final OutputStream outputStream) throws IOException {
SerializableObject.writeDouble(outputStream, getLatitude());
SerializableObject.writeDouble(outputStream, getLongitude());
SerializableObject.writeDouble(outputStream, x);
SerializableObject.writeDouble(outputStream, y);
SerializableObject.writeDouble(outputStream, z);
}
/** Compute an arc distance between two points.
* Note: this is an angular distance, and not a surface distance, and is therefore independent of planet model.
* For surface distance, see {@link PlanetModel#surfaceDistance(GeoPoint, GeoPoint)}
* @param v is the second point.
* @return the angle, in radians, between the two points.
*/
public double arcDistance(final Vector v) {
return Tools.safeAcos(dotProduct(v)/(magnitude() * v.magnitude()));
}
/** Compute an arc distance between two points.
* @param x is the x part of the second point.
* @param y is the y part of the second point.
* @param z is the z part of the second point.
* @return the angle, in radians, between the two points.
*/
public double arcDistance(final double x, final double y, final double z) {
return Tools.safeAcos(dotProduct(x,y,z)/(magnitude() * Vector.magnitude(x,y,z)));
}
/** Compute the latitude for the point.
* @return the latitude.
*/
public double getLatitude() {
double lat = this.latitude;//volatile-read once
if (lat == Double.NEGATIVE_INFINITY)
this.latitude = lat = Math.asin(z / magnitude());
return lat;
}
/** Compute the longitude for the point.
* @return the longitude value. Uses 0.0 if there is no computable longitude.
*/
public double getLongitude() {
double lon = this.longitude;//volatile-read once
if (lon == Double.NEGATIVE_INFINITY) {
if (Math.abs(x) < MINIMUM_RESOLUTION && Math.abs(y) < MINIMUM_RESOLUTION)
this.longitude = lon = 0.0;
else
this.longitude = lon = Math.atan2(y,x);
}
return lon;
}
/** Compute the linear magnitude of the point.
* @return the magnitude.
*/
@Override
public double magnitude() {
double mag = this.magnitude;//volatile-read once
if (mag == Double.NEGATIVE_INFINITY) {
this.magnitude = mag = super.magnitude();
}
return mag;
}
/** Compute whether point matches another.
*@param p is the other point.
*@return true if the same.
*/
public boolean isIdentical(final GeoPoint p) {
return isIdentical(p.x, p.y, p.z);
}
/** Compute whether point matches another.
*@param x is the x value
*@param y is the y value
*@param z is the z value
*@return true if the same.
*/
public boolean isIdentical(final double x, final double y, final double z) {
return Math.abs(this.x - x) < MINIMUM_RESOLUTION &&
Math.abs(this.y - y) < MINIMUM_RESOLUTION &&
Math.abs(this.z - z) < MINIMUM_RESOLUTION;
}
@Override
public String toString() {
if (this.longitude == Double.NEGATIVE_INFINITY) {
return super.toString();
}
return "[lat="+getLatitude()+", lon="+getLongitude()+"("+super.toString()+")]";
}
}