source: josm/trunk/src/org/openstreetmap/josm/data/coor/ILatLon.java

Last change on this file was 18589, checked in by taylor.smock, 18 months ago

Fix #22453: Decrease allocations/CPU samples in Geometry.getDistanceSegmentSegment

Statistics, using Mesa County, CO as the data ("nwr in 'Mesa County, CO'") with
the MapWithAI plugin validations enabled (validation run):

  • CPU samples: 54,604 -> 39,146 (-15,458, -28.3%)
  • Memory allocations: 170,983,028,400 -> 4,645,539,624 (-166,337,488,776, -97.3%)
    • All remaining allocations are from creating a new LatLon during interpolation.
  • .7% improvement in GC threads (overall, see notes).

Notes:

  • getDistanceWayNode was also modified to avoid EastNorth allocations.
  • All remaining allocations come from the creation of new LatLon objects. This may be alleviated when value classes become a thing in Java LTS, and we start using them.
  • Profiling was done from application startup to shut down.
File size: 7.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.coor;
3
4import static java.lang.Math.PI;
5import static java.lang.Math.asin;
6import static java.lang.Math.atan2;
7import static java.lang.Math.cos;
8import static java.lang.Math.sin;
9import static java.lang.Math.sqrt;
10import static org.openstreetmap.josm.data.projection.Ellipsoid.WGS84;
11import static org.openstreetmap.josm.tools.Utils.toRadians;
12
13import org.openstreetmap.josm.data.projection.Projecting;
14import org.openstreetmap.josm.tools.Logging;
15
16/**
17 * This interface represents a coordinate in LatLon space.
18 * <p>
19 * It provides methods to get the coordinates. The coordinates may be unknown.
20 * In this case, both {@link #lat()} and {@link #lon()} need to return a NaN value and {@link #isLatLonKnown()} needs to return false.
21 * <p>
22 * Whether the coordinates are immutable or not is implementation specific.
23 *
24 * @author Michael Zangl
25 * @since 12161
26 */
27public interface ILatLon {
28 /**
29 * Minimum difference in location to not be represented as the same position.
30 * The API returns 7 decimals.
31 */
32 double MAX_SERVER_PRECISION = 1e-7;
33
34 /**
35 * Returns the longitude, i.e., the east-west position in degrees.
36 * @return the longitude or NaN if {@link #isLatLonKnown()} returns false
37 */
38 double lon();
39
40 /**
41 * Returns the latitude, i.e., the north-south position in degrees.
42 * @return the latitude or NaN if {@link #isLatLonKnown()} returns false
43 */
44 double lat();
45
46 /**
47 * Determines if this object has valid coordinates.
48 * @return {@code true} if this object has valid coordinates
49 */
50 default boolean isLatLonKnown() {
51 return !Double.isNaN(lat()) && !Double.isNaN(lon());
52 }
53
54 /**
55 * Replies the projected east/north coordinates.
56 * <p>
57 * The result of the last conversion may be cached. Null is returned in case this object is invalid.
58 * @param projecting The projection to use.
59 * @return The projected east/north coordinates
60 * @since 10827
61 */
62 default EastNorth getEastNorth(Projecting projecting) {
63 if (!isLatLonKnown()) {
64 return null;
65 } else {
66 return projecting.latlon2eastNorth(this);
67 }
68 }
69
70 /**
71 * Determines if the other point has almost the same lat/lon values.
72 * @param other other lat/lon
73 * @return <code>true</code> if the other point has almost the same lat/lon
74 * values, only differing by no more than 1 / {@link #MAX_SERVER_PRECISION MAX_SERVER_PRECISION}.
75 * @since 18464 (extracted from {@link LatLon})
76 */
77 default boolean equalsEpsilon(ILatLon other) {
78 return equalsEpsilon(other, MAX_SERVER_PRECISION);
79 }
80
81 /**
82 * Determines if the other point has almost the same lat/lon values.
83 * @param other other lat/lon
84 * @param precision The precision to use
85 * @return <code>true</code> if the other point has almost the same lat/lon
86 * values, only differing by no more than 1 / precision.
87 * @since 18464 (extracted from {@link LatLon})
88 */
89 default boolean equalsEpsilon(ILatLon other, double precision) {
90 double p = precision / 2;
91 return Math.abs(lat() - other.lat()) <= p && Math.abs(lon() - other.lon()) <= p;
92 }
93
94 /**
95 * Computes the distance between this lat/lon and another point on the earth.
96 * Uses <a href="https://en.wikipedia.org/wiki/Haversine_formula">Haversine formula</a>.
97 * @param other the other point.
98 * @return distance in metres.
99 * @since 18494 (extracted from {@link LatLon})
100 */
101 default double greatCircleDistance(ILatLon other) {
102 double sinHalfLat = sin(toRadians(other.lat() - this.lat()) / 2);
103 double sinHalfLon = sin(toRadians(other.lon() - this.lon()) / 2);
104 double d = 2 * WGS84.a * asin(
105 sqrt(sinHalfLat*sinHalfLat +
106 cos(toRadians(this.lat()))*cos(toRadians(other.lat()))*sinHalfLon*sinHalfLon));
107 // For points opposite to each other on the sphere,
108 // rounding errors could make the argument of asin greater than 1
109 // (This should almost never happen.)
110 if (Double.isNaN(d)) {
111 Logging.error("NaN in greatCircleDistance: {0} {1}", this, other);
112 d = PI * WGS84.a;
113 }
114 return d;
115 }
116
117 /**
118 * Returns bearing from this point to another.
119 *
120 * Angle starts from north and increases clockwise, PI/2 means east.
121 *
122 * Please note that reverse bearing (from other point to this point) should NOT be
123 * calculated from return value of this method, because great circle path
124 * between the two points have different bearings at each position.
125 *
126 * To get bearing from another point to this point call other.bearing(this)
127 *
128 * @param other the "destination" position
129 * @return heading in radians in the range 0 &lt;= hd &lt; 2*PI
130 * @since 18494 (extracted from {@link LatLon}, added in 9796)
131 */
132 default double bearing(ILatLon other) {
133 double lat1 = toRadians(this.lat());
134 double lat2 = toRadians(other.lat());
135 double dlon = toRadians(other.lon() - this.lon());
136 double bearing = atan2(
137 sin(dlon) * cos(lat2),
138 cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dlon)
139 );
140 bearing %= 2 * PI;
141 if (bearing < 0) {
142 bearing += 2 * PI;
143 }
144 return bearing;
145 }
146
147 /**
148 * Does a linear interpolation between two ILatLon instances.
149 * @param ll2 The other ILatLon instance.
150 * @param proportion The proportion the other instance influences the result.
151 * @return The new {@link ILatLon} position.
152 * @since 18589
153 */
154 default ILatLon interpolate(ILatLon ll2, double proportion) {
155 // this is an alternate form of this.lat() + proportion * (ll2.lat() - this.lat()) that is slightly faster
156 return new LatLon((1 - proportion) * this.lat() + proportion * ll2.lat(),
157 (1 - proportion) * this.lon() + proportion * ll2.lon());
158 }
159
160 /**
161 * Returns the square of euclidean distance from this {@code Coordinate} to a specified coordinate.
162 *
163 * @param lon the X coordinate of the specified point to be measured against this {@code Coordinate}
164 * @param lat the Y coordinate of the specified point to be measured against this {@code Coordinate}
165 * @return the square of the euclidean distance from this {@code Coordinate} to a specified coordinate
166 * @since 18589
167 */
168 default double distanceSq(final double lon, final double lat) {
169 final double dx = this.lon() - lon;
170 final double dy = this.lat() - lat;
171 return dx * dx + dy * dy;
172 }
173
174 /**
175 * Returns the euclidean distance from this {@code ILatLon} to a specified {@code ILatLon}.
176 *
177 * @param other the specified coordinate to be measured against this {@code ILatLon}
178 * @return the euclidean distance from this {@code ILatLon} to a specified {@code ILatLon}
179 * @since 18589
180 */
181 default double distanceSq(final ILatLon other) {
182 return this.distanceSq(other.lon(), other.lat());
183 }
184}
Note: See TracBrowser for help on using the repository browser.