version 1.1.9
[debian/openrocket] / src / net / sf / openrocket / util / GeodeticComputationStrategy.java
1 package net.sf.openrocket.util;
2
3 import net.sf.openrocket.l10n.Translator;
4 import net.sf.openrocket.startup.Application;
5
6 /**
7  * A strategy that performs computations on WorldCoordinates.
8  * <p>
9  * The directions of the coordinate is:
10  *   positive X = EAST
11  *   positive Y = NORTH
12  *   positive Z = UPWARDS
13  */
14 public enum GeodeticComputationStrategy {
15         
16
17         /**
18          * Perform computations using a flat Earth approximation.  addCoordinate computes the
19          * location using a direct meters-per-degree scaling and getCoriolisAcceleration always
20          * returns NUL.
21          */
22         FLAT {
23                 private static final double METERS_PER_DEGREE_LATITUDE = 111325; // "standard figure"
24                 private static final double METERS_PER_DEGREE_LONGITUDE_EQUATOR = 111050;
25                 
26                 
27                 @Override
28                 public WorldCoordinate addCoordinate(WorldCoordinate location, Coordinate delta) {
29                         
30                         double metersPerDegreeLongitude = METERS_PER_DEGREE_LONGITUDE_EQUATOR * Math.cos(location.getLatitudeRad());
31                         // Limit to 1 meter per degree near poles
32                         metersPerDegreeLongitude = MathUtil.max(metersPerDegreeLongitude, 1);
33                         
34                         double newLat = location.getLatitudeDeg() + delta.y / METERS_PER_DEGREE_LATITUDE;
35                         double newLon = location.getLongitudeDeg() + delta.x / metersPerDegreeLongitude;
36                         double newAlt = location.getAltitude() + delta.z;
37                         
38                         return new WorldCoordinate(newLat, newLon, newAlt);
39                 }
40                 
41                 @Override
42                 public Coordinate getCoriolisAcceleration(WorldCoordinate location, Coordinate velocity) {
43                         return Coordinate.NUL;
44                 }
45         },
46         
47         /**
48          * Perform geodetic computations with a spherical Earch approximation.
49          */
50         SPHERICAL {
51                 
52                 @Override
53                 public WorldCoordinate addCoordinate(WorldCoordinate location, Coordinate delta) {
54                         double newAlt = location.getAltitude() + delta.z;
55                         
56                         // bearing (in radians, clockwise from north);
57                         // d/R is the angular distance (in radians), where d is the distance traveled and R is the earth's radius
58                         double d = MathUtil.hypot(delta.x, delta.y);
59                         
60                         // Check for zero movement before computing bearing
61                         if (MathUtil.equals(d, 0)) {
62                                 return new WorldCoordinate(location.getLatitudeDeg(), location.getLongitudeDeg(), newAlt);
63                         }
64                         
65                         double bearing = Math.atan2(delta.x, delta.y);
66                         
67                         // Calculate the new lat and lon                
68                         double newLat, newLon;
69                         double sinLat = Math.sin(location.getLatitudeRad());
70                         double cosLat = Math.cos(location.getLatitudeRad());
71                         double sinDR = Math.sin(d / WorldCoordinate.REARTH);
72                         double cosDR = Math.cos(d / WorldCoordinate.REARTH);
73                         
74                         newLat = Math.asin(sinLat * cosDR + cosLat * sinDR * Math.cos(bearing));
75                         newLon = location.getLongitudeRad() + Math.atan2(Math.sin(bearing) * sinDR * cosLat, cosDR - sinLat * Math.sin(newLat));
76                         
77                         if (Double.isNaN(newLat) || Double.isNaN(newLon)) {
78                                 throw new BugException("addCoordinate resulted in NaN location:  location=" + location + " delta=" + delta
79                                                 + " newLat=" + newLat + " newLon=" + newLon);
80                         }
81                         
82                         return new WorldCoordinate(Math.toDegrees(newLat), Math.toDegrees(newLon), newAlt);
83                 }
84                 
85                 @Override
86                 public Coordinate getCoriolisAcceleration(WorldCoordinate location, Coordinate velocity) {
87                         return computeCoriolisAcceleration(location, velocity);
88                 }
89                 
90         },
91         
92         /**
93          * Perform geodetic computations on a WGS84 reference ellipsoid using Vincenty Direct Solution.
94          */
95         WGS84 {
96                 
97                 @Override
98                 public WorldCoordinate addCoordinate(WorldCoordinate location, Coordinate delta) {
99                         double newAlt = location.getAltitude() + delta.z;
100                         
101                         // bearing (in radians, clockwise from north);
102                         // d/R is the angular distance (in radians), where d is the distance traveled and R is the earth's radius
103                         double d = MathUtil.hypot(delta.x, delta.y);
104                         
105                         // Check for zero movement before computing bearing
106                         if (MathUtil.equals(d, 0)) {
107                                 return new WorldCoordinate(location.getLatitudeDeg(), location.getLongitudeDeg(), newAlt);
108                         }
109                         
110                         double bearing = Math.atan(delta.x / delta.y);
111                         if (delta.y < 0)
112                                 bearing = bearing + Math.PI;
113                         
114                         // Calculate the new lat and lon                
115                         double newLat, newLon;
116                         double ret[] = dirct1(location.getLatitudeRad(), location.getLongitudeRad(), bearing, d, 6378137, 1.0 / 298.25722210088);
117                         newLat = ret[0];
118                         newLon = ret[1];
119                         
120                         if (Double.isNaN(newLat) || Double.isNaN(newLon)) {
121                                 throw new BugException("addCoordinate resulted in NaN location:  location=" + location + " delta=" + delta
122                                                 + " newLat=" + newLat + " newLon=" + newLon);
123                         }
124                         
125                         return new WorldCoordinate(Math.toDegrees(newLat), Math.toDegrees(newLon), newAlt);
126                 }
127                 
128                 @Override
129                 public Coordinate getCoriolisAcceleration(WorldCoordinate location, Coordinate velocity) {
130                         return computeCoriolisAcceleration(location, velocity);
131                 }
132         };
133         
134
135         private static final Translator trans = Application.getTranslator();
136         
137         private static final double PRECISION_LIMIT = 0.5e-13;
138         
139         
140         /**
141          * Return the name of this geodetic computation method.
142          */
143         public String getName() {
144                 return trans.get(name().toLowerCase() + ".name");
145         }
146         
147         /**
148          * Return a description of the geodetic computation methods.
149          */
150         public String getDescription() {
151                 return trans.get(name().toLowerCase() + ".desc");
152         }
153         
154         @Override
155         public String toString() {
156                 return getName();
157         }
158         
159         
160         /**
161          * Add a cartesian movement coordinate to a WorldCoordinate.
162          */
163         public abstract WorldCoordinate addCoordinate(WorldCoordinate location, Coordinate delta);
164         
165         
166         /**
167          * Compute the coriolis acceleration at a specified WorldCoordinate and velocity.
168          */
169         public abstract Coordinate getCoriolisAcceleration(WorldCoordinate location, Coordinate velocity);
170         
171         
172
173
174
175         private static Coordinate computeCoriolisAcceleration(WorldCoordinate latlon, Coordinate velocity) {
176                 
177                 double sinlat = Math.sin(latlon.getLatitudeRad());
178                 double coslat = Math.cos(latlon.getLatitudeRad());
179                 
180                 double v_n = velocity.y;
181                 double v_e = -1 * velocity.x;
182                 double v_u = velocity.z;
183                 
184                 // Not exactly sure why I have to reverse the x direction, but this gives the precession in the
185                 // correct direction (e.g, flying north in northern hemisphere should cause defection to the east (+ve x))
186                 // All the directions are very confusing because they are tied to the wind direction (to/from?), in which
187                 // +ve x or east according to WorldCoordinate is what everything is relative to.
188                 // The directions of everything need so thought, ideally the wind direction and launch rod should be
189                 // able to be set independently and in terms of bearing with north == +ve y.
190                 
191                 Coordinate coriolis = new Coordinate(2.0 * WorldCoordinate.EROT * (v_n * sinlat - v_u * coslat),
192                                                                                                 2.0 * WorldCoordinate.EROT * (-1.0 * v_e * sinlat),
193                                                                                                 2.0 * WorldCoordinate.EROT * (v_e * coslat)
194                                                                                         );
195                 return coriolis;
196         }
197         
198         
199
200         // ******************************************************************** //
201         // The Vincenty Direct Solution.
202         // Code from GeoConstants.java, Ian Cameron Smith, GPL
203         // ******************************************************************** //
204         
205         /**
206          * Solution of the geodetic direct problem after T. Vincenty.
207          * Modified Rainsford's method with Helmert's elliptical terms.
208          * Effective in any azimuth and at any distance short of antipodal.
209          *
210          * Programmed for the CDC-6600 by lcdr L. Pfeifer, NGS Rockville MD,
211          * 20 Feb 1975.
212          *
213          * @param       glat1           The latitude of the starting point, in radians,
214          *                                              positive north.
215          * @param       glon1           The latitude of the starting point, in radians,
216          *                                              positive east.
217          * @param       azimuth         The azimuth to the desired location, in radians
218          *                                              clockwise from north.
219          * @param       dist            The distance to the desired location, in meters.
220          * @param       axis            The semi-major axis of the reference ellipsoid,
221          *                                              in meters.
222          * @param       flat            The flattening of the reference ellipsoid.
223          * @return                              An array containing the latitude and longitude
224          *                                              of the desired point, in radians, and the
225          *                                              azimuth back from that point to the starting
226          *                                              point, in radians clockwise from north.
227          */
228         private static double[] dirct1(double glat1, double glon1,
229                                                                         double azimuth, double dist,
230                                                                         double axis, double flat) {
231                 double r = 1.0 - flat;
232                 
233                 double tu = r * Math.sin(glat1) / Math.cos(glat1);
234                 
235                 double sf = Math.sin(azimuth);
236                 double cf = Math.cos(azimuth);
237                 
238                 double baz = 0.0;
239                 
240                 if (cf != 0.0)
241                         baz = Math.atan2(tu, cf) * 2.0;
242                 
243                 double cu = 1.0 / Math.sqrt(tu * tu + 1.0);
244                 double su = tu * cu;
245                 double sa = cu * sf;
246                 double c2a = -sa * sa + 1.0;
247                 
248                 double x = Math.sqrt((1.0 / r / r - 1.0) * c2a + 1.0) + 1.0;
249                 x = (x - 2.0) / x;
250                 double c = 1.0 - x;
251                 c = (x * x / 4.0 + 1) / c;
252                 double d = (0.375 * x * x - 1.0) * x;
253                 tu = dist / r / axis / c;
254                 double y = tu;
255                 
256                 double sy, cy, cz, e;
257                 do {
258                         sy = Math.sin(y);
259                         cy = Math.cos(y);
260                         cz = Math.cos(baz + y);
261                         e = cz * cz * 2.0 - 1.0;
262                         
263                         c = y;
264                         x = e * cy;
265                         y = e + e - 1.0;
266                         y = (((sy * sy * 4.0 - 3.0) * y * cz * d / 6.0 + x) *
267                                                                         d / 4.0 - cz) * sy * d + tu;
268                 } while (Math.abs(y - c) > PRECISION_LIMIT);
269                 
270                 baz = cu * cy * cf - su * sy;
271                 c = r * Math.sqrt(sa * sa + baz * baz);
272                 d = su * cy + cu * sy * cf;
273                 double glat2 = Math.atan2(d, c);
274                 c = cu * cy - su * sy * cf;
275                 x = Math.atan2(sy * sf, c);
276                 c = ((-3.0 * c2a + 4.0) * flat + 4.0) * c2a * flat / 16.0;
277                 d = ((e * cy * c + cz) * sy * c + y) * sa;
278                 double glon2 = glon1 + x - (1.0 - c) * d * flat;
279                 baz = Math.atan2(sa, baz) + Math.PI;
280                 
281                 double[] ret = new double[3];
282                 ret[0] = glat2;
283                 ret[1] = glon2;
284                 ret[2] = baz;
285                 return ret;
286         }
287         
288
289 }