JSBSim Flight Dynamics Model  1.2.1 (08 Aug 2024)
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 #include <GeographicLib/Math.hpp>
46 #include <GeographicLib/Geodesic.hpp>
47 
48 #include "FGLocation.h"
49 
50 namespace JSBSim {
51 
52 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
53 CLASS IMPLEMENTATION
54 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
55 
57  : mECLoc(1.0, 0.0, 0.0), mCacheValid(false)
58 {
59  e2 = c = 0.0;
60  a = ec = ec2 = 1.0;
61 
62  mLon = mLat = mRadius = 0.0;
63  mGeodLat = GeodeticAltitude = 0.0;
64 
65  mTl2ec.InitMatrix();
66  mTec2l.InitMatrix();
67 }
68 
69 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
70 
71 FGLocation::FGLocation(double lon, double lat, double radius)
72  : mCacheValid(false)
73 {
74  e2 = c = 0.0;
75  a = ec = ec2 = 1.0;
76 
77  mLon = mLat = mRadius = 0.0;
78  mGeodLat = GeodeticAltitude = 0.0;
79 
80  mTl2ec.InitMatrix();
81  mTec2l.InitMatrix();
82 
83  double sinLat = sin(lat);
84  double cosLat = cos(lat);
85  double sinLon = sin(lon);
86  double cosLon = cos(lon);
87  mECLoc = { radius*cosLat*cosLon,
88  radius*cosLat*sinLon,
89  radius*sinLat };
90 }
91 
92 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
93 
95  : mECLoc(lv), mCacheValid(false)
96 {
97  e2 = c = 0.0;
98  a = ec = ec2 = 1.0;
99 
100  mLon = mLat = mRadius = 0.0;
101  mGeodLat = GeodeticAltitude = 0.0;
102 
103  mTl2ec.InitMatrix();
104  mTec2l.InitMatrix();
105 }
106 
107 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
108 
110  : mECLoc(l.mECLoc), mCacheValid(l.mCacheValid)
111 {
112  a = l.a;
113  e2 = l.e2;
114  c = l.c;
115  ec = l.ec;
116  ec2 = l.ec2;
117  mEllipseSet = l.mEllipseSet;
118 
119  /*ag
120  * if the cache is not valid, all of the following values are unset.
121  * They will be calculated once ComputeDerivedUnconditional is called.
122  * If unset, they may possibly contain NaN and could thus trigger floating
123  * point exceptions.
124  */
125  if (!mCacheValid) return;
126 
127  mLon = l.mLon;
128  mLat = l.mLat;
129  mRadius = l.mRadius;
130 
131  mTl2ec = l.mTl2ec;
132  mTec2l = l.mTec2l;
133 
134  mGeodLat = l.mGeodLat;
135  GeodeticAltitude = l.GeodeticAltitude;
136 }
137 
138 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
139 
141 {
142  mECLoc = l.mECLoc;
143  mCacheValid = l.mCacheValid;
144  mEllipseSet = l.mEllipseSet;
145 
146  a = l.a;
147  e2 = l.e2;
148  c = l.c;
149  ec = l.ec;
150  ec2 = l.ec2;
151 
152  //ag See comment in constructor above
153  if (!mCacheValid) return *this;
154 
155  mLon = l.mLon;
156  mLat = l.mLat;
157  mRadius = l.mRadius;
158 
159  mTl2ec = l.mTl2ec;
160  mTec2l = l.mTec2l;
161 
162  mGeodLat = l.mGeodLat;
163  GeodeticAltitude = l.GeodeticAltitude;
164 
165  return *this;
166 }
167 
168 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
169 
170 void FGLocation::SetLongitude(double longitude)
171 {
172  double rtmp = mECLoc.Magnitude(eX, eY);
173  // Check if we have zero radius.
174  // If so set it to 1, so that we can set a position
175  if (0.0 == mECLoc.Magnitude())
176  rtmp = 1.0;
177 
178  // Fast return if we are on the north or south pole ...
179  if (rtmp == 0.0)
180  return;
181 
182  mCacheValid = false;
183 
184  mECLoc(eX) = rtmp*cos(longitude);
185  mECLoc(eY) = rtmp*sin(longitude);
186 }
187 
188 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
189 
190 void FGLocation::SetLatitude(double latitude)
191 {
192  mCacheValid = false;
193 
194  double r = mECLoc.Magnitude();
195  if (r == 0.0) {
196  mECLoc(eX) = 1.0;
197  r = 1.0;
198  }
199 
200  double rtmp = mECLoc.Magnitude(eX, eY);
201  if (rtmp != 0.0) {
202  double fac = r/rtmp*cos(latitude);
203  mECLoc(eX) *= fac;
204  mECLoc(eY) *= fac;
205  } else {
206  mECLoc(eX) = r*cos(latitude);
207  mECLoc(eY) = 0.0;
208  }
209  mECLoc(eZ) = r*sin(latitude);
210 }
211 
212 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
213 
214 void FGLocation::SetRadius(double radius)
215 {
216  mCacheValid = false;
217 
218  double rold = mECLoc.Magnitude();
219  if (rold == 0.0)
220  mECLoc(eX) = radius;
221  else
222  mECLoc *= radius/rold;
223 }
224 
225 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
226 
227 void FGLocation::SetPosition(double lon, double lat, double radius)
228 {
229  mCacheValid = false;
230 
231  double sinLat = sin(lat);
232  double cosLat = cos(lat);
233  double sinLon = sin(lon);
234  double cosLon = cos(lon);
235 
236  mECLoc = { radius*cosLat*cosLon,
237  radius*cosLat*sinLon,
238  radius*sinLat };
239 }
240 
241 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
242 
243 void FGLocation::SetPositionGeodetic(double lon, double lat, double height)
244 {
245  assert(mEllipseSet);
246  mCacheValid = false;
247 
248  double slat = sin(lat);
249  double clat = cos(lat);
250  double RN = a / sqrt(1.0 - e2*slat*slat);
251 
252  mECLoc(eX) = (RN + height)*clat*cos(lon);
253  mECLoc(eY) = (RN + height)*clat*sin(lon);
254  mECLoc(eZ) = ((1 - e2)*RN + height)*slat;
255 }
256 
257 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
258 
259 void FGLocation::SetEllipse(double semimajor, double semiminor)
260 {
261  mCacheValid = false;
262  mEllipseSet = true;
263 
264  a = semimajor;
265  ec = semiminor/a;
266  ec2 = ec * ec;
267  e2 = 1.0 - ec2;
268  c = a * e2;
269 }
270 
271 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
272 
274 {
275  assert(mEllipseSet);
276  ComputeDerived();
277  double cosLat = cos(mLat);
278  return a*ec/sqrt(1.0-e2*cosLat*cosLat);
279 }
280 
281 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
282 
283 void FGLocation::ComputeDerivedUnconditional(void) const
284 {
285  // The radius is just the Euclidean norm of the vector.
286  mRadius = mECLoc.Magnitude();
287 
288  // The distance of the location to the Z-axis, which is the axis
289  // through the poles.
290  double rxy = mECLoc.Magnitude(eX, eY);
291 
292  // Compute the longitude and its sin/cos values.
293  double sinLon, cosLon;
294  if (rxy == 0.0) {
295  sinLon = 0.0;
296  cosLon = 1.0;
297  mLon = 0.0;
298  } else {
299  sinLon = mECLoc(eY)/rxy;
300  cosLon = mECLoc(eX)/rxy;
301  mLon = atan2(mECLoc(eY), mECLoc(eX));
302  }
303 
304  // Compute the geocentric & geodetic latitudes.
305  double sinLat, cosLat;
306  if (mRadius == 0.0) {
307  mLat = 0.0;
308  sinLat = 0.0;
309  cosLat = 1.0;
310  if (mEllipseSet) {
311  mGeodLat = 0.0;
312  GeodeticAltitude = -a;
313  }
314  }
315  else {
316  mLat = atan2( mECLoc(eZ), rxy );
317 
318  // Calculate the geodetic latitude based on "Transformation from Cartesian to
319  // geodetic coordinates accelerated by Halley's method", Fukushima T. (2006)
320  // Journal of Geodesy, Vol. 79, pp. 689-693
321  // Unlike I. Sofair's method which uses a closed form solution, Fukushima's
322  // method is an iterative method whose convergence is so fast that only one
323  // iteration suffices. In addition, Fukushima's method has a much better
324  // numerical stability over Sofair's method at the North and South poles and
325  // it also gives the correct result for a spherical Earth.
326  if (mEllipseSet) {
327  double s0 = fabs(mECLoc(eZ));
328  double zc = ec * s0;
329  double c0 = ec * rxy;
330  double c02 = c0 * c0;
331  double s02 = s0 * s0;
332  double a02 = c02 + s02;
333  double a0 = sqrt(a02);
334  double a03 = a02 * a0;
335  double s1 = zc*a03 + c*s02*s0;
336  double c1 = rxy*a03 - c*c02*c0;
337  double cs0c0 = c*c0*s0;
338  double b0 = 1.5*cs0c0*((rxy*s0-zc*c0)*a0-cs0c0);
339  s1 = s1*a03-b0*s0;
340  double cc = ec*(c1*a03-b0*c0);
341  mGeodLat = sign(mECLoc(eZ))*atan(s1 / cc);
342  double s12 = s1 * s1;
343  double cc2 = cc * cc;
344  double norm = sqrt(s12 + cc2);
345  cosLat = cc / norm;
346  sinLat = sign(mECLoc(eZ)) * s1 / norm;
347  GeodeticAltitude = (rxy*cc + s0*s1 - a*sqrt(ec2*s12 + cc2)) / norm;
348  }
349  else {
350  sinLat = mECLoc(eZ)/mRadius;
351  cosLat = rxy/mRadius;
352  }
353  }
354 
355  // Compute the transform matrices from and to the earth centered frame.
356  // See Stevens and Lewis, "Aircraft Control and Simulation", Second Edition,
357  // Eqn. 1.4-13, page 40. In Stevens and Lewis notation, this is C_n/e - the
358  // orientation of the navigation (local) frame relative to the ECEF frame,
359  // and a transformation from ECEF to nav (local) frame.
360 
361  mTec2l = { -cosLon*sinLat, -sinLon*sinLat, cosLat,
362  -sinLon , cosLon , 0.0 ,
363  -cosLon*cosLat, -sinLon*cosLat, -sinLat };
364 
365  // In Stevens and Lewis notation, this is C_e/n - the
366  // orientation of the ECEF frame relative to the nav (local) frame,
367  // and a transformation from nav (local) to ECEF frame.
368 
369  mTl2ec = mTec2l.Transposed();
370 
371  // Mark the cached values as valid
372  mCacheValid = true;
373 }
374 
375 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
376 
377 double FGLocation::GetDistanceTo(double target_longitude,
378  double target_latitude) const
379 {
380  assert(mEllipseSet);
381  ComputeDerived();
382  GeographicLib::Geodesic geod(a, 1 - ec);
383  GeographicLib::Math::real distance;
384  geod.Inverse(mGeodLat * radtodeg, mLon * radtodeg, target_latitude * radtodeg,
385  target_longitude * radtodeg, distance);
386 
387  return distance;
388 }
389 
390 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
391 
392 double FGLocation::GetHeadingTo(double target_longitude,
393  double target_latitude) const
394 {
395  assert(mEllipseSet);
396  ComputeDerived();
397  GeographicLib::Geodesic geod(a, 1 - ec);
398  GeographicLib::Math::real heading, azimuth2;
399  geod.Inverse(mGeodLat * radtodeg, mLon * radtodeg, target_latitude * radtodeg,
400  target_longitude * radtodeg, heading, azimuth2);
401 
402  return heading * degtorad;
403 }
404 
405 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
406 
407 } // namespace JSBSim
This class implements a 3 element column vector.
double Magnitude(void) const
Length of the vector.
FGLocation holds an arbitrary location in the Earth centered Earth fixed reference frame (ECEF).
Definition: FGLocation.h:152
void SetEllipse(double semimajor, double semiminor)
Sets the semimajor and semiminor axis lengths for this planet.
Definition: FGLocation.cpp:259
void SetLongitude(double longitude)
Set the longitude.
Definition: FGLocation.cpp:170
const FGLocation & operator=(const FGColumnVector3 &v)
Sets this location via the supplied vector.
Definition: FGLocation.h:384
FGLocation(void)
Default constructor.
Definition: FGLocation.cpp:56
void SetLatitude(double latitude)
Set the GEOCENTRIC latitude.
Definition: FGLocation.cpp:190
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:392
double GetSeaLevelRadius(void) const
Get the sea level radius in feet below the current location.
Definition: FGLocation.cpp:273
void SetRadius(double radius)
Set the distance from the center of the earth.
Definition: FGLocation.cpp:214
void SetPosition(double lon, double lat, double radius)
Sets the longitude, latitude and the distance from the center of the earth.
Definition: FGLocation.cpp:227
void SetPositionGeodetic(double lon, double lat, double height)
Sets the longitude, latitude and the distance above the reference spheroid.
Definition: FGLocation.cpp:243
double GetDistanceTo(double target_longitude, double target_latitude) const
Get the geodetic distance between the current location and a given location.
Definition: FGLocation.cpp:377
FGMatrix33 Transposed(void) const
Transposed matrix.
Definition: FGMatrix33.h:221
void InitMatrix(void)
Initialize the matrix.
Definition: FGMatrix33.cpp:259