source: josm/trunk/src/org/openstreetmap/josm/data/coor/LatLon.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.
  • Property svn:eol-style set to native
File size: 11.5 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.coor;
3
4import java.awt.geom.Area;
5import java.text.DecimalFormat;
6import java.text.NumberFormat;
7import java.util.Locale;
8import java.util.Objects;
9
10import org.openstreetmap.josm.data.Bounds;
11import org.openstreetmap.josm.data.osm.Node;
12import org.openstreetmap.josm.data.projection.ProjectionRegistry;
13import org.openstreetmap.josm.tools.Utils;
14
15/**
16 * LatLon are unprojected latitude / longitude coordinates.
17 * <br>
18 * <b>Latitude</b> specifies the north-south position in degrees
19 * where valid values are in the [-90,90] and positive values specify positions north of the equator.
20 * <br>
21 * <b>Longitude</b> specifies the east-west position in degrees
22 * where valid values are in the [-180,180] and positive values specify positions east of the prime meridian.
23 * <br>
24 * <img alt="lat/lon" src="https://upload.wikimedia.org/wikipedia/commons/6/62/Latitude_and_Longitude_of_the_Earth.svg">
25 * <br>
26 * This class is immutable.
27 *
28 * @author Imi
29 */
30public class LatLon extends Coordinate implements ILatLon {
31
32 private static final long serialVersionUID = 1L;
33
34 /**
35 * Minimum difference in location to not be represented as the same position.
36 * The API returns 7 decimals.
37 */
38 public static final double MAX_SERVER_PRECISION = ILatLon.MAX_SERVER_PRECISION;
39 /**
40 * The inverse of the server precision
41 * @see #MAX_SERVER_PRECISION
42 */
43 public static final double MAX_SERVER_INV_PRECISION = 1e7;
44
45 /**
46 * The (0,0) coordinates.
47 * @since 6178
48 */
49 public static final LatLon ZERO = new LatLon(0, 0);
50
51 /** North pole. */
52 public static final LatLon NORTH_POLE = new LatLon(90, 0);
53 /** South pole. */
54 public static final LatLon SOUTH_POLE = new LatLon(-90, 0);
55
56 /**
57 * The normal number format for server precision coordinates
58 */
59 public static final DecimalFormat cDdFormatter;
60 /**
61 * The number format used for high precision coordinates
62 */
63 public static final DecimalFormat cDdHighPrecisionFormatter;
64 static {
65 // Don't use the localized decimal separator. This way we can present
66 // a comma separated list of coordinates.
67 cDdFormatter = (DecimalFormat) NumberFormat.getInstance(Locale.UK);
68 cDdFormatter.applyPattern("###0.0######");
69 cDdHighPrecisionFormatter = (DecimalFormat) NumberFormat.getInstance(Locale.UK);
70 cDdHighPrecisionFormatter.applyPattern("###0.0##########");
71 }
72
73 /**
74 * Replies true if lat is in the range [-90,90]
75 *
76 * @param lat the latitude
77 * @return true if lat is in the range [-90,90]
78 */
79 public static boolean isValidLat(double lat) {
80 return lat >= -90d && lat <= 90d;
81 }
82
83 /**
84 * Replies true if lon is in the range [-180,180]
85 *
86 * @param lon the longitude
87 * @return true if lon is in the range [-180,180]
88 */
89 public static boolean isValidLon(double lon) {
90 return lon >= -180d && lon <= 180d;
91 }
92
93 /**
94 * Make sure longitude value is within <code>[-180, 180]</code> range.
95 * @param lon the longitude in degrees
96 * @return lon plus/minus multiples of <code>360</code>, as needed to get
97 * in <code>[-180, 180]</code> range
98 */
99 public static double normalizeLon(double lon) {
100 if (lon >= -180 && lon <= 180)
101 return lon;
102 else {
103 lon = lon % 360.0;
104 if (lon > 180) {
105 return lon - 360;
106 } else if (lon < -180) {
107 return lon + 360;
108 }
109 return lon;
110 }
111 }
112
113 /**
114 * Replies true if lat is in the range [-90,90] and lon is in the range [-180,180]
115 *
116 * @return true if lat is in the range [-90,90] and lon is in the range [-180,180]
117 */
118 public boolean isValid() {
119 return isValidLat(lat()) && isValidLon(lon());
120 }
121
122 /**
123 * Clamp the lat value to be inside the world.
124 * @param value The value
125 * @return The value clamped to the world.
126 */
127 public static double toIntervalLat(double value) {
128 return Utils.clamp(value, -90, 90);
129 }
130
131 /**
132 * Returns a valid OSM longitude [-180,+180] for the given extended longitude value.
133 * For example, a value of -181 will return +179, a value of +181 will return -179.
134 * @param value A longitude value not restricted to the [-180,+180] range.
135 * @return a valid OSM longitude [-180,+180]
136 */
137 public static double toIntervalLon(double value) {
138 if (isValidLon(value))
139 return value;
140 else {
141 int n = (int) (value + Math.signum(value)*180.0) / 360;
142 return value - n*360.0;
143 }
144 }
145
146 /**
147 * Constructs a new object representing the given latitude/longitude.
148 * @param lat the latitude, i.e., the north-south position in degrees
149 * @param lon the longitude, i.e., the east-west position in degrees
150 */
151 public LatLon(double lat, double lon) {
152 super(lon, lat);
153 }
154
155 /**
156 * Creates a new LatLon object for the given coordinate
157 * @param coor The coordinates to copy from.
158 */
159 public LatLon(ILatLon coor) {
160 super(coor.lon(), coor.lat());
161 }
162
163 @Override
164 public double lat() {
165 return y;
166 }
167
168 @Override
169 public double lon() {
170 return x;
171 }
172
173 /**
174 * Determines if the other point has almost the same lat/lon values.
175 * @param other other lat/lon
176 * @return <code>true</code> if the other point has almost the same lat/lon
177 * values, only differing by no more than 1 / {@link #MAX_SERVER_PRECISION MAX_SERVER_PRECISION}.
178 * @deprecated since 18464 (use {@link ILatLon#equalsEpsilon(ILatLon)} instead)
179 */
180 @Deprecated
181 public boolean equalsEpsilon(LatLon other) {
182 return ILatLon.super.equalsEpsilon(other);
183 }
184
185 /**
186 * Determines if this lat/lon is outside of the world
187 * @return <code>true</code>, if the coordinate is outside the world, compared by using lat/lon.
188 * @deprecated use {@link Node#isOutSideWorld} instead, see also #13538.
189 */
190 @Deprecated
191 public boolean isOutSideWorld() {
192 Bounds b = ProjectionRegistry.getProjection().getWorldBoundsLatLon();
193 return lat() < b.getMinLat() || lat() > b.getMaxLat() ||
194 lon() < b.getMinLon() || lon() > b.getMaxLon();
195 }
196
197 /**
198 * Determines if this lat/lon is within the given bounding box.
199 * @param b bounding box
200 * @return <code>true</code> if this is within the given bounding box.
201 */
202 public boolean isWithin(Bounds b) {
203 return b.contains(this);
204 }
205
206 /**
207 * Check if this is contained in given area or area is null.
208 *
209 * @param a Area
210 * @return <code>true</code> if this is contained in given area or area is null.
211 */
212 public boolean isIn(Area a) {
213 return a == null || a.contains(x, y);
214 }
215
216 /**
217 * Computes the distance between this lat/lon and another point on the earth.
218 * Uses <a href="https://en.wikipedia.org/wiki/Haversine_formula">Haversine formula</a>.
219 * @param other the other point.
220 * @return distance in metres.
221 * @deprecated since 18494 (use {@link ILatLon#greatCircleDistance(ILatLon)} instead)
222 */
223 @Deprecated
224 public double greatCircleDistance(LatLon other) {
225 return ILatLon.super.greatCircleDistance(other);
226 }
227
228 /**
229 * Returns bearing from this point to another.
230 *
231 * Angle starts from north and increases clockwise, PI/2 means east.
232 *
233 * Please note that reverse bearing (from other point to this point) should NOT be
234 * calculated from return value of this method, because great circle path
235 * between the two points have different bearings at each position.
236 *
237 * To get bearing from another point to this point call other.bearing(this)
238 *
239 * @param other the "destination" position
240 * @return heading in radians in the range 0 &lt;= hd &lt; 2*PI
241 * @since 9796
242 * @deprecated since 18494 (use {@link ILatLon#bearing(ILatLon)} instead)
243 */
244 @Deprecated
245 public double bearing(LatLon other) {
246 return ILatLon.super.bearing(other);
247 }
248
249 /**
250 * Returns this lat/lon pair in human-readable format.
251 *
252 * @return String in the format "lat=1.23456 deg, lon=2.34567 deg"
253 */
254 public String toDisplayString() {
255 NumberFormat nf = NumberFormat.getInstance();
256 nf.setMaximumFractionDigits(5);
257 return "lat=" + nf.format(lat()) + "\u00B0, lon=" + nf.format(lon()) + '\u00B0';
258 }
259
260 /**
261 * Interpolate between this and a other latlon. If you don't care about the return type, use {@link ILatLon#interpolate(ILatLon, double)}
262 * instead.
263 * @param ll2 The other lat/lon object
264 * @param proportion The proportion to interpolate
265 * @return a new latlon at this position if proportion is 0, at the other position it proportion is 1 and linearly interpolated otherwise.
266 */
267 public LatLon interpolate(LatLon ll2, double proportion) {
268 ILatLon interpolated = ILatLon.super.interpolate(ll2, proportion);
269 if (interpolated instanceof LatLon) {
270 return (LatLon) interpolated;
271 }
272 return new LatLon(interpolated);
273 }
274
275 /**
276 * Get the center between two lat/lon points
277 * @param ll2 The other {@link LatLon}
278 * @return The center at the average coordinates of the two points. Does not take the 180° meridian into account.
279 */
280 public LatLon getCenter(LatLon ll2) {
281 // The JIT will inline this for us, it is as fast as the normal /2 approach
282 return interpolate(ll2, .5);
283 }
284
285 /**
286 * Returns the euclidean distance from this {@code LatLon} to a specified {@code LatLon}.
287 *
288 * @param ll the specified coordinate to be measured against this {@code LatLon}
289 * @return the euclidean distance from this {@code LatLon} to a specified {@code LatLon}
290 * @since 6166
291 */
292 public double distance(final LatLon ll) {
293 return super.distance(ll);
294 }
295
296 /**
297 * Returns the square of the euclidean distance from this {@code LatLon} to a specified {@code LatLon}.
298 *
299 * @param ll the specified coordinate to be measured against this {@code LatLon}
300 * @return the square of the euclidean distance from this {@code LatLon} to a specified {@code LatLon}
301 * @since 6166
302 */
303 public double distanceSq(final LatLon ll) {
304 return super.distanceSq(ll);
305 }
306
307 @Override
308 public String toString() {
309 return "LatLon[lat="+lat()+",lon="+lon()+']';
310 }
311
312 /**
313 * Returns the value rounded to OSM precisions, i.e. to {@link #MAX_SERVER_PRECISION}.
314 * @param value lat/lon value
315 *
316 * @return rounded value
317 */
318 public static double roundToOsmPrecision(double value) {
319 return Math.round(value * MAX_SERVER_INV_PRECISION) / MAX_SERVER_INV_PRECISION;
320 }
321
322 /**
323 * Replies a clone of this lat LatLon, rounded to OSM precisions, i.e. to {@link #MAX_SERVER_PRECISION}
324 *
325 * @return a clone of this lat LatLon
326 */
327 public LatLon getRoundedToOsmPrecision() {
328 return new LatLon(
329 roundToOsmPrecision(lat()),
330 roundToOsmPrecision(lon())
331 );
332 }
333
334 @Override
335 public int hashCode() {
336 return Objects.hash(x, y);
337 }
338
339 @Override
340 public boolean equals(Object obj) {
341 if (this == obj) return true;
342 if (obj == null || getClass() != obj.getClass()) return false;
343 LatLon that = (LatLon) obj;
344 return Double.compare(that.x, x) == 0 &&
345 Double.compare(that.y, y) == 0;
346 }
347}
Note: See TracBrowser for help on using the repository browser.