View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *    http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  // NOTE: we keep the header as it came from ASF; it did not originate in Spatial4j
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   * Various distance calculations and constants. To the extent possible, a {@link
29   * org.locationtech.spatial4j.distance.DistanceCalculator}, retrieved from {@link
30   * org.locationtech.spatial4j.context.SpatialContext#getDistCalc()} should be used in preference to calling
31   * these methods directly.
32   * <p>
33   * This code came from <a href="https://issues.apache.org/jira/browse/LUCENE-1387">Apache
34   * Lucene, LUCENE-1387</a>, which in turn came from "LocalLucene".
35   */
36  public class DistanceUtils {
37  
38    //pre-compute some angles that are commonly used
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;//1.609
56  
57    /**
58     * The International Union of Geodesy and Geophysics says the Earth's mean radius in KM is:
59     *
60     * [1] http://en.wikipedia.org/wiki/Earth_radius
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    /** Equivalent to degrees2Dist(1, EARTH_MEAN_RADIUS_KM) */
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     * Calculate the p-norm (i.e. length) between two vectors.
76     * <p>
77     * See <a href="http://en.wikipedia.org/wiki/Lp_space">Lp space</a>
78     *
79     * @param vec1  The first vector
80     * @param vec2  The second vector
81     * @param power The power (2 for cartesian distance, 1 for manhattan, etc.)
82     * @return The length.
83     *
84     * @see #vectorDistance(double[], double[], double, double)
85     *
86     */
87    @Deprecated
88    public static double vectorDistance(double[] vec1, double[] vec2, double power) {
89      //only calc oneOverPower if it's needed
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     * Calculate the p-norm (i.e. length) between two vectors.
96     *
97     * @param vec1         The first vector
98     * @param vec2         The second vector
99     * @param power        The power (2 for cartesian distance, 1 for manhattan, etc.)
100    * @param oneOverPower If you've pre-calculated oneOverPower and cached it, use this method to save
101    *                     one division operation over {@link #vectorDistance(double[], double[], double)}.
102    * @return The length.
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) { // Manhattan
113       for (int i = 0; i < vec1.length; i++) {
114         result += Math.abs(vec1[i] - vec2[i]);
115       }
116     } else if (power == 2.0) { // Cartesian
117       result = Math.sqrt(distSquaredCartesian(vec1, vec2));
118     } else if (power == Integer.MAX_VALUE || Double.isInfinite(power)) {//infinite norm?
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    * Return the coordinates of a vector that is the corner of a box (upper right or lower left), assuming a Rectangular
133    * coordinate system.  Note, this does not apply for points on a sphere or ellipse (although it could be used as an approximation).
134    *
135    * @param center     The center point
136    * @param result Holds the result, potentially resizing if needed.
137    * @param distance   The d from the center to the corner
138    * @param upperRight If true, return the coords for the upper right corner, else return the lower left.
139    * @return The point, either the upperLeft or the lower right
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     //We don't care about the power here,
150     // b/c we are always in a rectangular coordinate system, so any norm can be used by
151     //using the definition of sine
152     distance = SIN_45_AS_RADS * distance; // sin(Pi/4) == (2^0.5)/2 == opp/hyp == opp/distance, solve for opp, similarly for cosine
153     for (int i = 0; i < center.length; i++) {
154       result[i] = center[i] + distance;
155     }
156     return result;
157   }
158 
159   /**
160    * Given a start point (startLat, startLon), distance, and a bearing on a sphere, return the destination point.
161    *
162    * @param startLat The starting point latitude, in radians
163    * @param startLon The starting point longitude, in radians
164    * @param distanceRAD The distance to travel along the bearing in radians.
165    * @param bearingRAD The bearing, in radians.  North is a 0, moving clockwise till radians(360).
166    * @param reuse A preallocated object to hold the results.
167    * @return The destination point, IN RADIANS.
168    */
169   public static Point pointOnBearingRAD(double startLat, double startLon, double distanceRAD, double bearingRAD, SpatialContext ctx, Point reuse) {
170     /*
171  	  lat2 = asin(sin(lat1)*cos(d/R) + cos(lat1)*sin(d/R)*cos(θ))
172   	lon2 = lon1 + atan2(sin(θ)*sin(d/R)*cos(lat1), cos(d/R)−sin(lat1)*sin(lat2))
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     // normalize lon first
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     // normalize lat - could flip poles
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);//x y
212       return reuse;
213     }
214   }
215 
216   /**
217    * Puts in range -180 &lt;= lon_deg &lt;= +180.
218    */
219   public static double normLonDEG(double lon_deg) {
220     if (lon_deg >= -180 && lon_deg <= 180)
221       return lon_deg;//common case, and avoids slight double precision shifting
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    * Puts in range -90 &lt;= lat_deg &lt;= 90.
233    */
234   public static double normLatDEG(double lat_deg) {
235     if (lat_deg >= -90 && lat_deg <= 90)
236       return lat_deg;//common case, and avoids slight double precision shifting
237     double off = Math.abs((lat_deg + 90) % 360);
238     return (off <= 180 ? off : 360-off) - 90;
239   }
240 
241   /**
242    * Calculates the bounding box of a circle, as specified by its center point
243    * and distance.  <code>reuse</code> is an optional argument to store the
244    * results to avoid object creation.
245    */
246   public static Rectangle calcBoxByDistFromPtDEG(double lat, double lon, double distDEG, SpatialContext ctx, Rectangle reuse) {
247     //See http://janmatuschek.de/LatitudeLongitudeBoundingCoordinates Section 3.1, 3.2 and 3.3
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) {//distance is >= opposite side of the globe
252       minX = -180; maxX = 180; minY = -90; maxY = 90;
253     } else {
254 
255       //--calc latitude bounds
256       maxY = lat + distDEG;
257       minY = lat - distDEG;
258 
259       if (maxY >= 90 || minY <= -90) {//touches either pole
260         //we have special logic for longitude
261         minX = -180; maxX = 180;//world wrap: 360 deg
262         if (maxY <= 90 && minY >= -90) {//doesn't pass either pole: 180 deg
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         //--calc longitude bounds
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    * The delta longitude of a point-distance. In other words, half the width of
288    * the bounding box of a circle.
289    */
290   public static double calcBoxByDistFromPt_deltaLonDEG(double lat, double lon, double distDEG) {
291     //http://gis.stackexchange.com/questions/19221/find-tangent-point-on-circle-furthest-east-or-west
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    * The latitude of the horizontal axis (e.g. left-right line)
305    * of a circle.  The horizontal axis of a circle passes through its furthest
306    * left-most and right-most edges. On a 2D plane, this result is always
307    * <code>from.getY()</code> but, perhaps surprisingly, on a sphere it is going
308    * to be slightly different.
309    */
310   public static double calcBoxByDistFromPt_latHorizAxisDEG(double lat, double lon, double distDEG) {
311     //http://gis.stackexchange.com/questions/19221/find-tangent-point-on-circle-furthest-east-or-west
312     if (distDEG == 0)
313       return lat;
314     // if we don't do this when == 90 or -90, computed result can be (+/-)89.9999 when at pole.
315     //     No biggie but more accurate.
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     //handle NaN (shouldn't happen due to checks earlier)
327     if (lat > 0)
328       return 90;
329     if (lat < 0)
330       return -90;
331     return lat;//0
332   }
333 
334   /**
335    * Calculates the degrees longitude distance at latitude {@code lat} to cover
336    * a distance {@code dist}.
337    * <p>
338    * Used to calculate a new expanded buffer distance to account for skewing
339    * effects for shapes that use the lat-lon space as a 2D plane instead of a
340    * sphere.  The expanded buffer will be sure to cover the intended area, but
341    * the shape is still skewed and so it will cover a larger area.  For latitude
342    * 0 (the equator) the result is the same buffer.  At 60 (or -60) degrees, the
343    * result is twice the buffer, meaning that a shape at 60 degrees is twice as
344    * high as it is wide when projected onto a lat-lon plane even if in the real
345    * world it's equal all around.
346    * <p>
347    * If the result added to abs({@code lat}) is &gt;= 90 degrees, then skewing is
348    * so severe that the caller should consider tossing the shape and
349    * substituting a spherical cap instead.
350    *
351    * @param lat  latitude in degrees
352    * @param dist distance in degrees
353    * @return longitudinal degrees (x delta) at input latitude that is &gt;= dist
354    *         distance. Will be &gt;= dist and &lt;= 90.
355    */
356   public static double calcLonDegreesAtLat(double lat, double dist) {
357     //This code was pulled out of DistanceUtils.pointOnBearingRAD() and
358     // optimized
359     // for bearing = 90 degrees, and so we can get an intermediate calculation.
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    * The square of the cartesian Distance.  Not really a distance, but useful if all that matters is
376    * comparing the result to another one.
377    *
378    * @param vec1 The first point
379    * @param vec2 The second point
380    * @return The squared cartesian distance
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    * @param lat1     The y coordinate of the first point, in radians
395    * @param lon1     The x coordinate of the first point, in radians
396    * @param lat2     The y coordinate of the second point, in radians
397    * @param lon2     The x coordinate of the second point, in radians
398    * @return The distance between the two points, as determined by the Haversine formula, in radians.
399    */
400   public static double distHaversineRAD(double lat1, double lon1, double lat2, double lon2) {
401     //TODO investigate slightly different formula using asin() and min() http://www.movable-type.co.uk/scripts/gis-faq-5.1.html
402 
403     // Check for same position
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)//numeric robustness issue. If we didn't check, the answer would be NaN!
411       h = 1;
412     return 2 * Math.atan2(Math.sqrt(h), Math.sqrt(1 - h));
413   }
414 
415   /**
416    * Calculates the distance between two lat-lon's using the Law of Cosines. Due to numeric conditioning
417    * errors, it is not as accurate as the Haversine formula for small distances.  But with
418    * double precision, it isn't that bad -- <a href="http://www.movable-type.co.uk/scripts/latlong.html">
419    *   allegedly 1 meter</a>.
420    * <p>
421    * See <a href="http://gis.stackexchange.com/questions/4906/why-is-law-of-cosines-more-preferable-than-haversine-when-calculating-distance-b">
422    *  Why is law of cosines more preferable than haversine when calculating distance between two latitude-longitude points?</a>
423    * <p>
424    * The arguments and return value are in radians.
425    */
426   public static double distLawOfCosinesRAD(double lat1, double lon1, double lat2, double lon2) {
427     // Check for same position
428     if (lat1 == lat2 && lon1 == lon2)
429       return 0.0;
430 
431     // Get the longitude difference. Don't need to worry about
432     // crossing dateline since cos(x) = cos(-x)
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     // Find angle subtended (with some bounds checking) in radians
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    * Calculates the great circle distance using the Vincenty Formula, simplified for a spherical model. This formula
449    * is accurate for any pair of points. The equation
450    * was taken from <a href="http://en.wikipedia.org/wiki/Great-circle_distance">Wikipedia</a>.
451    * <p>
452    * The arguments are in radians, and the result is in radians.
453    */
454   public static double distVincentyRAD(double lat1, double lon1, double lat2, double lon2) {
455     // Check for same position
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    * Converts a distance in the units of the radius to degrees (360 degrees are
476    * in a circle). A spherical earth model is assumed.
477    */
478   public static double dist2Degrees(double dist, double radius) {
479     return toDegrees(dist2Radians(dist, radius));
480   }
481 
482   /**
483    * Converts <code>degrees</code> (1/360th of circumference of a circle) into a
484    * distance as measured by the units of the radius.  A spherical earth model
485    * is assumed.
486    */
487   public static double degrees2Dist(double degrees, double radius) {
488     return radians2Dist(toRadians(degrees), radius);
489   }
490 
491   /**
492    * Converts a distance in the units of <code>radius</code> (e.g. kilometers)
493    * to radians (multiples of the radius). A spherical earth model is assumed.
494    */
495   public static double dist2Radians(double dist, double radius) {
496     return dist / radius;
497   }
498 
499   /**
500    * Converts <code>radians</code> (multiples of the <code>radius</code>) to
501    * distance in the units of the radius (e.g. kilometers).
502    */
503   public static double radians2Dist(double radians, double radius) {
504     return radians * radius;
505   }
506 
507   /**
508    * Same as {@link Math#toRadians(double)} but 3x faster (multiply vs. divide).
509    * See CompareRadiansSnippet.java in tests.
510    */
511   public static double toRadians(double degrees) {
512     return degrees * DEGREES_TO_RADIANS;
513   }
514 
515   /**
516    * Same as {@link Math#toDegrees(double)} but 3x faster (multiply vs. divide).
517    * See CompareRadiansSnippet.java in tests.
518    */
519   public static double toDegrees(double radians) {
520     return radians * RADIANS_TO_DEGREES;
521   }
522 
523 }