1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.locationtech.spatial4j.distance;
21
22 import org.locationtech.spatial4j.context.SpatialContext;
23 import org.locationtech.spatial4j.shape.Point;
24 import org.locationtech.spatial4j.shape.Rectangle;
25
26
27
28
29
30
31
32
33
34
35
36 public class DistanceUtils {
37
38
39 @Deprecated
40 public static final double DEG_45_AS_RADS = Math.PI / 4;
41 @Deprecated
42 public static final double SIN_45_AS_RADS = Math.sin(DEG_45_AS_RADS);
43
44 public static final double DEG_90_AS_RADS = Math.PI / 2;
45 public static final double DEG_180_AS_RADS = Math.PI;
46 @Deprecated
47 public static final double DEG_225_AS_RADS = 5 * DEG_45_AS_RADS;
48 @Deprecated
49 public static final double DEG_270_AS_RADS = 3 * DEG_90_AS_RADS;
50
51 public static final double DEGREES_TO_RADIANS = Math.PI / 180;
52 public static final double RADIANS_TO_DEGREES = 1 / DEGREES_TO_RADIANS;
53
54 public static final double KM_TO_MILES = 0.621371192;
55 public static final double MILES_TO_KM = 1 / KM_TO_MILES;
56
57
58
59
60
61
62 public static final double EARTH_MEAN_RADIUS_KM = 6371.0087714;
63 public static final double EARTH_EQUATORIAL_RADIUS_KM = 6378.1370;
64
65
66 public static final double DEG_TO_KM = DEGREES_TO_RADIANS * EARTH_MEAN_RADIUS_KM;
67 public static final double KM_TO_DEG = 1 / DEG_TO_KM;
68
69 public static final double EARTH_MEAN_RADIUS_MI = EARTH_MEAN_RADIUS_KM * KM_TO_MILES;
70 public static final double EARTH_EQUATORIAL_RADIUS_MI = EARTH_EQUATORIAL_RADIUS_KM * KM_TO_MILES;
71
72 private DistanceUtils() {}
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87 @Deprecated
88 public static double vectorDistance(double[] vec1, double[] vec2, double power) {
89
90 double oneOverPower = (power == 0 || power == 1.0 || power == 2.0) ? Double.NaN : 1.0 / power;
91 return vectorDistance(vec1, vec2, power, oneOverPower);
92 }
93
94
95
96
97
98
99
100
101
102
103
104 @Deprecated
105 public static double vectorDistance(double[] vec1, double[] vec2, double power, double oneOverPower) {
106 double result = 0;
107
108 if (power == 0) {
109 for (int i = 0; i < vec1.length; i++) {
110 result += vec1[i] - vec2[i] == 0 ? 0 : 1;
111 }
112 } else if (power == 1.0) {
113 for (int i = 0; i < vec1.length; i++) {
114 result += Math.abs(vec1[i] - vec2[i]);
115 }
116 } else if (power == 2.0) {
117 result = Math.sqrt(distSquaredCartesian(vec1, vec2));
118 } else if (power == Integer.MAX_VALUE || Double.isInfinite(power)) {
119 for (int i = 0; i < vec1.length; i++) {
120 result = Math.max(result, Math.max(vec1[i], vec2[i]));
121 }
122 } else {
123 for (int i = 0; i < vec1.length; i++) {
124 result += Math.pow(vec1[i] - vec2[i], power);
125 }
126 result = Math.pow(result, oneOverPower);
127 }
128 return result;
129 }
130
131
132
133
134
135
136
137
138
139
140
141 @Deprecated
142 public static double[] vectorBoxCorner(double[] center, double[] result, double distance, boolean upperRight) {
143 if (result == null || result.length != center.length) {
144 result = new double[center.length];
145 }
146 if (upperRight == false) {
147 distance = -distance;
148 }
149
150
151
152 distance = SIN_45_AS_RADS * distance;
153 for (int i = 0; i < center.length; i++) {
154 result[i] = center[i] + distance;
155 }
156 return result;
157 }
158
159
160
161
162
163
164
165
166
167
168
169 public static Point pointOnBearingRAD(double startLat, double startLon, double distanceRAD, double bearingRAD, SpatialContext ctx, Point reuse) {
170
171
172
173
174 double cosAngDist = Math.cos(distanceRAD);
175 double cosStartLat = Math.cos(startLat);
176 double sinAngDist = Math.sin(distanceRAD);
177 double sinStartLat = Math.sin(startLat);
178 double sinLat2 = sinStartLat * cosAngDist +
179 cosStartLat * sinAngDist * Math.cos(bearingRAD);
180 double lat2 = Math.asin(sinLat2);
181 double lon2 = startLon + Math.atan2(Math.sin(bearingRAD) * sinAngDist * cosStartLat,
182 cosAngDist - sinStartLat * sinLat2);
183
184
185 if (lon2 > DEG_180_AS_RADS) {
186 lon2 = -1.0 * (DEG_180_AS_RADS - (lon2 - DEG_180_AS_RADS));
187 } else if (lon2 < -DEG_180_AS_RADS) {
188 lon2 = (lon2 + DEG_180_AS_RADS) + DEG_180_AS_RADS;
189 }
190
191
192 if (lat2 > DEG_90_AS_RADS) {
193 lat2 = DEG_90_AS_RADS - (lat2 - DEG_90_AS_RADS);
194 if (lon2 < 0) {
195 lon2 = lon2 + DEG_180_AS_RADS;
196 } else {
197 lon2 = lon2 - DEG_180_AS_RADS;
198 }
199 } else if (lat2 < -DEG_90_AS_RADS) {
200 lat2 = -DEG_90_AS_RADS - (lat2 + DEG_90_AS_RADS);
201 if (lon2 < 0) {
202 lon2 = lon2 + DEG_180_AS_RADS;
203 } else {
204 lon2 = lon2 - DEG_180_AS_RADS;
205 }
206 }
207
208 if (reuse == null) {
209 return ctx.makePoint(lon2, lat2);
210 } else {
211 reuse.reset(lon2, lat2);
212 return reuse;
213 }
214 }
215
216
217
218
219 public static double normLonDEG(double lon_deg) {
220 if (lon_deg >= -180 && lon_deg <= 180)
221 return lon_deg;
222 double off = (lon_deg + 180) % 360;
223 if (off < 0)
224 return 180 + off;
225 else if (off == 0 && lon_deg > 0)
226 return 180;
227 else
228 return -180 + off;
229 }
230
231
232
233
234 public static double normLatDEG(double lat_deg) {
235 if (lat_deg >= -90 && lat_deg <= 90)
236 return lat_deg;
237 double off = Math.abs((lat_deg + 90) % 360);
238 return (off <= 180 ? off : 360-off) - 90;
239 }
240
241
242
243
244
245
246 public static Rectangle calcBoxByDistFromPtDEG(double lat, double lon, double distDEG, SpatialContext ctx, Rectangle reuse) {
247
248 double minX; double maxX; double minY; double maxY;
249 if (distDEG == 0) {
250 minX = lon; maxX = lon; minY = lat; maxY = lat;
251 } else if (distDEG >= 180) {
252 minX = -180; maxX = 180; minY = -90; maxY = 90;
253 } else {
254
255
256 maxY = lat + distDEG;
257 minY = lat - distDEG;
258
259 if (maxY >= 90 || minY <= -90) {
260
261 minX = -180; maxX = 180;
262 if (maxY <= 90 && minY >= -90) {
263 minX = normLonDEG(lon - 90);
264 maxX = normLonDEG(lon + 90);
265 }
266 if (maxY > 90)
267 maxY = 90;
268 if (minY < -90)
269 minY = -90;
270 } else {
271
272 double lon_delta_deg = calcBoxByDistFromPt_deltaLonDEG(lat, lon, distDEG);
273
274 minX = normLonDEG(lon - lon_delta_deg);
275 maxX = normLonDEG(lon + lon_delta_deg);
276 }
277 }
278 if (reuse == null) {
279 return ctx.makeRectangle(minX, maxX, minY, maxY);
280 } else {
281 reuse.reset(minX, maxX, minY, maxY);
282 return reuse;
283 }
284 }
285
286
287
288
289
290 public static double calcBoxByDistFromPt_deltaLonDEG(double lat, double lon, double distDEG) {
291
292 if (distDEG == 0)
293 return 0;
294 double lat_rad = toRadians(lat);
295 double dist_rad = toRadians(distDEG);
296 double result_rad = Math.asin(Math.sin(dist_rad) / Math.cos(lat_rad));
297
298 if (!Double.isNaN(result_rad))
299 return toDegrees(result_rad);
300 return 90;
301 }
302
303
304
305
306
307
308
309
310 public static double calcBoxByDistFromPt_latHorizAxisDEG(double lat, double lon, double distDEG) {
311
312 if (distDEG == 0)
313 return lat;
314
315
316 else if (lat + distDEG >= 90)
317 return 90;
318 else if (lat - distDEG <= -90)
319 return -90;
320
321 double lat_rad = toRadians(lat);
322 double dist_rad = toRadians(distDEG);
323 double result_rad = Math.asin( Math.sin(lat_rad) / Math.cos(dist_rad));
324 if (!Double.isNaN(result_rad))
325 return toDegrees(result_rad);
326
327 if (lat > 0)
328 return 90;
329 if (lat < 0)
330 return -90;
331 return lat;
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 public static double calcLonDegreesAtLat(double lat, double dist) {
357
358
359
360 double distanceRAD = DistanceUtils.toRadians(dist);
361 double startLat = DistanceUtils.toRadians(lat);
362
363 double cosAngDist = Math.cos(distanceRAD);
364 double cosStartLat = Math.cos(startLat);
365 double sinAngDist = Math.sin(distanceRAD);
366 double sinStartLat = Math.sin(startLat);
367
368 double lonDelta = Math.atan2(sinAngDist * cosStartLat,
369 cosAngDist * (1 - sinStartLat * sinStartLat));
370
371 return DistanceUtils.toDegrees(lonDelta);
372 }
373
374
375
376
377
378
379
380
381
382 @Deprecated
383 public static double distSquaredCartesian(double[] vec1, double[] vec2) {
384 double result = 0;
385 for (int i = 0; i < vec1.length; i++) {
386 double v = vec1[i] - vec2[i];
387 result += v * v;
388 }
389 return result;
390 }
391
392
393
394
395
396
397
398
399
400 public static double distHaversineRAD(double lat1, double lon1, double lat2, double lon2) {
401
402
403
404 if (lat1 == lat2 && lon1 == lon2)
405 return 0.0;
406 double hsinX = Math.sin((lon1 - lon2) * 0.5);
407 double hsinY = Math.sin((lat1 - lat2) * 0.5);
408 double h = hsinY * hsinY +
409 (Math.cos(lat1) * Math.cos(lat2) * hsinX * hsinX);
410 if (h > 1)
411 h = 1;
412 return 2 * Math.atan2(Math.sqrt(h), Math.sqrt(1 - h));
413 }
414
415
416
417
418
419
420
421
422
423
424
425
426 public static double distLawOfCosinesRAD(double lat1, double lon1, double lat2, double lon2) {
427
428 if (lat1 == lat2 && lon1 == lon2)
429 return 0.0;
430
431
432
433 double dLon = lon2 - lon1;
434
435 double cosB = (Math.sin(lat1) * Math.sin(lat2))
436 + (Math.cos(lat1) * Math.cos(lat2) * Math.cos(dLon));
437
438
439 if (cosB < -1.0)
440 return Math.PI;
441 else if (cosB >= 1.0)
442 return 0;
443 else
444 return Math.acos(cosB);
445 }
446
447
448
449
450
451
452
453
454 public static double distVincentyRAD(double lat1, double lon1, double lat2, double lon2) {
455
456 if (lat1 == lat2 && lon1 == lon2)
457 return 0.0;
458
459 double cosLat1 = Math.cos(lat1);
460 double cosLat2 = Math.cos(lat2);
461 double sinLat1 = Math.sin(lat1);
462 double sinLat2 = Math.sin(lat2);
463 double dLon = lon2 - lon1;
464 double cosDLon = Math.cos(dLon);
465 double sinDLon = Math.sin(dLon);
466
467 double a = cosLat2 * sinDLon;
468 double b = cosLat1*sinLat2 - sinLat1*cosLat2*cosDLon;
469 double c = sinLat1*sinLat2 + cosLat1*cosLat2*cosDLon;
470
471 return Math.atan2(Math.sqrt(a*a+b*b),c);
472 }
473
474
475
476
477
478 public static double dist2Degrees(double dist, double radius) {
479 return toDegrees(dist2Radians(dist, radius));
480 }
481
482
483
484
485
486
487 public static double degrees2Dist(double degrees, double radius) {
488 return radians2Dist(toRadians(degrees), radius);
489 }
490
491
492
493
494
495 public static double dist2Radians(double dist, double radius) {
496 return dist / radius;
497 }
498
499
500
501
502
503 public static double radians2Dist(double radians, double radius) {
504 return radians * radius;
505 }
506
507
508
509
510
511 public static double toRadians(double degrees) {
512 return degrees * DEGREES_TO_RADIANS;
513 }
514
515
516
517
518
519 public static double toDegrees(double radians) {
520 return radians * RADIANS_TO_DEGREES;
521 }
522
523 }