1 package net.sf.openrocket.util;
3 import net.sf.openrocket.l10n.Translator;
4 import net.sf.openrocket.startup.Application;
7 * A strategy that performs computations on WorldCoordinates.
9 * The directions of the coordinate is:
12 * positive Z = UPWARDS
14 public enum GeodeticComputationStrategy {
18 * Perform computations using a flat Earth approximation. addCoordinate computes the
19 * location using a direct meters-per-degree scaling and getCoriolisAcceleration always
23 private static final double METERS_PER_DEGREE_LATITUDE = 111325; // "standard figure"
24 private static final double METERS_PER_DEGREE_LONGITUDE_EQUATOR = 111050;
28 public WorldCoordinate addCoordinate(WorldCoordinate location, Coordinate delta) {
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);
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;
38 return new WorldCoordinate(newLat, newLon, newAlt);
42 public Coordinate getCoriolisAcceleration(WorldCoordinate location, Coordinate velocity) {
43 return Coordinate.NUL;
48 * Perform geodetic computations with a spherical Earch approximation.
53 public WorldCoordinate addCoordinate(WorldCoordinate location, Coordinate delta) {
54 double newAlt = location.getAltitude() + delta.z;
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);
60 // Check for zero movement before computing bearing
61 if (MathUtil.equals(d, 0)) {
62 return new WorldCoordinate(location.getLatitudeDeg(), location.getLongitudeDeg(), newAlt);
65 double bearing = Math.atan2(delta.x, delta.y);
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);
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));
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);
82 return new WorldCoordinate(Math.toDegrees(newLat), Math.toDegrees(newLon), newAlt);
86 public Coordinate getCoriolisAcceleration(WorldCoordinate location, Coordinate velocity) {
87 return computeCoriolisAcceleration(location, velocity);
93 * Perform geodetic computations on a WGS84 reference ellipsoid using Vincenty Direct Solution.
98 public WorldCoordinate addCoordinate(WorldCoordinate location, Coordinate delta) {
99 double newAlt = location.getAltitude() + delta.z;
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);
105 // Check for zero movement before computing bearing
106 if (MathUtil.equals(d, 0)) {
107 return new WorldCoordinate(location.getLatitudeDeg(), location.getLongitudeDeg(), newAlt);
110 double bearing = Math.atan(delta.x / delta.y);
112 bearing = bearing + Math.PI;
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);
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);
125 return new WorldCoordinate(Math.toDegrees(newLat), Math.toDegrees(newLon), newAlt);
129 public Coordinate getCoriolisAcceleration(WorldCoordinate location, Coordinate velocity) {
130 return computeCoriolisAcceleration(location, velocity);
135 private static final Translator trans = Application.getTranslator();
137 private static final double PRECISION_LIMIT = 0.5e-13;
141 * Return the name of this geodetic computation method.
143 public String getName() {
144 return trans.get(name().toLowerCase() + ".name");
148 * Return a description of the geodetic computation methods.
150 public String getDescription() {
151 return trans.get(name().toLowerCase() + ".desc");
155 public String toString() {
161 * Add a cartesian movement coordinate to a WorldCoordinate.
163 public abstract WorldCoordinate addCoordinate(WorldCoordinate location, Coordinate delta);
167 * Compute the coriolis acceleration at a specified WorldCoordinate and velocity.
169 public abstract Coordinate getCoriolisAcceleration(WorldCoordinate location, Coordinate velocity);
175 private static Coordinate computeCoriolisAcceleration(WorldCoordinate latlon, Coordinate velocity) {
177 double sinlat = Math.sin(latlon.getLatitudeRad());
178 double coslat = Math.cos(latlon.getLatitudeRad());
180 double v_n = velocity.y;
181 double v_e = -1 * velocity.x;
182 double v_u = velocity.z;
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.
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)
200 // ******************************************************************** //
201 // The Vincenty Direct Solution.
202 // Code from GeoConstants.java, Ian Cameron Smith, GPL
203 // ******************************************************************** //
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.
210 * Programmed for the CDC-6600 by lcdr L. Pfeifer, NGS Rockville MD,
213 * @param glat1 The latitude of the starting point, in radians,
215 * @param glon1 The latitude of the starting point, in radians,
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,
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.
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;
233 double tu = r * Math.sin(glat1) / Math.cos(glat1);
235 double sf = Math.sin(azimuth);
236 double cf = Math.cos(azimuth);
241 baz = Math.atan2(tu, cf) * 2.0;
243 double cu = 1.0 / Math.sqrt(tu * tu + 1.0);
246 double c2a = -sa * sa + 1.0;
248 double x = Math.sqrt((1.0 / r / r - 1.0) * c2a + 1.0) + 1.0;
251 c = (x * x / 4.0 + 1) / c;
252 double d = (0.375 * x * x - 1.0) * x;
253 tu = dist / r / axis / c;
256 double sy, cy, cz, e;
260 cz = Math.cos(baz + y);
261 e = cz * cz * 2.0 - 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);
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;
281 double[] ret = new double[3];