1 /* -*-c++-*- */
2 /* osgEarth - Geospatial SDK for OpenSceneGraph
3  * Copyright 2019 Pelican Mapping
4  * http://osgearth.org
5  *
6  * osgEarth is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>
18  */
19 
20 #include <osgEarth/GeoMath>
21 
22 using namespace osgEarth;
23 
24 double
distance(double lat1Rad,double lon1Rad,double lat2Rad,double lon2Rad,double radius)25 GeoMath::distance(double lat1Rad, double lon1Rad, double lat2Rad, double lon2Rad, double radius)
26 {
27     double dLat = (lat2Rad-lat1Rad);
28     double dLon = (lon2Rad-lon1Rad);
29     double a = sin(dLat/2.0) * sin(dLat/2.0) +
30                cos(lat1Rad) *  cos(lat2Rad) *
31                sin(dLon/2.0) * sin(dLon/2.0);
32     double c = 2.0 * atan2(sqrt(a), sqrt(1.0-a));
33     double d = radius * c;
34     return d;
35 }
36 
37 double
distance(const std::vector<osg::Vec3d> & points,double radius)38 GeoMath::distance(const std::vector< osg::Vec3d > &points, double radius)
39 {
40     double length = 0;
41 
42     if (points.size() > 1)
43     {
44         for (unsigned int i = 0; i < points.size()-1; ++i)
45         {
46             const osg::Vec3d& current = points[i];
47             const osg::Vec3d& next    = points[i+1];
48             length += GeoMath::distance(osg::DegreesToRadians(current.y()), osg::DegreesToRadians(current.x()),
49                 osg::DegreesToRadians(next.y()), osg::DegreesToRadians(next.x()), radius);
50         }
51     }
52     return length;
53 }
54 
55 double
distance(const osg::Vec3d & p1,const osg::Vec3d & p2,const SpatialReference * srs)56 GeoMath::distance(const osg::Vec3d& p1, const osg::Vec3d& p2, const SpatialReference* srs )
57 {
58     if ( srs == 0L || srs->isProjected() )
59     {
60         return (p2-p1).length();
61     }
62     else
63     {
64         return distance(
65             osg::DegreesToRadians( p1.y() ), osg::DegreesToRadians( p1.x() ),
66             osg::DegreesToRadians( p2.y() ), osg::DegreesToRadians( p2.x() ),
67             srs->getEllipsoid()->getRadiusEquator() );
68     }
69 }
70 
71 double
bearing(double lat1Rad,double lon1Rad,double lat2Rad,double lon2Rad)72 GeoMath::bearing(double lat1Rad, double lon1Rad,
73                  double lat2Rad, double lon2Rad)
74 {
75     double dLon = (lon2Rad-lon1Rad);
76     double y = sin(dLon) * cos(lat2Rad);
77     double x = cos(lat1Rad)*sin(lat2Rad) - sin(lat1Rad)*cos(lat2Rad)*cos(dLon);
78     double brng = atan2(y, x);
79     return brng;
80 }
81 
82 void
greatCircleMinMaxLatitude(double lat1Rad,double lon1Rad,double lat2Rad,double lon2Rad,double & out_minLatRad,double & out_maxLatRad)83 GeoMath::greatCircleMinMaxLatitude(double lat1Rad, double lon1Rad,
84                                    double lat2Rad, double lon2Rad,
85                                    double& out_minLatRad, double& out_maxLatRad)
86 {
87     out_minLatRad = osg::minimum(lat1Rad, lat2Rad);
88     out_maxLatRad = osg::maximum(lat1Rad, lat2Rad);
89 
90     // apply some spherical trig
91     // http://en.wikipedia.org/wiki/Spherical_trigonometry
92 
93     double a = fabs(bearing(lat1Rad, lon1Rad, lat2Rad, lon2Rad)); // initial azimuth from p1=>p2
94     double b = fabs(bearing(lat2Rad, lon2Rad, lat1Rad, lon1Rad)); // initial azimuth from p2=>p1
95 
96     // form a spherical triangle with the 2 points and the north pole, and
97     // use the law of sines to calculate the point at which the great circle
98     // crosses a meridian (thereby make a right angle and demarking the maximum
99     // latitude of the arc). Test whether that point actually lies between the
100     // two points: the angles made by each point and the north pole must be
101     // less than 90 degrees.
102 
103     double B = osg::PI_2 - lat1Rad;                       // angle between p1 and the pole
104     if ( a < osg::PI_2 && b < osg::PI_2 )
105         out_maxLatRad = osg::maximum( out_maxLatRad, osg::PI_2 - asin(sin(B)*sin(a)) );
106     //out_maxLatRad = a < osg::PI_2 && b < osg::PI_2 ?
107     //    osg::PI_2 - asin( sin(B)*sin(a) ) :
108     //    osg::maximum(lat1Rad,lat2Rad);
109 
110     // flip over to the triangle formed by the south pole:
111     a = osg::PI - a, b = osg::PI - b;
112     B = osg::PI - B; //lat1Rad - (-osg::PI_2);
113 
114     if ( a < osg::PI_2 && b < osg::PI_2 )
115         out_minLatRad = osg::minimum( out_minLatRad, -osg::PI_2 + asin(sin(B)*sin(a)) );
116     //out_minLatRad = a < osg::PI_2 && b < osg::PI_2 ?
117     //    osg::PI_2 - asin( sin(B)*sin(a) ) :
118     //    osg::minimum(lat1Rad,lat2Rad);
119 
120     //OE_INFO
121     //    << "a = " << osg::RadiansToDegrees(a)
122     //    << ", b = " << osg::RadiansToDegrees(b)
123     //    << ", maxLat = " << osg::RadiansToDegrees(out_maxLatRad)
124     //    << ", minLat = " << osg::RadiansToDegrees(out_minLatRad)
125     //    << std::endl;
126 }
127 
128 void
midpoint(double lat1Rad,double lon1Rad,double lat2Rad,double lon2Rad,double & out_latRad,double & out_lonRad)129 GeoMath::midpoint(double lat1Rad, double lon1Rad,
130                   double lat2Rad, double lon2Rad,
131                   double &out_latRad, double &out_lonRad)
132 {
133     double dLon = (lon2Rad-lon1Rad);
134 
135     double cosLat1 = cos(lat1Rad);
136     double cosLat2 = cos(lat2Rad);
137     double sinLat1 = sin(lat1Rad);
138     double sinLat2 = sin(lat2Rad);
139 
140 
141     double Bx = cosLat2 * cos(dLon);
142     double By = cosLat2 * sin(dLon);
143     out_latRad = atan2(sinLat1+sinLat2,
144                        sqrt( (cosLat1+Bx)*(cosLat1+Bx) + By*By) );
145     out_lonRad = lon1Rad + atan2(By, cosLat1 + Bx);
146 }
147 
148 void
destination(double lat1Rad,double lon1Rad,double bearingRad,double distance,double & out_latRad,double & out_lonRad,double radius)149 GeoMath::destination(double lat1Rad, double lon1Rad,
150                      double bearingRad, double distance,
151                      double &out_latRad, double &out_lonRad,
152                      double radius)
153 {
154     double dR = distance / radius;
155     out_latRad = asin( sin(lat1Rad)*cos(dR) +
156                        cos(lat1Rad)*sin(dR)*cos(bearingRad) );
157     out_lonRad = lon1Rad + atan2(sin(bearingRad)*sin(dR)*cos(lat1Rad),
158                                  cos(dR)-sin(lat1Rad)*sin(out_latRad));
159 }
160 
161 double
rhumbDistance(double lat1Rad,double lon1Rad,double lat2Rad,double lon2Rad,double radius)162 GeoMath::rhumbDistance(double lat1Rad, double lon1Rad,
163                        double lat2Rad, double lon2Rad,
164                        double radius)
165 {
166     double dLat = (lat2Rad - lat1Rad);
167     double dLon = osg::absolute(lon2Rad - lon1Rad);
168 
169     double dPhi = log(tan(lat2Rad/2.0+osg::PI/4.0)/tan(lat1Rad/2.0+osg::PI/4.0));
170     bool eastWest = osg::equivalent(dPhi, 0.0);
171     double q = eastWest ? cos(lat1Rad) : dLat/dPhi;
172     // if dLon over 180� take shorter rhumb across 180� meridian:
173     if (dLon > osg::PI) dLon = 2.0*osg::PI - dLon;
174     double dist = sqrt(dLat*dLat + q*q*dLon*dLon) * radius;
175     return dist;
176 }
177 
178 double
rhumbDistance(const std::vector<osg::Vec3d> & points,double radius)179 GeoMath::rhumbDistance(const std::vector< osg::Vec3d > &points, double radius)
180 {
181     double length = 0;
182     if (points.size() > 1)
183     {
184         for (unsigned int i = 0; i < points.size()-1; ++i)
185         {
186             const osg::Vec3d& current = points[i];
187             const osg::Vec3d& next    = points[i+1];
188             length += GeoMath::rhumbDistance(osg::DegreesToRadians(current.y()), osg::DegreesToRadians(current.x()),
189                                              osg::DegreesToRadians(next.y()), osg::DegreesToRadians(next.x()), radius);
190         }
191     }
192     return length;
193 }
194 
195 double
rhumbBearing(double lat1Rad,double lon1Rad,double lat2Rad,double lon2Rad)196 GeoMath::rhumbBearing(double lat1Rad, double lon1Rad,
197                       double lat2Rad, double lon2Rad)
198 {
199   double dLon = lon2Rad - lon1Rad;
200 
201   double dPhi = log(tan(lat2Rad/2.0+osg::PI/4.0)/tan(lat1Rad/2.0+osg::PI/4.0));
202   if (osg::absolute(dLon) > osg::PI) dLon = dLon > 0.0 ? -(2.0*osg::PI-dLon) : (2.0*osg::PI+dLon);
203   double brng = atan2(dLon, dPhi);
204   return fmod(brng + 2.0 * osg::PI, 2.0 * osg::PI);
205 
206 }
207 
208 void
rhumbDestination(double lat1Rad,double lon1Rad,double bearing,double distance,double & out_latRad,double & out_lonRad,double radius)209 GeoMath::rhumbDestination(double lat1Rad, double lon1Rad,
210                           double bearing, double distance,
211                           double &out_latRad, double &out_lonRad,
212                           double radius)
213 {
214   double R = radius;
215   double d = distance / R;
216 
217   double lat2Rad = lat1Rad + d*cos(bearing);
218   double dLat = lat2Rad-lat1Rad;
219   double dPhi = log(tan(lat2Rad/2.0+osg::PI/4.0)/tan(lat1Rad/2.0+osg::PI/4.0));
220   bool eastWestLine = osg::equivalent(dPhi, 0.0);
221   double q = eastWestLine ? cos(lat1Rad) : dLat/dPhi;
222   double dLon = d*sin(bearing)/q;
223   // check for some daft bugger going past the pole
224   if (osg::absolute(lat2Rad) > osg::PI/2.0) lat2Rad = lat2Rad > 0.0 ? osg::PI-lat2Rad : -(osg::PI-lat2Rad);
225   //double lon2Rad = (lon1Rad+dLon+3.0*Math.PI)%(2.0*osg::PI) - osg::PI;
226   double lon2Rad = fmod((lon1Rad+dLon+3.0*osg::PI),(2.0*osg::PI)) - osg::PI;
227 
228   out_latRad = lat2Rad;
229   out_lonRad = lon2Rad;
230 }
231 
232 unsigned
interesectLineWithSphere(const osg::Vec3d & p0,const osg::Vec3d & p1,double R,osg::Vec3d & out_i0,osg::Vec3d & out_i1)233 GeoMath::interesectLineWithSphere(const osg::Vec3d& p0,
234                                   const osg::Vec3d& p1,
235                                   double            R,
236                                   osg::Vec3d&       out_i0,
237                                   osg::Vec3d&       out_i1)
238 {
239     unsigned hits = 0;
240 
241     // http://stackoverflow.com/questions/6533856/ray-sphere-intersection
242 
243     osg::Vec3d d = p1-p0;
244 
245     double A = d * d;
246     double B = 2.0 * (d * p0);
247     double C = (p0 * p0) - R*R;
248 
249     // now solve the quadratic A + B*t + C*t^2 = 0.
250     double D = B*B - 4.0*A*C;
251     if ( D >= 0 )
252     {
253         if ( osg::equivalent(D, 0.0) )
254         {
255             // one root (line is tangent to sphere)
256             double t = -B/(2.0*A);
257             //if (t >= 0.0 && t <= 1.0)
258             {
259                 out_i0 = p0 + d*t;
260                 ++hits;
261             }
262         }
263         else
264         {
265             // two roots (line passes through sphere twice)
266             double sqrtD = sqrt(D);
267             double t0 = (-B + sqrtD)/(2.0*A);
268             double t1 = (-B - sqrtD)/(2.0*A);
269 
270             //if ( t0 >= 0.0 && t0 <= 1.0 )
271             {
272                 out_i0 = p0 + d*t0;
273                 ++hits;
274             }
275 
276             //if ( t1 >= 0.0 && t1 <= 1.0 )
277             {
278                 if (hits == 0)
279                     out_i0 = p0 + d*t1;
280                 else
281                     out_i1 = p0 + d*t1;
282                 ++hits;
283             }
284         }
285     }
286 
287     return hits;
288 }
289 
290 unsigned
intersectLineWithPlane(const osg::Vec3d & p0,const osg::Vec3d & p1,const osg::Plane & plane,osg::Vec3d & out_p)291 GeoMath::intersectLineWithPlane(const osg::Vec3d& p0,
292                                 const osg::Vec3d& p1,
293                                 const osg::Plane& plane,
294                                 osg::Vec3d&       out_p)
295 {
296     osg::Vec3d V = p1-p0;
297     V.normalize();
298     double denom = plane.dotProductNormal(V);
299 
300     // if N*V == 0, line is parallel to the plane
301     if ( osg::equivalent(denom, 0.0) )
302     {
303         // if p0 lies on the plane, line is coincident with plane
304         // and intersections are infinite
305         if ( osg::equivalent(plane.distance(p0), 0.0) )
306         {
307             out_p = p0;
308             return 2;
309         }
310         else
311         {
312             // line does not intersect plane.
313             return 0;
314         }
315     }
316     else
317     {
318         // one intersection:
319         double t = -(plane.dotProductNormal(p0) + plane[3])/denom;
320         out_p = p0 + V*t;
321         return 1;
322     }
323 }
324 
325 bool
isPointVisible(const osg::Vec3d & eye,const osg::Vec3d & target,double R)326 GeoMath::isPointVisible(const osg::Vec3d& eye,
327                         const osg::Vec3d& target,
328                         double            R)
329 {
330     double r2 = R*R;
331 
332     // same quadrant:
333     if ( eye * target >= 0.0 )
334     {
335         double d2 = eye.length2();
336         double horiz2 = d2 - r2;
337         double dist2 = (target-eye).length2();
338         if ( dist2 < horiz2 )
339         {
340             return true;
341         }
342     }
343 
344     // different quadrants:
345     else
346     {
347         // there's a horizon between them; now see if the thing is visible.
348         // find the triangle formed by the viewpoint, the target point, and
349         // the center of the earth.
350         double a = (target-eye).length();
351         double b = target.length();
352         double c = eye.length();
353 
354         // Heron's formula for triangle area:
355         double s = 0.5*(a+b+c);
356         double area = 0.25*sqrt( s*(s-a)*(s-b)*(s-c) );
357 
358         // Get the triangle's height:
359         double h = (2*area)/a;
360 
361         if ( h >= R )
362         {
363             return true;
364         }
365     }
366 
367     return false;
368 }