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 }