JSBSim Flight Dynamics Model  1.1.11 (13 Feb 2022)
An Open Source Flight Dynamics and Control Software Library in C++
FGLocation.cpp
1 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2 
3  Module: FGLocation.cpp
4  Author: Jon S. Berndt
5  Date started: 04/04/2004
6  Purpose: Store an arbitrary location on the globe
7 
8  ------- Copyright (C) 1999 Jon S. Berndt (jon@jsbsim.org) ------------------
9  ------- (C) 2004 Mathias Froehlich (Mathias.Froehlich@web.de) ----
10  ------- (C) 2011 Ola Røer Thorsen (ola@silentwings.no) -----------
11 
12  This program is free software; you can redistribute it and/or modify it under
13  the terms of the GNU Lesser General Public License as published by the Free
14  Software Foundation; either version 2 of the License, or (at your option) any
15  later version.
16 
17  This program is distributed in the hope that it will be useful, but WITHOUT
18  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19  FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
20  details.
21 
22  You should have received a copy of the GNU Lesser General Public License along
23  with this program; if not, write to the Free Software Foundation, Inc., 59
24  Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25 
26  Further information about the GNU Lesser General Public License can also be
27  found on the world wide web at http://www.gnu.org.
28 
29 FUNCTIONAL DESCRIPTION
30 ------------------------------------------------------------------------------
31 This class encapsulates an arbitrary position in the globe with its accessors.
32 It has vector properties, so you can add multiply ....
33 
34 HISTORY
35 ------------------------------------------------------------------------------
36 04/04/2004 MF Created
37 11/01/2011 ORT Encapsulated ground callback code in FGLocation and removed
38  it from FGFDMExec.
39 
40 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
41 INCLUDES
42 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
43 
44 #include <cmath>
45 
46 #include "FGLocation.h"
47 
48 namespace JSBSim {
49 
50 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
51 CLASS IMPLEMENTATION
52 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
53 
55  : mECLoc(1.0, 0.0, 0.0), mCacheValid(false)
56 {
57  e2 = c = 0.0;
58  a = ec = ec2 = 1.0;
59 
60  mLon = mLat = mRadius = 0.0;
61  mGeodLat = GeodeticAltitude = 0.0;
62 
63  mTl2ec.InitMatrix();
64  mTec2l.InitMatrix();
65 }
66 
67 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
68 
69 FGLocation::FGLocation(double lon, double lat, double radius)
70  : mCacheValid(false)
71 {
72  e2 = c = 0.0;
73  a = ec = ec2 = 1.0;
74 
75  mLon = mLat = mRadius = 0.0;
76  mGeodLat = GeodeticAltitude = 0.0;
77 
78  mTl2ec.InitMatrix();
79  mTec2l.InitMatrix();
80 
81  double sinLat = sin(lat);
82  double cosLat = cos(lat);
83  double sinLon = sin(lon);
84  double cosLon = cos(lon);
85  mECLoc = { radius*cosLat*cosLon,
86  radius*cosLat*sinLon,
87  radius*sinLat };
88 }
89 
90 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
91 
93  : mECLoc(lv), mCacheValid(false)
94 {
95  e2 = c = 0.0;
96  a = ec = ec2 = 1.0;
97 
98  mLon = mLat = mRadius = 0.0;
99  mGeodLat = GeodeticAltitude = 0.0;
100 
101  mTl2ec.InitMatrix();
102  mTec2l.InitMatrix();
103 }
104 
105 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
106 
108  : mECLoc(l.mECLoc), mCacheValid(l.mCacheValid)
109 {
110  a = l.a;
111  e2 = l.e2;
112  c = l.c;
113  ec = l.ec;
114  ec2 = l.ec2;
115  mEllipseSet = l.mEllipseSet;
116 
117  /*ag
118  * if the cache is not valid, all of the following values are unset.
119  * They will be calculated once ComputeDerivedUnconditional is called.
120  * If unset, they may possibly contain NaN and could thus trigger floating
121  * point exceptions.
122  */
123  if (!mCacheValid) return;
124 
125  mLon = l.mLon;
126  mLat = l.mLat;
127  mRadius = l.mRadius;
128 
129  mTl2ec = l.mTl2ec;
130  mTec2l = l.mTec2l;
131 
132  mGeodLat = l.mGeodLat;
133  GeodeticAltitude = l.GeodeticAltitude;
134 }
135 
136 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
137 
139 {
140  mECLoc = l.mECLoc;
141  mCacheValid = l.mCacheValid;
142  mEllipseSet = l.mEllipseSet;
143 
144  a = l.a;
145  e2 = l.e2;
146  c = l.c;
147  ec = l.ec;
148  ec2 = l.ec2;
149 
150  //ag See comment in constructor above
151  if (!mCacheValid) return *this;
152 
153  mLon = l.mLon;
154  mLat = l.mLat;
155  mRadius = l.mRadius;
156 
157  mTl2ec = l.mTl2ec;
158  mTec2l = l.mTec2l;
159 
160  mGeodLat = l.mGeodLat;
161  GeodeticAltitude = l.GeodeticAltitude;
162 
163  return *this;
164 }
165 
166 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
167 
168 void FGLocation::SetLongitude(double longitude)
169 {
170  double rtmp = mECLoc.Magnitude(eX, eY);
171  // Check if we have zero radius.
172  // If so set it to 1, so that we can set a position
173  if (0.0 == mECLoc.Magnitude())
174  rtmp = 1.0;
175 
176  // Fast return if we are on the north or south pole ...
177  if (rtmp == 0.0)
178  return;
179 
180  mCacheValid = false;
181 
182  mECLoc(eX) = rtmp*cos(longitude);
183  mECLoc(eY) = rtmp*sin(longitude);
184 }
185 
186 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
187 
188 void FGLocation::SetLatitude(double latitude)
189 {
190  mCacheValid = false;
191 
192  double r = mECLoc.Magnitude();
193  if (r == 0.0) {
194  mECLoc(eX) = 1.0;
195  r = 1.0;
196  }
197 
198  double rtmp = mECLoc.Magnitude(eX, eY);
199  if (rtmp != 0.0) {
200  double fac = r/rtmp*cos(latitude);
201  mECLoc(eX) *= fac;
202  mECLoc(eY) *= fac;
203  } else {
204  mECLoc(eX) = r*cos(latitude);
205  mECLoc(eY) = 0.0;
206  }
207  mECLoc(eZ) = r*sin(latitude);
208 }
209 
210 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
211 
212 void FGLocation::SetRadius(double radius)
213 {
214  mCacheValid = false;
215 
216  double rold = mECLoc.Magnitude();
217  if (rold == 0.0)
218  mECLoc(eX) = radius;
219  else
220  mECLoc *= radius/rold;
221 }
222 
223 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
224 
225 void FGLocation::SetPosition(double lon, double lat, double radius)
226 {
227  mCacheValid = false;
228 
229  double sinLat = sin(lat);
230  double cosLat = cos(lat);
231  double sinLon = sin(lon);
232  double cosLon = cos(lon);
233 
234  mECLoc = { radius*cosLat*cosLon,
235  radius*cosLat*sinLon,
236  radius*sinLat };
237 }
238 
239 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
240 
241 void FGLocation::SetPositionGeodetic(double lon, double lat, double height)
242 {
243  assert(mEllipseSet);
244  mCacheValid = false;
245 
246  double slat = sin(lat);
247  double clat = cos(lat);
248  double RN = a / sqrt(1.0 - e2*slat*slat);
249 
250  mECLoc(eX) = (RN + height)*clat*cos(lon);
251  mECLoc(eY) = (RN + height)*clat*sin(lon);
252  mECLoc(eZ) = ((1 - e2)*RN + height)*slat;
253 }
254 
255 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
256 
257 void FGLocation::SetEllipse(double semimajor, double semiminor)
258 {
259  mCacheValid = false;
260  mEllipseSet = true;
261 
262  a = semimajor;
263  ec = semiminor/a;
264  ec2 = ec * ec;
265  e2 = 1.0 - ec2;
266  c = a * e2;
267 }
268 
269 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
270 
272 {
273  assert(mEllipseSet);
274  ComputeDerived();
275  double cosLat = cos(mLat);
276  return a*ec/sqrt(1.0-e2*cosLat*cosLat);
277 }
278 
279 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
280 
281 void FGLocation::ComputeDerivedUnconditional(void) const
282 {
283  // The radius is just the Euclidean norm of the vector.
284  mRadius = mECLoc.Magnitude();
285 
286  // The distance of the location to the Z-axis, which is the axis
287  // through the poles.
288  double rxy = mECLoc.Magnitude(eX, eY);
289 
290  // Compute the longitude and its sin/cos values.
291  double sinLon, cosLon;
292  if (rxy == 0.0) {
293  sinLon = 0.0;
294  cosLon = 1.0;
295  mLon = 0.0;
296  } else {
297  sinLon = mECLoc(eY)/rxy;
298  cosLon = mECLoc(eX)/rxy;
299  mLon = atan2(mECLoc(eY), mECLoc(eX));
300  }
301 
302  // Compute the geocentric & geodetic latitudes.
303  double sinLat, cosLat;
304  if (mRadius == 0.0) {
305  mLat = 0.0;
306  sinLat = 0.0;
307  cosLat = 1.0;
308  if (mEllipseSet) {
309  mGeodLat = 0.0;
310  GeodeticAltitude = -a;
311  }
312  }
313  else {
314  mLat = atan2( mECLoc(eZ), rxy );
315 
316  // Calculate the geodetic latitude based on "Transformation from Cartesian to
317  // geodetic coordinates accelerated by Halley's method", Fukushima T. (2006)
318  // Journal of Geodesy, Vol. 79, pp. 689-693
319  // Unlike I. Sofair's method which uses a closed form solution, Fukushima's
320  // method is an iterative method whose convergence is so fast that only one
321  // iteration suffices. In addition, Fukushima's method has a much better
322  // numerical stability over Sofair's method at the North and South poles and
323  // it also gives the correct result for a spherical Earth.
324  if (mEllipseSet) {
325  double s0 = fabs(mECLoc(eZ));
326  double zc = ec * s0;
327  double c0 = ec * rxy;
328  double c02 = c0 * c0;
329  double s02 = s0 * s0;
330  double a02 = c02 + s02;
331  double a0 = sqrt(a02);
332  double a03 = a02 * a0;
333  double s1 = zc*a03 + c*s02*s0;
334  double c1 = rxy*a03 - c*c02*c0;
335  double cs0c0 = c*c0*s0;
336  double b0 = 1.5*cs0c0*((rxy*s0-zc*c0)*a0-cs0c0);
337  s1 = s1*a03-b0*s0;
338  double cc = ec*(c1*a03-b0*c0);
339  mGeodLat = sign(mECLoc(eZ))*atan(s1 / cc);
340  double s12 = s1 * s1;
341  double cc2 = cc * cc;
342  double norm = sqrt(s12 + cc2);
343  cosLat = cc / norm;
344  sinLat = sign(mECLoc(eZ)) * s1 / norm;
345  GeodeticAltitude = (rxy*cc + s0*s1 - a*sqrt(ec2*s12 + cc2)) / norm;
346  }
347  else {
348  sinLat = mECLoc(eZ)/mRadius;
349  cosLat = rxy/mRadius;
350  }
351  }
352 
353  // Compute the transform matrices from and to the earth centered frame.
354  // See Stevens and Lewis, "Aircraft Control and Simulation", Second Edition,
355  // Eqn. 1.4-13, page 40. In Stevens and Lewis notation, this is C_n/e - the
356  // orientation of the navigation (local) frame relative to the ECEF frame,
357  // and a transformation from ECEF to nav (local) frame.
358 
359  mTec2l = { -cosLon*sinLat, -sinLon*sinLat, cosLat,
360  -sinLon , cosLon , 0.0 ,
361  -cosLon*cosLat, -sinLon*cosLat, -sinLat };
362 
363  // In Stevens and Lewis notation, this is C_e/n - the
364  // orientation of the ECEF frame relative to the nav (local) frame,
365  // and a transformation from nav (local) to ECEF frame.
366 
367  mTl2ec = mTec2l.Transposed();
368 
369  // Mark the cached values as valid
370  mCacheValid = true;
371 }
372 
373 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
374 // The calculations, below, implement the Haversine formulas to calculate
375 // heading and distance to a set of lat/long coordinates from the current
376 // position.
377 //
378 // The basic equations are (lat1, long1 are source positions; lat2
379 // long2 are target positions):
380 //
381 // R = earth’s radius
382 // Δlat = lat2 − lat1
383 // Δlong = long2 − long1
384 //
385 // For the waypoint distance calculation:
386 //
387 // a = sin²(Δlat/2) + cos(lat1)∙cos(lat2)∙sin²(Δlong/2)
388 // c = 2∙atan2(√a, √(1−a))
389 // d = R∙c
390 
391 double FGLocation::GetDistanceTo(double target_longitude,
392  double target_latitude) const
393 {
394  ComputeDerived();
395  double delta_lat_rad = target_latitude - mLat;
396  double delta_lon_rad = target_longitude - mLon;
397 
398  double distance_a = pow(sin(0.5*delta_lat_rad), 2.0)
399  + (cos(mLat) * cos(target_latitude)
400  * (pow(sin(0.5*delta_lon_rad), 2.0)));
401 
402  return 2.0 * GetRadius() * atan2(sqrt(distance_a), sqrt(1.0 - distance_a));
403 }
404 
405 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
406 // The calculations, below, implement the Haversine formulas to calculate
407 // heading and distance to a set of lat/long coordinates from the current
408 // position.
409 //
410 // The basic equations are (lat1, long1 are source positions; lat2
411 // long2 are target positions):
412 //
413 // R = earth’s radius
414 // Δlat = lat2 − lat1
415 // Δlong = long2 − long1
416 //
417 // For the heading angle calculation:
418 //
419 // θ = atan2(sin(Δlong)∙cos(lat2), cos(lat1)∙sin(lat2) − sin(lat1)∙cos(lat2)∙cos(Δlong))
420 
421 double FGLocation::GetHeadingTo(double target_longitude,
422  double target_latitude) const
423 {
424  ComputeDerived();
425  double delta_lon_rad = target_longitude - mLon;
426 
427  double Y = sin(delta_lon_rad) * cos(target_latitude);
428  double X = cos(mLat) * sin(target_latitude)
429  - sin(mLat) * cos(target_latitude) * cos(delta_lon_rad);
430 
431  double heading_to_waypoint_rad = atan2(Y, X);
432  if (heading_to_waypoint_rad < 0) heading_to_waypoint_rad += 2.0*M_PI;
433 
434  return heading_to_waypoint_rad;
435 }
436 
437 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
438 
439 } // namespace JSBSim
JSBSim::FGColumnVector3
This class implements a 3 element column vector.
Definition: FGColumnVector3.h:63
JSBSim::FGLocation
FGLocation holds an arbitrary location in the Earth centered Earth fixed reference frame (ECEF).
Definition: FGLocation.h:151
JSBSim::FGLocation::GetDistanceTo
double GetDistanceTo(double target_longitude, double target_latitude) const
Get the geodetic distance between the current location and a given location.
Definition: FGLocation.cpp:391
JSBSim::FGLocation::SetRadius
void SetRadius(double radius)
Set the distance from the center of the earth.
Definition: FGLocation.cpp:212
JSBSim::FGLocation::operator=
const FGLocation & operator=(const FGColumnVector3 &v)
Sets this location via the supplied vector.
Definition: FGLocation.h:384
JSBSim::FGLocation::GetRadius
double GetRadius() const
Get the distance from the center of the earth in feet.
Definition: FGLocation.h:291
JSBSim::FGColumnVector3::Magnitude
double Magnitude(void) const
Length of the vector.
Definition: FGColumnVector3.cpp:109
JSBSim::FGLocation::SetPositionGeodetic
void SetPositionGeodetic(double lon, double lat, double height)
Sets the longitude, latitude and the distance above the reference spheroid.
Definition: FGLocation.cpp:241
JSBSim::FGMatrix33::Transposed
FGMatrix33 Transposed(void) const
Transposed matrix.
Definition: FGMatrix33.h:221
JSBSim::FGLocation::SetLongitude
void SetLongitude(double longitude)
Set the longitude.
Definition: FGLocation.cpp:168
JSBSim::FGLocation::SetLatitude
void SetLatitude(double latitude)
Set the GEOCENTRIC latitude.
Definition: FGLocation.cpp:188
JSBSim::FGLocation::SetEllipse
void SetEllipse(double semimajor, double semiminor)
Sets the semimajor and semiminor axis lengths for this planet.
Definition: FGLocation.cpp:257
JSBSim::FGLocation::SetPosition
void SetPosition(double lon, double lat, double radius)
Sets the longitude, latitude and the distance from the center of the earth.
Definition: FGLocation.cpp:225
JSBSim::FGLocation::GetSeaLevelRadius
double GetSeaLevelRadius(void) const
Get the sea level radius in feet below the current location.
Definition: FGLocation.cpp:271
JSBSim::FGLocation::FGLocation
FGLocation(void)
Default constructor.
Definition: FGLocation.cpp:54
JSBSim::FGMatrix33::InitMatrix
void InitMatrix(void)
Initialize the matrix.
Definition: FGMatrix33.cpp:259
JSBSim::FGLocation::GetHeadingTo
double GetHeadingTo(double target_longitude, double target_latitude) const
Get the heading that should be followed from the current location to a given location along the short...
Definition: FGLocation.cpp:421