-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathPoint.java.html
More file actions
414 lines (360 loc) · 21.6 KB
/
Copy pathPoint.java.html
File metadata and controls
414 lines (360 loc) · 21.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>Point.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Mapcode Java Library</a> > <a href="index.source.html" class="el_package">com.mapcode</a> > <span class="el_source">Point.java</span></div><h1>Point.java</h1><pre class="source lang-java linenums">/*
* Copyright (C) 2014-2017, Stichting Mapcode Foundation (http://www.mapcode.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mapcode;
import javax.annotation.Nonnull;
import java.util.Arrays;
import java.util.Random;
import static com.mapcode.CheckArgs.checkNonnull;
/**
* This class defines a class for lat/lon points.
*
* Internally, the class implements a fixed-point representation where a coordinate is expressed in
* "fractions", of 1/3.240,000,000,000th of a degree. A double (an IEEE 754-1985 binary64) is just
* sufficient to represent coordinates between -180 and +180 degrees in such fractions.
* However, for applications that use micro-degrees a lot, the implementation below is more efficient.
* It represent the fractions in pairs of integers, the first integer
* representing 1/1,000,000th of degrees, the second representing the remainder.
*/
<span class="pc bpc" id="L35" title="1 of 2 branches missed.">@SuppressWarnings("MagicNumber")</span>
public final class Point {
// Latitude and longitude ranges.
public static final double LON_DEG_MIN = -180.0;
public static final double LON_DEG_MAX = 180.0;
public static final double LAT_DEG_MIN = -90.0;
public static final double LAT_DEG_MAX = 90.0;
// Conversion constants.
public static final int DEG_TO_MICRO_DEG = 1000000;
public static final int MICRO_DEG_90 = 90 * DEG_TO_MICRO_DEG;
public static final int MICRO_DEG_180 = 180 * DEG_TO_MICRO_DEG;
public static final int MICRO_DEG_360 = 360 * DEG_TO_MICRO_DEG;
// Radius of Earth.
public static final double EARTH_RADIUS_X_METERS = 6378137.0;
public static final double EARTH_RADIUS_Y_METERS = 6356752.3;
// Circumference of Earth.
public static final double EARTH_CIRCUMFERENCE_X = EARTH_RADIUS_X_METERS * 2.0 * Math.PI;
public static final double EARTH_CIRCUMFERENCE_Y = EARTH_RADIUS_Y_METERS * 2.0 * Math.PI;
// Meters per degree latitude is fixed. For longitude: use factor * cos(midpoint of two degree latitudes).
public static final double METERS_PER_DEGREE_LAT = EARTH_CIRCUMFERENCE_Y / 360.0;
public static final double METERS_PER_DEGREE_LON_EQUATOR = EARTH_CIRCUMFERENCE_X / 360.0; // * cos(deg(lat)).
/**
* Create a point from lat/lon in degrees (may be precision!)
*
* @param latDeg Latitude in degrees. Range: [-90, 90].
* @param lonDeg Longitude in degrees. Range: [-180, 180).
* @return A defined point.
*/
@Nonnull
public static Point fromDeg(final double latDeg, final double lonDeg) {
<span class="fc" id="L71"> return new Point(latDeg, lonDeg);</span>
}
/**
* Public construction, from integer microdegrees (no loss of precision).
*
* @param latMicroDeg Latitude, in microdegrees.
* @param lonMicroDeg Longitude, in microdegrees.
* @return A defined point.
*/
@Nonnull
public static Point fromMicroDeg(final int latMicroDeg, final int lonMicroDeg) {
<span class="fc" id="L83"> final Point p = new Point();</span>
<span class="fc" id="L84"> p.latMicroDeg = latMicroDeg;</span>
<span class="fc" id="L85"> p.latFractionOnlyDeg = 0;</span>
<span class="fc" id="L86"> p.lonMicroDeg = lonMicroDeg;</span>
<span class="fc" id="L87"> p.lonFractionOnlyDeg = 0;</span>
<span class="fc" id="L88"> p.defined = true;</span>
<span class="fc" id="L89"> return p.wrap();</span>
}
/**
* Get the latitude in degrees (may lose precision).
*
* @return Latitude in degrees. No range is enforced.
*/
public double getLatDeg() {
<span class="pc bpc" id="L98" title="2 of 4 branches missed."> assert defined;</span>
<span class="fc" id="L99"> return (latMicroDeg / MICRODEG_TO_DEG_FACTOR) + (latFractionOnlyDeg / LAT_TO_FRACTIONS_FACTOR);</span>
}
/**
* Get the longitude in degrees (may lose precision).
*
* @return Longitude in degrees. No range is enforced.
*/
public double getLonDeg() {
<span class="pc bpc" id="L108" title="2 of 4 branches missed."> assert defined;</span>
<span class="fc" id="L109"> return (lonMicroDeg / MICRODEG_TO_DEG_FACTOR) + (lonFractionOnlyDeg / LON_TO_FRACTIONS_FACTOR);</span>
}
/**
* Get latitude as micro-degrees. Note that this looses precision beyond microdegrees!
*
* @return floor(Latitude in microdegrees)
*/
public int getLatMicroDeg() {
<span class="pc bpc" id="L118" title="2 of 4 branches missed."> assert defined;</span>
<span class="fc" id="L119"> return latMicroDeg;</span>
}
/**
* Get longitude as micro-degrees. Note that this looses precision beyond microdegrees!
*
* @return floor(Longitude in microdegrees)
*/
public int getLonMicroDeg() {
<span class="pc bpc" id="L128" title="2 of 4 branches missed."> assert defined;</span>
<span class="fc" id="L129"> return lonMicroDeg;</span>
}
/**
* Create a random point, uniformly distributed over the surface of the Earth.
*
* @param randomGenerator Random generator used to create a point.
* @return Random point with uniform distribution over the sphere.
*/
@Nonnull
public static Point fromUniformlyDistributedRandomPoints(@Nonnull final Random randomGenerator) {
<span class="fc" id="L140"> checkNonnull("randomGenerator", randomGenerator);</span>
// Calculate uniformly distributed 3D point on sphere (radius = 1.0):
// http://mathproofs.blogspot.co.il/2005/04/uniform-random-distribution-on-sphere.html
<span class="fc" id="L144"> final double unitRand1 = randomGenerator.nextDouble();</span>
<span class="fc" id="L145"> final double unitRand2 = randomGenerator.nextDouble();</span>
<span class="fc" id="L146"> final double theta0 = (2.0 * Math.PI) * unitRand1;</span>
<span class="fc" id="L147"> final double theta1 = Math.acos(1.0 - (2.0 * unitRand2));</span>
<span class="fc" id="L148"> final double x = Math.sin(theta0) * Math.sin(theta1);</span>
<span class="fc" id="L149"> final double y = Math.cos(theta0) * Math.sin(theta1);</span>
<span class="fc" id="L150"> final double z = Math.cos(theta1);</span>
// Convert Carthesian 3D point into lat/lon (radius = 1.0):
// http://stackoverflow.com/questions/1185408/converting-from-longitude-latitude-to-cartesian-coordinates
<span class="fc" id="L154"> final double latRad = Math.asin(z);</span>
<span class="fc" id="L155"> final double lonRad = Math.atan2(y, x);</span>
// Convert radians to degrees.
<span class="pc bpc" id="L158" title="2 of 4 branches missed."> assert !Double.isNaN(latRad);</span>
<span class="pc bpc" id="L159" title="2 of 4 branches missed."> assert !Double.isNaN(lonRad);</span>
<span class="fc" id="L160"> final double lat = latRad * (180.0 / Math.PI);</span>
<span class="fc" id="L161"> final double lon = lonRad * (180.0 / Math.PI);</span>
<span class="fc" id="L162"> return fromDeg(lat, lon);</span>
}
/**
* Calculate the distance between two points. This algorithm does not take the curvature of the Earth into
* account, so it only works for small distance up to, say 200 km, and not too close to the poles.
*
* @param p1 Point 1.
* @param p2 Point 2.
* @return Straight distance between p1 and p2. Only accurate for small distances up to 200 km.
*/
public static double distanceInMeters(@Nonnull final Point p1, @Nonnull final Point p2) {
<span class="fc" id="L174"> checkNonnull("p1", p1);</span>
<span class="fc" id="L175"> checkNonnull("p2", p2);</span>
final Point from;
final Point to;
<span class="fc bfc" id="L179" title="All 2 branches covered."> if (p1.getLonDeg() <= p2.getLonDeg()) {</span>
<span class="fc" id="L180"> from = p1;</span>
<span class="fc" id="L181"> to = p2;</span>
} else {
<span class="fc" id="L183"> from = p2;</span>
<span class="fc" id="L184"> to = p1;</span>
}
// Calculate mid point of 2 latitudes.
<span class="fc" id="L188"> final double avgLat = (from.getLatDeg() + to.getLatDeg()) / 2.0;</span>
<span class="fc" id="L190"> final double deltaLatDeg = Math.abs(to.getLatDeg() - from.getLatDeg());</span>
<span class="fc" id="L191"> final double deltaLonDeg360 = Math.abs(to.getLonDeg() - from.getLonDeg());</span>
<span class="fc bfc" id="L192" title="All 2 branches covered."> final double deltaLonDeg = ((deltaLonDeg360 <= 180.0) ? deltaLonDeg360 : (360.0 - deltaLonDeg360));</span>
// Meters per longitude is fixed; per latitude requires * cos(avg(lat)).
<span class="fc" id="L195"> final double deltaXMeters = degreesLonToMetersAtLat(deltaLonDeg, avgLat);</span>
<span class="fc" id="L196"> final double deltaYMeters = degreesLatToMeters(deltaLatDeg);</span>
// Calculate length through Earth. This is an approximation, but works fine for short distances.
<span class="fc" id="L200"> return Math.sqrt((deltaXMeters * deltaXMeters) + (deltaYMeters * deltaYMeters));</span>
}
public static double degreesLatToMeters(final double latDegrees) {
<span class="fc" id="L204"> return latDegrees * METERS_PER_DEGREE_LAT;</span>
}
public static double degreesLonToMetersAtLat(final double lonDegrees, final double lat) {
<span class="fc" id="L208"> return lonDegrees * METERS_PER_DEGREE_LON_EQUATOR * Math.cos(Math.toRadians(lat));</span>
}
public static double metersToDegreesLonAtLat(final double eastMeters, final double lat) {
<span class="fc" id="L212"> return (eastMeters / METERS_PER_DEGREE_LON_EQUATOR) / Math.cos(Math.toRadians(lat));</span>
}
@Nonnull
@Override
public String toString() {
<span class="pc bpc" id="L218" title="1 of 2 branches missed."> return defined ? ("(" + getLatDeg() + ", " + getLonDeg() + ')') : "undefined";</span>
}
@SuppressWarnings("NonFinalFieldReferencedInHashCode")
@Override
public int hashCode() {
<span class="nc" id="L224"> return Arrays.hashCode(new Object[]{latMicroDeg, lonMicroDeg, latFractionOnlyDeg, lonFractionOnlyDeg, defined});</span>
}
@SuppressWarnings("NonFinalFieldReferenceInEquals")
@Override
public boolean equals(final Object obj) {
<span class="pc bpc" id="L230" title="1 of 2 branches missed."> if (this == obj) {</span>
<span class="nc" id="L231"> return true;</span>
}
<span class="pc bpc" id="L233" title="1 of 2 branches missed."> if (!(obj instanceof Point)) {</span>
<span class="nc" id="L234"> return false;</span>
}
<span class="fc" id="L236"> final Point that = (Point) obj;</span>
<span class="pc bpc" id="L237" title="5 of 10 branches missed."> return (this.latMicroDeg == that.latMicroDeg) &&</span>
(this.lonMicroDeg == that.lonMicroDeg) &&
(this.latFractionOnlyDeg == that.latFractionOnlyDeg) &&
(this.lonFractionOnlyDeg == that.lonFractionOnlyDeg) &&
(this.defined == that.defined);
}
// -----------------------------------------------------------------------
// (Package) private data and methods.
// -----------------------------------------------------------------------
// Constants to convert between Degrees, MicroDegrees and Fractions
static final double MICRODEG_TO_DEG_FACTOR = 1000000.0;
static final double MAX_PRECISION_FACTOR = 810000.0;
static final double LAT_MICRODEG_TO_FRACTIONS_FACTOR = MAX_PRECISION_FACTOR;
static final double LON_MICRODEG_TO_FRACTIONS_FACTOR = MAX_PRECISION_FACTOR * 4;
static final double LAT_TO_FRACTIONS_FACTOR = MICRODEG_TO_DEG_FACTOR * LAT_MICRODEG_TO_FRACTIONS_FACTOR;
static final double LON_TO_FRACTIONS_FACTOR = MICRODEG_TO_DEG_FACTOR * LON_MICRODEG_TO_FRACTIONS_FACTOR;
private int latMicroDeg; // Whole nr of MICRODEG_TO_DEG_FACTOR.
private int lonMicroDeg; // Whole nr of MICRODEG_TO_DEG_FACTOR.
private int latFractionOnlyDeg; // Whole nr of LAT_TO_FRACTIONS_FACTOR, relative to latMicroDeg.
private int lonFractionOnlyDeg; // Whole nr of LON_TO_FRACTIONS_FACTOR, relative to lonMicroDeg.
/**
* Points can be "undefined" within the mapcode implementation, but never outside of that.
* Any methods creating or setting undefined points must be package private and external
* interfaces must never pass undefined points to callers.
*/
private boolean defined;
/**
* Private constructors.
*/
<span class="fc" id="L271"> private Point() {</span>
<span class="fc" id="L272"> defined = false;</span>
<span class="fc" id="L273"> }</span>
/**
* Public construction, from floating point degrees (potentially lossy).
*/
@SuppressWarnings("NumericCastThatLosesPrecision")
<span class="fc" id="L279"> private Point(final double latDeg, final double lonDeg) {</span>
<span class="fc" id="L281"> double lat = latDeg + 90;</span>
<span class="fc bfc" id="L282" title="All 2 branches covered."> if (lat < 0) {</span>
<span class="fc" id="L283"> lat = 0;</span>
<span class="fc bfc" id="L284" title="All 2 branches covered."> } else if (lat > 180) {</span>
<span class="fc" id="L285"> lat = 180;</span>
}
// Rounding factor.
<span class="fc" id="L289"> final double fractionRounding = 0.1;</span>
// Lat now [0..180].
<span class="fc" id="L292"> lat = lat * LAT_TO_FRACTIONS_FACTOR;</span>
<span class="fc" id="L293"> double latFractionOnly = Math.floor(lat + fractionRounding);</span>
<span class="fc" id="L294"> latMicroDeg = (int) (latFractionOnly / LAT_MICRODEG_TO_FRACTIONS_FACTOR);</span>
<span class="fc" id="L295"> latFractionOnly = latFractionOnly - ((double) latMicroDeg * LAT_MICRODEG_TO_FRACTIONS_FACTOR);</span>
<span class="fc" id="L296"> latFractionOnlyDeg = (int) latFractionOnly;</span>
<span class="fc" id="L297"> latMicroDeg = latMicroDeg - MICRO_DEG_90;</span>
// Math.floor has limited precision for really large values, so we need to limit the lon explicitly.
<span class="fc" id="L300"> double lon = Math.min(360.0, Math.max(0.0, lonDeg - (360.0 * Math.floor(lonDeg / 360.0))));</span>
<span class="fc bfc" id="L301" title="All 2 branches covered."> if (Double.compare(lon, 360.0) == 0) {</span>
<span class="fc" id="L302"> lon = 0.0;</span>
}
// Lon now in [0..360>.
<span class="fc" id="L306"> lon = lon * LON_TO_FRACTIONS_FACTOR;</span>
<span class="fc" id="L307"> double lonFractionOnly = Math.floor(lon + fractionRounding);</span>
<span class="fc" id="L308"> lonMicroDeg = (int) (lonFractionOnly / LON_MICRODEG_TO_FRACTIONS_FACTOR);</span>
<span class="fc" id="L309"> lonFractionOnly = lonFractionOnly - ((double) lonMicroDeg * LON_MICRODEG_TO_FRACTIONS_FACTOR);</span>
<span class="fc" id="L310"> lonFractionOnlyDeg = (int) lonFractionOnly;</span>
// Wrap lonMicroDeg from [0..360> to [-180..180).
<span class="fc bfc" id="L313" title="All 2 branches covered."> if (lonMicroDeg >= MICRO_DEG_180) {</span>
<span class="fc" id="L314"> lonMicroDeg = lonMicroDeg - MICRO_DEG_360;</span>
}
<span class="fc" id="L317"> defined = true;</span>
<span class="fc" id="L318"> }</span>
/**
* Get the the longitude "fractions", which is a whole number of 1/LON_TO_FRACTIONS_FACTOR-th
* degrees versus the millionths of degrees.
*/
int getLonFraction() {
<span class="pc bpc" id="L325" title="2 of 4 branches missed."> assert defined;</span>
<span class="fc" id="L326"> return lonFractionOnlyDeg;</span>
}
/**
* Get the the latitude "fractions", which is a whole number of 1/LAT_TO_FRACTIONS_FACTOR-th
* degrees versus the millionths of degrees
*/
int getLatFraction() {
<span class="pc bpc" id="L334" title="2 of 4 branches missed."> assert defined;</span>
<span class="fc" id="L335"> return latFractionOnlyDeg;</span>
}
/**
* Package private construction, from integer fractions (no loss of precision).
*/
@SuppressWarnings("NumericCastThatLosesPrecision")
@Nonnull
static Point fromLatLonFractions(final double latFraction, final double lonFraction) {
<span class="fc" id="L344"> final Point p = new Point();</span>
<span class="fc" id="L345"> p.latMicroDeg = (int) Math.floor(latFraction / LAT_MICRODEG_TO_FRACTIONS_FACTOR);</span>
<span class="fc" id="L346"> p.latFractionOnlyDeg = (int) (latFraction - (LAT_MICRODEG_TO_FRACTIONS_FACTOR * p.latMicroDeg));</span>
<span class="fc" id="L347"> p.lonMicroDeg = (int) Math.floor(lonFraction / LON_MICRODEG_TO_FRACTIONS_FACTOR);</span>
<span class="fc" id="L348"> p.lonFractionOnlyDeg = (int) (lonFraction - (LON_MICRODEG_TO_FRACTIONS_FACTOR * p.lonMicroDeg));</span>
<span class="fc" id="L349"> p.defined = true;</span>
<span class="fc" id="L350"> return p.wrap();</span>
}
static int degToMicroDeg(final double deg) {
//noinspection NumericCastThatLosesPrecision
<span class="nc" id="L355"> return (int) Math.floor(deg * MICRODEG_TO_DEG_FACTOR);</span>
}
static double microDegToDeg(final int microDeg) {
<span class="nc" id="L359"> return ((double) microDeg) / MICRODEG_TO_DEG_FACTOR;</span>
}
@Nonnull
Point wrap() {
<span class="pc bpc" id="L364" title="1 of 2 branches missed."> if (defined) {</span>
// Cut latitude to [-90, 90].
<span class="pc bpc" id="L366" title="1 of 2 branches missed."> if (latMicroDeg < -MICRO_DEG_90) {</span>
<span class="nc" id="L367"> latMicroDeg = -MICRO_DEG_90;</span>
<span class="nc" id="L368"> latFractionOnlyDeg = 0;</span>
}
<span class="pc bpc" id="L370" title="1 of 2 branches missed."> if (latMicroDeg > MICRO_DEG_90) {</span>
<span class="nc" id="L371"> latMicroDeg = MICRO_DEG_90;</span>
<span class="nc" id="L372"> latFractionOnlyDeg = 0;</span>
}
// Map longitude to [-180, 180). Values outside this range are wrapped to this range.
<span class="fc" id="L375"> lonMicroDeg %= MICRO_DEG_360;</span>
<span class="fc bfc" id="L376" title="All 2 branches covered."> if (lonMicroDeg >= MICRO_DEG_180) {</span>
<span class="fc" id="L377"> lonMicroDeg -= MICRO_DEG_360;</span>
<span class="fc bfc" id="L378" title="All 2 branches covered."> } else if (lonMicroDeg < -MICRO_DEG_180) {</span>
<span class="fc" id="L379"> lonMicroDeg += MICRO_DEG_360;</span>
}
}
<span class="fc" id="L382"> return this;</span>
}
/**
* Create an undefined points. No latitude or longitude can be obtained from it.
* Only within the mapcode implementation points can be undefined, so this methods is package private.
*
* @return Undefined points.
*/
@Nonnull
static Point undefined() {
<span class="fc" id="L393"> return new Point();</span>
}
/**
* Set a point to be undefined, invalidating the latitude and longitude.
* Only within the mapcode implementation points can be undefined, so this methods is package private.
*/
void setUndefined() {
<span class="fc" id="L401"> defined = false;</span>
<span class="fc" id="L402"> }</span>
/**
* Return whether the point is defined or not.
* Only within the mapcode implementation points can be undefined, so this methods is package private.
*
* @return True if defined. If false, no lat/lon is available.
*/
boolean isDefined() {
<span class="fc" id="L411"> return defined;</span>
}
}
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.1.201803210924</span></div></body></html>