All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opentripplanner.framework.geometry.WgsCoordinate Maven / Gradle / Ivy

The newest version!
package org.opentripplanner.framework.geometry;

import java.io.Serializable;
import java.util.Collection;
import java.util.Objects;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Point;
import org.opentripplanner.utils.lang.DoubleUtils;
import org.opentripplanner.utils.tostring.ValueObjectToStringBuilder;

/**
 * This class represent a OTP coordinate.
 * 

* This is a ValueObject (design pattern). */ public final class WgsCoordinate implements Serializable { public static final WgsCoordinate GREENWICH = new WgsCoordinate(51.48, 0d); private static final double LAT_MIN = -90; private static final double LAT_MAX = 90; private static final double LON_MIN = -180; private static final double LON_MAX = 180; private final double latitude; private final double longitude; public WgsCoordinate(double latitude, double longitude) { // Verify coordinates are in range DoubleUtils.requireInRange(latitude, LAT_MIN, LAT_MAX, "latitude"); DoubleUtils.requireInRange(longitude, LON_MIN, LON_MAX, "longitude"); // Normalize coordinates to precision around ~ 1 centimeters (7 decimals) this.latitude = DoubleUtils.roundTo7Decimals(latitude); this.longitude = DoubleUtils.roundTo7Decimals(longitude); } public WgsCoordinate(Point point) { Objects.requireNonNull(point); this.latitude = DoubleUtils.roundTo7Decimals(point.getY()); this.longitude = DoubleUtils.roundTo7Decimals(point.getX()); } public WgsCoordinate(Coordinate coordinate) { Objects.requireNonNull(coordinate); this.latitude = DoubleUtils.roundTo7Decimals(coordinate.getY()); this.longitude = DoubleUtils.roundTo7Decimals(coordinate.getX()); } /** * Unlike the constructor this factory method retuns {@code null} if both {@code lat} and {@code * lon} is {@code null}. */ public static WgsCoordinate creatOptionalCoordinate(Double latitude, Double longitude) { if (latitude == null && longitude == null) { return null; } // Set coordinate is both lat and lon exist if (latitude != null && longitude != null) { return new WgsCoordinate(latitude, longitude); } throw new IllegalArgumentException( "Both 'latitude' and 'longitude' must have a value or both must be 'null'." ); } /** * Find the mean coordinate between the given set of {@code coordinates}. */ public static WgsCoordinate mean(Collection coordinates) { if (coordinates.isEmpty()) { throw new IllegalArgumentException( "Unable to calculate mean for an empty set of coordinates" ); } if (coordinates.size() == 1) { return coordinates.iterator().next(); } double n = coordinates.size(); double latitude = 0.0; double longitude = 0.0; for (WgsCoordinate c : coordinates) { latitude += c.latitude(); longitude += c.longitude(); } return new WgsCoordinate(latitude / n, longitude / n); } public double latitude() { return latitude; } public double longitude() { return longitude; } /** Return OTP domain coordinate as JTS GeoTools Library coordinate. */ public Coordinate asJtsCoordinate() { return new Coordinate(longitude, latitude); } /** * Compare to coordinates and return {@code true} if they are close together - have the same * location. The comparison uses an EPSILON of 1E-7 for each axis, for both latitude and * longitude. * * When we compare two coordinates we want to see if they are within a given distance, * roughly within a square centimeter. This is not * transitive, hence violating the equals/hasCode guideline. Consider 3 point along * one of the axis: *

   *      | 8mm | 8mm |
   *      x --- y --- z
   *     
* Then {@code x.sameLocation(y)} is {@code true} and {@code y.sameLocation(z)} is {@code true}, * but {@code x.sameLocation(z)} is {@code false}. */ public boolean sameLocation(WgsCoordinate other) { return equals(other); } /** * Add (deltaLat, deltaLon) to the current coordinates and return the new position. */ public WgsCoordinate add(double deltaLat, double deltaLon) { return new WgsCoordinate(latitude + deltaLat, longitude + deltaLon); } /** * Return a new version of this coordinate where latitude/longitude are rounded to 4 decimal * places which at the equator has ~11 meter precision. *

* See https://wiki.openstreetmap.org/wiki/Precision_of_coordinates *

* This is useful when you want to cache coordinate-based computations but don't need absolute * precision. *

* DO NOT USE THIS IN ROUTING (USE AT LEAST 7 DECIMALS)! */ public WgsCoordinate roundToApproximate10m() { var lat = DoubleUtils.roundTo4Decimals(latitude); var lng = DoubleUtils.roundTo4Decimals(longitude); return new WgsCoordinate(lat, lng); } /** * Return a new version of this coordinate where latitude/longitude are rounded to 3 decimal * places which at the equator has ~100 meter precision. *

* See https://wiki.openstreetmap.org/wiki/Precision_of_coordinates *

* This is useful when you want to cache coordinate-based computations but don't need absolute * precision. *

* DO NOT USE THIS IN ROUTING (USE AT LEAST 7 DECIMALS)! */ public WgsCoordinate roundToApproximate100m() { var lat = DoubleUtils.roundTo3Decimals(latitude); var lng = DoubleUtils.roundTo3Decimals(longitude); return new WgsCoordinate(lat, lng); } /** * Compute a fairly accurate distance between two coordinates. Use the fast version in * {@link SphericalDistanceLibrary} if many computations are needed. Return the distance in * meters between the two coordinates. */ public double distanceTo(WgsCoordinate other) { return SphericalDistanceLibrary.distance( this.latitude, this.longitude, other.latitude, other.longitude ); } public boolean isNorthOf(double latitudeBorder) { return latitude > latitudeBorder; } /** * Return a new coordinate that is moved an approximate number of meters east. */ public WgsCoordinate moveEastMeters(double meters) { return SphericalDistanceLibrary.moveMeters(this, 0, meters); } /** * Return a new coordinate that is moved an approximate number of meters west. */ public WgsCoordinate moveWestMeters(double meters) { return SphericalDistanceLibrary.moveMeters(this, 0, -meters); } /** * Return a new coordinate that is moved an approximate number of meters north. */ public WgsCoordinate moveNorthMeters(double meters) { return SphericalDistanceLibrary.moveMeters(this, meters, 0); } /** * Return a new coordinate that is moved an approximate number of meters south. */ public WgsCoordinate moveSouthMeters(double meters) { return SphericalDistanceLibrary.moveMeters(this, -meters, 0); } /** * Return a string on the form: {@code "(60.12345, 11.12345)"}. Up to 5 digits are used after the * period(.), even if the coordinate is specified with a higher precision. */ @Override public String toString() { return ValueObjectToStringBuilder.of().addCoordinate(latitude(), longitude()).toString(); } /** * Return true if the coordinates are numerically equal. The coordinate latitude and longitude * are rounded to the closest number of 1E-7 when constructed. This enforces two coordinates * that is close together to be equals. */ @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } WgsCoordinate other = (WgsCoordinate) o; return latitude == other.latitude && longitude == other.longitude; } @Override public int hashCode() { return Objects.hash(latitude, longitude); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy