package org.limewire.promotion;
public class LatitudeLongitude {
private final static double AVERAGE_EARTH_RADIUS_KM = 6371.0;
private final byte[] latitude;
private final byte[] longitude;
/**
* Construct an instance using the given byte-encoded integer values using
* the following decoding:
* <ul>
* <li>convert the array to a long (1-8 bytes are acceptable)
* <li>Calculate the conversion rate byte taking (2^(array.length*8))/360
* (for instance, 3 bytes would be (16,777,216/360.0)
* <li>Divide the converted long by the conversion rate to get the degrees,
* and pass that to the degree constructor
* </ul>
*
* @param latitude the Latitude
* @param longitude the Longitude
*/
public LatitudeLongitude(byte[] latitude, byte[] longitude) {
this.latitude = latitude;
this.longitude = longitude;
}
/**
* Construct an instance with the given latitude and longitude converted to
* be storable in 3 bytes each.
*
* @param latitudeDegrees North > 0, South < 0
* @param longitudeDegrees East > 0, West < 0
*/
public LatitudeLongitude(double latitudeDegrees, double longitudeDegrees) {
this(convertDegreesToBytes(latitudeDegrees, 3), convertDegreesToBytes(longitudeDegrees, 3));
}
/**
* Converts a degree value to a normal value between 0 and 359 to a byte
* array with the given byte count (1-8 bytes). The array will represent a
* long between 0 and 2^(8 * byteCount), where the highest value scales to
* 360. Package visible for unit testing.
* <p>
* Examples:
* <ul>
* <li>(360,3) -> [-1,-1,-1]
* <li>(0,3) -> [0,0,0]
* <li>(180,3) -> [127,-1,-1]
*/
static byte[] convertDegreesToBytes(double degrees, int byteCount) {
double scale = Math.pow(2, 8 * byteCount) / 360.0;
long value = (long) (normalizeDegrees(degrees) * scale);
return org.limewire.util.ByteUtils.long2bytes(value, byteCount);
}
/**
* Takes a degree measure and returns a value between 0 and 359. -90 becomes
* 270, 361 becomes 1, etc.
*/
private static double normalizeDegrees(double degrees) {
while (degrees < 0)
degrees += 360;
return degrees % 360;
}
/**
* Converts a B.E. byte array into a long, then converts the long to a
* double between 0 and 360 based on the maximum size of the long, then
* converts to radians. Package-visible for unit testing.
*/
static double toRadians(byte[] bytes) {
long value = org.limewire.util.ByteUtils.beb2long(bytes, 0, bytes.length);
return Math.toRadians(value / (Math.pow(2, 8 * bytes.length) / 360.0));
}
private double getLatitudeRadians() {
return toRadians(latitude);
}
private double getLongitudeRadians() {
return toRadians(longitude);
}
/** @return the distance from the other point, in kilometers. */
public double distanceFrom(LatitudeLongitude otherPoint) {
double lat = getLatitudeRadians();
double lon = getLongitudeRadians();
double otherlat = otherPoint.getLatitudeRadians();
double otherlon = otherPoint.getLongitudeRadians();
double p1 = Math.cos(lat) * Math.cos(lon) * Math.cos(otherlat) * Math.cos(otherlon);
double p2 = Math.cos(lat) * Math.sin(lon) * Math.cos(otherlat) * Math.sin(otherlon);
double p3 = Math.sin(lat) * Math.sin(otherlat);
return (Math.acos(p1 + p2 + p3) * AVERAGE_EARTH_RADIUS_KM);
}
/**
* If this instance was created using
* {@link LatitudeLongitude#LatitudeLongitude(double, double)} or using
* {@link LatitudeLongitude#LatitudeLongitude(byte[], byte[])} using a pair
* of 3-byte arrays, returns an exact representation for that value.
* Otherwise a new approximation is created (probably accurate enough to
* never matter since we're only accurate to 2 meters anyway) and returned.
*
* @return a 6-byte array, the first 3 bytes encoding latitude, the second 3
* encoding longitude.
*/
public byte[] toBytes() {
byte[] array = new byte[6];
System.arraycopy(to3Bytes(latitude), 0, array, 0, 3);
System.arraycopy(to3Bytes(longitude), 0, array, 3, 3);
return array;
}
/**
* if the array is 3 bytes exactly, returns the original array. Otherwise,
* converts the array into a degree representation and then back into a byte
* array of the correct size
*/
private byte[] to3Bytes(byte[] in) {
if (in.length == 3)
return in;
double value = toRadians(in);
return convertDegreesToBytes(Math.toDegrees(value), 3);
}
}