@Override public String toString() { return String.valueOf(this.asDegrees()); } }
/** * @param yAxis * The tile's y index * @param zoom * The zoom level * @return The corresponding latitude */ private Latitude tile2lat(final int yAxis, final int zoom) { final double pivot = Math.PI - CIRCULAR_MULTIPLIER * Math.PI * yAxis / Math.pow(ZOOM_LEVEL_POWER, zoom); return Latitude.degrees(Math.toDegrees(Math.atan(Math.sinh(pivot)))); }
/** * @param degrees * A Latitude value in degrees * @return The built {@link Latitude} object using the degrees value */ public static Latitude degrees(final double degrees) { return dm7(Math.round(degrees * DM7_PER_DEGREE)); }
/** * The half-way point along a great-circle path between this and that point * * @param that * The other point to compute the midpoint between * @return The {@link Location} of the midpoint * @see "http://www.movable-type.co.uk/scripts/latlong.html" */ public Location midPoint(final Location that) { // Convert to Radians final double lat1 = this.getLatitude().asRadians(); final double lon1 = this.getLongitude().asRadians(); final double lat2 = that.getLatitude().asRadians(); final double lon2 = that.getLongitude().asRadians(); final double longitudeDelta = lon2 - lon1; final double xBearing = Math.cos(lat2) * Math.cos(longitudeDelta); final double yBearing = Math.cos(lat2) * Math.sin(longitudeDelta); final double pheta = Math.atan2(Math.sin(lat1) + Math.sin(lat2), Math.sqrt( (Math.cos(lat1) + xBearing) * (Math.cos(lat1) + xBearing) + yBearing * yBearing)); double lambda = lon1 + Math.atan2(yBearing, Math.cos(lat1) + xBearing); // Normalize to -180/180 lambda = (lambda + FACTOR_OF_3 * Math.PI) % (2 * Math.PI) - Math.PI; return new Location(Latitude.radians(pheta), Longitude.radians(lambda)); }
/** * @param bounds * Bounds to constrain the result * @return A random location within the bounds */ public static Location random(final Rectangle bounds) { final int latitude = RANDOM.ints((int) bounds.lowerLeft().getLatitude().asDm7(), (int) bounds.upperRight().getLatitude().asDm7()).iterator().next(); final int longitude = RANDOM.ints((int) bounds.lowerLeft().getLongitude().asDm7(), (int) bounds.upperRight().getLongitude().asDm7()).iterator().next(); return new Location(Latitude.dm7(latitude), Longitude.dm7(longitude)); }
/** * @return A dm7 latitude and dm7 longitude concatenated in a long. The first 32 bits are for * the dm7 latitude, and the last 32 bits are for the dm7 longitude. */ public long asConcatenation() { long result = this.latitude.asDm7(); result <<= INT_SIZE; result |= this.longitude.asDm7() & INT_FULL_MASK_AS_LONG; return result; }
final double latitude1 = this.getLatitude().asRadians(); final double longitude1 = this.getLongitude().asRadians(); final double bearing = initialHeading.asRadians(); return new Location(Latitude.radiansBounded(latitude2), Longitude.radiansBounded(longitude2));
/** * @param other * The other {@link Location} to test * @return True if this {@link Location} and the other {@link Location} to test are on the same * East-West line. */ public boolean hasSameLatitudeAs(final Location other) { return this.getLatitude().equals(other.getLatitude()); }
/** * This uses the ‘haversine’ formula to calculate the great-circle distance between two * locations, more calculation but more accurate * * @param that * The other point to compute the distance to * @return The haversine distance * @see "http://www.movable-type.co.uk/scripts/latlong.html" */ public Distance haversineDistanceTo(final Location that) { // convert to radians final double lat1 = this.getLatitude().asRadians(); final double lon1 = this.getLongitude().asRadians(); final double lat2 = that.getLatitude().asRadians(); final double lon2 = that.getLongitude().asRadians(); final double deltaLat = lat2 - lat1; final double deltaLon = lon2 - lon1; final double hav = Math.pow(Math.sin(deltaLat / 2), 2) + Math.cos(lat1) * Math.cos(lat2) * Math.pow(Math.sin(deltaLon / 2), 2); final double result = 2 * Math.atan2(Math.sqrt(hav), Math.sqrt(1 - hav)); return Distance.AVERAGE_EARTH_RADIUS.scaleBy(result); }
/** * @param dm7 * A latitude value in degree of magnitude 7 (dm7) * @return The built {@link Latitude} object using the dm7 value */ public static Latitude dm7(final long dm7) { if (dm7 < MINIMUM_DM7 || dm7 > MAXIMUM_DM7) { throw new IllegalArgumentException("Cannot have a latitude of " + dm7 / DM7_PER_DEGREE + " degrees which is outside of " + MINIMUM_DM7 / DM7_PER_DEGREE + " degrees -> " + MAXIMUM_DM7 / DM7_PER_DEGREE + " degrees."); } return new Latitude((int) dm7); }
@Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (this.latitude == null ? 0 : this.latitude.hashCode()); result = prime * result + (this.longitude == null ? 0 : this.longitude.hashCode()); return result; }
@Override public EarthCenteredEarthFixedCoordinate convert(final GeodeticCoordinate coordinate) { final double radiusOfCurviture = WorldGeodeticStandardConstants.SEMI_MAJOR_AXIS.asMeters() / Math.sqrt(1 - WorldGeodeticStandardConstants.ECCENTRICITY_SQUARED * Math.pow(Math.sin(coordinate.getLatitude().asPositiveRadians()), 2)); final double height = coordinate.getAltitude().asMeters(); final double xValue = (radiusOfCurviture + height) * Math.cos(coordinate.getLatitude().asPositiveRadians()) * Math.cos(coordinate.getLongitude().asPositiveRadians()); final double yValue = (radiusOfCurviture + height) * Math.cos(coordinate.getLatitude().asPositiveRadians()) * Math.sin(coordinate.getLongitude().asPositiveRadians()); final double zValue = ((1 - WorldGeodeticStandardConstants.ECCENTRICITY_SQUARED) * radiusOfCurviture + height) * Math.sin(coordinate.getLatitude().asPositiveRadians()); return new EarthCenteredEarthFixedCoordinate(xValue, yValue, zValue); } }
final double p0Y = this.start().getLatitude().asDegrees(); final double p1X = this.end().getLongitude().asDegrees(); final double p1Y = this.end().getLatitude().asDegrees(); final double p2X = that.start().getLongitude().asDegrees(); final double p2Y = that.start().getLatitude().asDegrees(); final double p3X = that.end().getLongitude().asDegrees(); final double p3Y = that.end().getLatitude().asDegrees(); return new Location(Latitude.degrees(p0Y + tValue * s1Y), Longitude.degrees(p0X + tValue * s1X));
final double latitudeAsDm7 = shape.start().getLatitude().asDm7() + offsetDistance / shape.dotProductLength() * shape.latitudeSpan(); final double longitudeAsDm7 = shape.start().getLongitude().asDm7() + offsetDistance / shape.dotProductLength() * shape.longitudeSpan(); final Location snapped = new Location(Latitude.dm7(Math.round(latitudeAsDm7)), Longitude.dm7(Math.round(longitudeAsDm7))); return new SnappedLocation(origin, snapped, shape);
/** * Midpoint along a Rhumb line between this point and that point * * @param that * The other point to compute the midpoint between * @return The {@link Location} of the loxodromic midpoint * @see "http://www.movable-type.co.uk/scripts/latlong.html" */ public Location loxodromicMidPoint(final Location that) { // Convert to Radians final double lat1 = this.getLatitude().asRadians(); double lon1 = this.getLongitude().asRadians(); final double lat2 = that.getLatitude().asRadians(); final double lon2 = that.getLongitude().asRadians(); // Crossing anti-meridian if (Math.abs(lon2 - lon1) > Math.PI) { lon1 += 2 * Math.PI; } final double pheta = (lat1 + lat2) / 2; final double phi1 = Math.tan(Math.PI / 4 + lat1 / 2); final double phi2 = Math.tan(Math.PI / 4 + lat2 / 2); final double phi3 = Math.tan(Math.PI / 4 + pheta / 2); double lambda = ((lon2 - lon1) * Math.log(phi3) + lon1 * Math.log(phi2) - lon2 * Math.log(phi1)) / Math.log(phi2 / phi1); // Normalize to -180/180 lambda = (lambda + FACTOR_OF_3 * Math.PI) % (2 * Math.PI) - Math.PI; return new Location(Latitude.radians(pheta), Longitude.radians(lambda)); }
@Override public boolean equals(final Object other) { if (other instanceof Location) { final Location that = (Location) other; return this.getLatitude().equals(that.getLatitude()) && this.getLongitude().equals(that.getLongitude()); } return false; }
/** * @return The approximate surface area of this polygon if it were projected onto the Earth. Not * valid if the polygon self-intersects, and/or overlaps itself. Uses "Some Algorithms * for Polygons on a Sphere" paper as reference. * @see "https://trs.jpl.nasa.gov/bitstream/handle/2014/41271/07-0286.pdf" */ @Override public Surface surfaceOnSphere() { double dm7 = 0L; final List<Location> locations = Lists.newArrayList(this.closedLoop()); if (locations.size() > 2) { double radians = 0L; for (int index = 0; index < locations.size() - 1; index++) { radians += (locations.get(index + 1).getLongitude().asRadians() - locations.get(index).getLongitude().asRadians()) * (2 + Math.sin(locations.get(index).getLatitude().asRadians()) + Math.sin(locations.get(index + 1).getLatitude().asRadians())); } radians = Math.abs(radians / 2.0); // Calculations are in Radians, convert to Degrees. dm7 = radians * ((double) Angle.DM7_PER_RADIAN * (double) Angle.DM7_PER_RADIAN); } return Surface.forDm7Squared(Math.round(dm7)); }
@Override public Location backwardConvert(final Coordinate coordinate) { return new Location(Latitude.degrees(coordinate.y), Longitude.degrees(coordinate.x)); }