geodetic computations
authorplaa <plaa@180e2498-e6e9-4542-8430-84ac67f01cd8>
Sun, 28 Aug 2011 13:14:27 +0000 (13:14 +0000)
committerplaa <plaa@180e2498-e6e9-4542-8430-84ac67f01cd8>
Sun, 28 Aug 2011 13:14:27 +0000 (13:14 +0000)
git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@165 180e2498-e6e9-4542-8430-84ac67f01cd8

21 files changed:
ChangeLog
l10n/messages.properties
src/net/sf/openrocket/gui/adaptors/DoubleModel.java
src/net/sf/openrocket/gui/main/SimulationEditDialog.java
src/net/sf/openrocket/models/gravity/BasicGravityModel.java
src/net/sf/openrocket/models/gravity/GravityModel.java
src/net/sf/openrocket/models/gravity/WGSGravityModel.java [new file with mode: 0644]
src/net/sf/openrocket/simulation/AbstractSimulationStepper.java
src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java
src/net/sf/openrocket/simulation/FlightDataType.java
src/net/sf/openrocket/simulation/RK4SimulationStepper.java
src/net/sf/openrocket/simulation/SimulationConditions.java
src/net/sf/openrocket/simulation/SimulationOptions.java
src/net/sf/openrocket/simulation/SimulationStatus.java
src/net/sf/openrocket/unit/UnitGroup.java
src/net/sf/openrocket/util/GeodeticComputationStrategy.java [new file with mode: 0644]
src/net/sf/openrocket/util/WorldCoordinate.java [new file with mode: 0644]
test/net/sf/openrocket/util/GeodeticComputationStrategyTest.java [new file with mode: 0644]
test/net/sf/openrocket/util/WorldCoordinateTest.java [new file with mode: 0644]
web/html/documentation.html
web/htp/documentation.htp

index 51b08c9ea5d07e565959f552afc662bd97ac5427..334f0c9a1e1fd9d821f6c146235d94d8d2fdd9aa 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+2011-08-28  Richard Graham
+
+       * Patch for geodetic computations + coriolis effect
+
 2011-08-25  Sampo Niskanen
 
        * Released version 1.1.8
index 34e12adb9e69623013afcc3a387fcd7d21cff4c1..49ff997684a2e11ecaccaf1d39c9f12165f89f23 100644 (file)
@@ -306,6 +306,10 @@ simedtdlg.lbl.ttip.Pressure = The atmospheric pressure at the launch site.
 simedtdlg.lbl.Launchsite = Launch site
 simedtdlg.lbl.Latitude = Latitude:
 simedtdlg.lbl.ttip.Latitude = <html>The launch site latitude affects the gravitational pull of Earth.<br>Positive values are on the Northern hemisphere, negative values on the Southern hemisphere.
+
+simedtdlg.lbl.Longitude = Longitude:
+simedtdlg.lbl.ttip.Longitude = <html>Required for weather prediction and elevation models.
+
 simedtdlg.lbl.Altitude = Altitude:
 simedtdlg.lbl.ttip.Altitude = <html>The launch altitude above mean sea level.<br>This affects the position of the rocket in the atmospheric model.
 simedtdlg.border.Launchrod = Launch rod
@@ -324,6 +328,8 @@ simedtdlg.lbl.ExtBarrowman = Extended Barrowman
 simedtdlg.lbl.Simmethod = Simulation method:
 simedtdlg.lbl.ttip.Simmethod1 = <html>The six degree-of-freedom simulator allows the rocket total freedom during flight.<br>
 simedtdlg.lbl.ttip.Simmethod2 = Integration is performed using a 4<sup>th</sup> order Runge-Kutta 4 numerical integration.
+simedtdlg.lbl.GeodeticMethod = Geodetic calculations:
+simedtdlg.lbl.ttip.GeodeticMethodTip = Relate to the calculation of coordinates on the earth.  This also enables coriolis effect computations.
 simedtdlg.lbl.Timestep = Time step:
 simedtdlg.lbl.ttip.Timestep1 = <html>The time between simulation steps.<br>A smaller time step results in a more accurate but slower simulation.<br>
 simedtdlg.lbl.ttip.Timestep2 = The 4<sup>th</sup> order simulation method is quite accurate with a time step of
@@ -345,6 +351,14 @@ simedtdlg.IntensityDesc.High = High
 simedtdlg.IntensityDesc.Veryhigh = Very high
 simedtdlg.IntensityDesc.Extreme = Extreme
 
+GeodeticComputationStrategy.none.name = None
+GeodeticComputationStrategy.none.desc = Perform no geodetic computations.
+GeodeticComputationStrategy.spherical.name = Spherical approximation
+GeodeticComputationStrategy.spherical.desc = <html>Perform geodetic computations assuming a spherical Earth.<br>This is sufficiently accurate for almost all purposes.
+GeodeticComputationStrategy.wgs84.name = WGS84 ellipsoid
+GeodeticComputationStrategy.wgs84.desc = <html>Perform geodetic computations on the WGS84 reference ellipsoid using Vincenty's method.<br>Slower and unnecessary in most cases.
+
+
 
 
 ! Simulation Panel
@@ -1246,6 +1260,9 @@ FlightDataType.TYPE_AIR_PRESSURE = Air pressure
 FlightDataType.TYPE_SPEED_OF_SOUND = Speed of sound
 FlightDataType.TYPE_TIME_STEP = Simulation time step
 FlightDataType.TYPE_COMPUTATION_TIME = Computation time
+FlightDataType.TYPE_LATITUDE = Latitude
+FlightDataType.TYPE_LONGITUDE = Longitude
+FlightDataType.TYPE_CORIOLIS_ACCELERATION = Coriolis acceleration
 
 ! PlotConfiguration
 PlotConfiguration.Verticalmotion = Vertical motion vs. time
index e1fc3071c939c4a5308b92f31d1e63d7563ba379..3b4e0dae208d3e0f1a19bc6dd49a5bf55ceb9405 100644 (file)
@@ -587,7 +587,7 @@ public class DoubleModel implements ChangeListener, ChangeSource, Invalidatable
         * The double value is read and written using the methods "get"/"set" + valueName.
         *  
         * @param source Component whose parameter to use.
-        * @param valueName Name of metods used to get/set the parameter.
+        * @param valueName Name of methods used to get/set the parameter.
         * @param multiplier Value shown by the model is the value from component.getXXX * multiplier
         * @param min Minimum value allowed (in SI units)
         * @param max Maximum value allowed (in SI units)
index 90561b49c96079305f0f2f9c94e1e115496c1281..92c2dac81aebc5a6d9c0a33b2c0c084338417346 100644 (file)
@@ -34,6 +34,7 @@ import net.sf.openrocket.document.Simulation;
 import net.sf.openrocket.gui.SpinnerEditor;
 import net.sf.openrocket.gui.adaptors.BooleanModel;
 import net.sf.openrocket.gui.adaptors.DoubleModel;
+import net.sf.openrocket.gui.adaptors.EnumModel;
 import net.sf.openrocket.gui.adaptors.MotorConfigurationModel;
 import net.sf.openrocket.gui.components.BasicSlider;
 import net.sf.openrocket.gui.components.DescriptionArea;
@@ -47,9 +48,9 @@ import net.sf.openrocket.models.atmosphere.ExtendedISAModel;
 import net.sf.openrocket.rocketcomponent.Configuration;
 import net.sf.openrocket.simulation.FlightData;
 import net.sf.openrocket.simulation.FlightDataBranch;
+import net.sf.openrocket.simulation.FlightDataType;
 import net.sf.openrocket.simulation.RK4SimulationStepper;
 import net.sf.openrocket.simulation.SimulationOptions;
-import net.sf.openrocket.simulation.FlightDataType;
 import net.sf.openrocket.simulation.listeners.SimulationListener;
 import net.sf.openrocket.simulation.listeners.example.CSVSaveListener;
 import net.sf.openrocket.startup.Application;
@@ -57,6 +58,7 @@ import net.sf.openrocket.unit.Unit;
 import net.sf.openrocket.unit.UnitGroup;
 import net.sf.openrocket.util.Chars;
 import net.sf.openrocket.util.GUIUtil;
+import net.sf.openrocket.util.GeodeticComputationStrategy;
 import net.sf.openrocket.util.Icons;
 import net.sf.openrocket.util.Prefs;
 
@@ -73,22 +75,22 @@ import org.jfree.data.xy.XYSeriesCollection;
 
 
 public class SimulationEditDialog extends JDialog {
-
+       
        public static final int DEFAULT = -1;
        public static final int EDIT = 1;
        public static final int PLOT = 2;
        
-       
+
        private final Window parentWindow;
        private final Simulation simulation;
        private final SimulationOptions conditions;
        private final Configuration configuration;
        private static final Translator trans = Application.getTranslator();
-
+       
        
        public SimulationEditDialog(Window parent, Simulation s) {
                this(parent, s, 0);
-       }       
+       }
        
        public SimulationEditDialog(Window parent, Simulation s, int tab) {
                //// Edit simulation
@@ -99,7 +101,7 @@ public class SimulationEditDialog extends JDialog {
                this.conditions = simulation.getOptions();
                configuration = simulation.getConfiguration();
                
-               JPanel mainPanel = new JPanel(new MigLayout("fill","[grow, fill]"));
+               JPanel mainPanel = new JPanel(new MigLayout("fill", "[grow, fill]"));
                
                //// Simulation name:
                mainPanel.add(new JLabel(trans.get("simedtdlg.lbl.Simname") + " "), "span, split 2, shrink");
@@ -109,19 +111,22 @@ public class SimulationEditDialog extends JDialog {
                        public void changedUpdate(DocumentEvent e) {
                                setText();
                        }
+                       
                        @Override
                        public void insertUpdate(DocumentEvent e) {
                                setText();
                        }
+                       
                        @Override
                        public void removeUpdate(DocumentEvent e) {
                                setText();
                        }
+                       
                        private void setText() {
                                String name = field.getText();
                                if (name == null || name.equals(""))
                                        return;
-                               System.out.println("Setting name:"+name);
+                               System.out.println("Setting name:" + name);
                                simulation.setName(name);
                                
                        }
@@ -153,11 +158,11 @@ public class SimulationEditDialog extends JDialog {
                }
                
                mainPanel.add(tabbedPane, "spanx, grow, wrap");
-
                
+
                // Buttons
                mainPanel.add(new JPanel(), "spanx, split, growx");
-
+               
                JButton button;
                //// Run simulation button
                button = new JButton(trans.get("simedtdlg.but.runsimulation"));
@@ -180,7 +185,7 @@ public class SimulationEditDialog extends JDialog {
                });
                mainPanel.add(close, "");
                
-               
+
                this.add(mainPanel);
                this.validate();
                this.pack();
@@ -190,9 +195,9 @@ public class SimulationEditDialog extends JDialog {
        }
        
        
-       
-       
-       
+
+
+
        private JPanel flightConditionsTab() {
                JPanel panel = new JPanel(new MigLayout("fill"));
                JPanel sub;
@@ -220,14 +225,14 @@ public class SimulationEditDialog extends JDialog {
                });
                panel.add(combo, "growx, wrap para");
                
-               
+
                //// Wind settings:  Average wind speed, turbulence intensity, std. deviation
                sub = new JPanel(new MigLayout("fill, gap rel unrel",
-                               "[grow][65lp!][30lp!][75lp!]",""));
+                               "[grow][65lp!][30lp!][75lp!]", ""));
                //// Wind
                sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.lbl.Wind")));
                panel.add(sub, "growx, split 2, aligny 0, flowy, gapright para");
-
+               
 
                // Wind average
                //// Average windspeed:
@@ -237,19 +242,19 @@ public class SimulationEditDialog extends JDialog {
                label.setToolTipText(tip);
                sub.add(label);
                
-               m = new DoubleModel(conditions,"WindSpeedAverage", UnitGroup.UNITS_VELOCITY,0);
+               m = new DoubleModel(conditions, "WindSpeedAverage", UnitGroup.UNITS_VELOCITY, 0);
                
                spin = new JSpinner(m.getSpinnerModel());
                spin.setEditor(new SpinnerEditor(spin));
                spin.setToolTipText(tip);
-               sub.add(spin,"w 65lp!");
+               sub.add(spin, "w 65lp!");
                
                unit = new UnitSelector(m);
                unit.setToolTipText(tip);
-               sub.add(unit,"growx");
+               sub.add(unit, "growx");
                slider = new BasicSlider(m.getSliderModel(0, 10.0));
                slider.setToolTipText(tip);
-               sub.add(slider,"w 75lp, wrap");
+               sub.add(slider, "w 75lp, wrap");
                
 
 
@@ -262,22 +267,22 @@ public class SimulationEditDialog extends JDialog {
                label.setToolTipText(tip);
                sub.add(label);
                
-               m = new DoubleModel(conditions,"WindSpeedDeviation", UnitGroup.UNITS_VELOCITY,0);
-               DoubleModel m2 = new DoubleModel(conditions,"WindSpeedAverage", 0.25, 
-                               UnitGroup.UNITS_COEFFICIENT,0);
+               m = new DoubleModel(conditions, "WindSpeedDeviation", UnitGroup.UNITS_VELOCITY, 0);
+               DoubleModel m2 = new DoubleModel(conditions, "WindSpeedAverage", 0.25,
+                               UnitGroup.UNITS_COEFFICIENT, 0);
                
                spin = new JSpinner(m.getSpinnerModel());
                spin.setEditor(new SpinnerEditor(spin));
                spin.setToolTipText(tip);
-               sub.add(spin,"w 65lp!");
+               sub.add(spin, "w 65lp!");
                
                unit = new UnitSelector(m);
                unit.setToolTipText(tip);
-               sub.add(unit,"growx");
+               sub.add(unit, "growx");
                slider = new BasicSlider(m.getSliderModel(new DoubleModel(0), m2));
                slider.setToolTipText(tip);
-               sub.add(slider,"w 75lp, wrap");
-                               
+               sub.add(slider, "w 75lp, wrap");
+               
 
                // Wind turbulence intensity
                //// Turbulence intensity:
@@ -286,28 +291,28 @@ public class SimulationEditDialog extends JDialog {
                //// Typical values range from 
                //// to
                tip = trans.get("simedtdlg.lbl.ttip.Turbulenceintensity1") +
-                       trans.get("simedtdlg.lbl.ttip.Turbulenceintensity2") + " "+
-                       UnitGroup.UNITS_RELATIVE.getDefaultUnit().toStringUnit(0.05) +
-                       " " + trans.get("simedtdlg.lbl.ttip.Turbulenceintensity3") +" " +
-                       UnitGroup.UNITS_RELATIVE.getDefaultUnit().toStringUnit(0.20) + ".";
+                               trans.get("simedtdlg.lbl.ttip.Turbulenceintensity2") + " " +
+                               UnitGroup.UNITS_RELATIVE.getDefaultUnit().toStringUnit(0.05) +
+                               " " + trans.get("simedtdlg.lbl.ttip.Turbulenceintensity3") + " " +
+                               UnitGroup.UNITS_RELATIVE.getDefaultUnit().toStringUnit(0.20) + ".";
                label.setToolTipText(tip);
                sub.add(label);
                
-               m = new DoubleModel(conditions,"WindTurbulenceIntensity", UnitGroup.UNITS_RELATIVE,0);
+               m = new DoubleModel(conditions, "WindTurbulenceIntensity", UnitGroup.UNITS_RELATIVE, 0);
                
                spin = new JSpinner(m.getSpinnerModel());
                spin.setEditor(new SpinnerEditor(spin));
                spin.setToolTipText(tip);
-               sub.add(spin,"w 65lp!");
+               sub.add(spin, "w 65lp!");
                
                unit = new UnitSelector(m);
                unit.setToolTipText(tip);
-               sub.add(unit,"growx");
+               sub.add(unit, "growx");
                
                final JLabel intensityLabel = new JLabel(
                                getIntensityDescription(conditions.getWindTurbulenceIntensity()));
                intensityLabel.setToolTipText(tip);
-               sub.add(intensityLabel,"w 75lp, wrap");
+               sub.add(intensityLabel, "w 75lp, wrap");
                m.addChangeListener(new ChangeListener() {
                        @Override
                        public void stateChanged(ChangeEvent e) {
@@ -317,17 +322,17 @@ public class SimulationEditDialog extends JDialog {
                });
                
 
-               
-               
-               
+
+
+
                //// Temperature and pressure
                sub = new JPanel(new MigLayout("fill, gap rel unrel",
-                               "[grow][65lp!][30lp!][75lp!]",""));
+                               "[grow][65lp!][30lp!][75lp!]", ""));
                //// Atmospheric conditions
                sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.border.Atmoscond")));
                panel.add(sub, "growx, aligny 0, gapright para");
-
                
+
                BooleanModel isa = new BooleanModel(conditions, "ISAAtmosphere");
                JCheckBox check = new JCheckBox(isa);
                //// Use International Standard Atmosphere
@@ -336,8 +341,8 @@ public class SimulationEditDialog extends JDialog {
                //// <br>This model has a temperature of
                //// and a pressure of
                //// at sea level.
-               check.setToolTipText(trans.get("simedtdlg.checkbox.ttip.InterStdAtmosphere1") +" " + 
-                               UnitGroup.UNITS_TEMPERATURE.toStringUnit(ExtendedISAModel.STANDARD_TEMPERATURE)+
+               check.setToolTipText(trans.get("simedtdlg.checkbox.ttip.InterStdAtmosphere1") + " " +
+                               UnitGroup.UNITS_TEMPERATURE.toStringUnit(ExtendedISAModel.STANDARD_TEMPERATURE) +
                                " " + trans.get("simedtdlg.checkbox.ttip.InterStdAtmosphere2") + " " +
                                UnitGroup.UNITS_PRESSURE.toStringUnit(ExtendedISAModel.STANDARD_PRESSURE) +
                                " " + trans.get("simedtdlg.checkbox.ttip.InterStdAtmosphere3"));
@@ -351,25 +356,25 @@ public class SimulationEditDialog extends JDialog {
                isa.addEnableComponent(label, false);
                sub.add(label);
                
-               m = new DoubleModel(conditions,"LaunchTemperature", UnitGroup.UNITS_TEMPERATURE,0);
+               m = new DoubleModel(conditions, "LaunchTemperature", UnitGroup.UNITS_TEMPERATURE, 0);
                
                spin = new JSpinner(m.getSpinnerModel());
                spin.setEditor(new SpinnerEditor(spin));
                spin.setToolTipText(tip);
                isa.addEnableComponent(spin, false);
-               sub.add(spin,"w 65lp!");
+               sub.add(spin, "w 65lp!");
                
                unit = new UnitSelector(m);
                unit.setToolTipText(tip);
                isa.addEnableComponent(unit, false);
-               sub.add(unit,"growx");
-               slider = new BasicSlider(m.getSliderModel(253.15, 308.15));  // -20 ... 35
+               sub.add(unit, "growx");
+               slider = new BasicSlider(m.getSliderModel(253.15, 308.15)); // -20 ... 35
                slider.setToolTipText(tip);
                isa.addEnableComponent(slider, false);
-               sub.add(slider,"w 75lp, wrap");
+               sub.add(slider, "w 75lp, wrap");
                
 
-               
+
                // Pressure:
                label = new JLabel(trans.get("simedtdlg.lbl.Pressure"));
                //// The atmospheric pressure at the launch site.
@@ -378,35 +383,35 @@ public class SimulationEditDialog extends JDialog {
                isa.addEnableComponent(label, false);
                sub.add(label);
                
-               m = new DoubleModel(conditions,"LaunchPressure", UnitGroup.UNITS_PRESSURE,0);
+               m = new DoubleModel(conditions, "LaunchPressure", UnitGroup.UNITS_PRESSURE, 0);
                
                spin = new JSpinner(m.getSpinnerModel());
                spin.setEditor(new SpinnerEditor(spin));
                spin.setToolTipText(tip);
                isa.addEnableComponent(spin, false);
-               sub.add(spin,"w 65lp!");
+               sub.add(spin, "w 65lp!");
                
                unit = new UnitSelector(m);
                unit.setToolTipText(tip);
                isa.addEnableComponent(unit, false);
-               sub.add(unit,"growx");
+               sub.add(unit, "growx");
                slider = new BasicSlider(m.getSliderModel(0.950e5, 1.050e5));
                slider.setToolTipText(tip);
                isa.addEnableComponent(slider, false);
-               sub.add(slider,"w 75lp, wrap");
+               sub.add(slider, "w 75lp, wrap");
                
 
-               
-               
-               
+
+
+
                //// Launch site conditions
                sub = new JPanel(new MigLayout("fill, gap rel unrel",
-                               "[grow][65lp!][30lp!][75lp!]",""));
+                               "[grow][65lp!][30lp!][75lp!]", ""));
                //// Launch site
                sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.lbl.Launchsite")));
                panel.add(sub, "growx, split 2, aligny 0, flowy");
-
                
+
                // Latitude:
                label = new JLabel(trans.get("simedtdlg.lbl.Latitude"));
                //// <html>The launch site latitude affects the gravitational pull of Earth.<br>
@@ -415,22 +420,42 @@ public class SimulationEditDialog extends JDialog {
                label.setToolTipText(tip);
                sub.add(label);
                
-               m = new DoubleModel(conditions,"LaunchLatitude", UnitGroup.UNITS_NONE, -90, 90);
+               m = new DoubleModel(conditions, "LaunchLatitude", UnitGroup.UNITS_NONE, -90, 90);
                
                spin = new JSpinner(m.getSpinnerModel());
                spin.setEditor(new SpinnerEditor(spin));
                spin.setToolTipText(tip);
-               sub.add(spin,"w 65lp!");
+               sub.add(spin, "w 65lp!");
                
                label = new JLabel(Chars.DEGREE + " N");
                label.setToolTipText(tip);
-               sub.add(label,"growx");
+               sub.add(label, "growx");
                slider = new BasicSlider(m.getSliderModel(-90, 90));
                slider.setToolTipText(tip);
-               sub.add(slider,"w 75lp, wrap");
+               sub.add(slider, "w 75lp, wrap");
                
 
+               // Longitude:
+               label = new JLabel(trans.get("simedtdlg.lbl.Longitude"));
+               tip = trans.get("simedtdlg.lbl.ttip.Longitude");
+               label.setToolTipText(tip);
+               sub.add(label);
+               
+               m = new DoubleModel(conditions, "LaunchLongitude", UnitGroup.UNITS_NONE, -180, 180);
+               
+               spin = new JSpinner(m.getSpinnerModel());
+               spin.setEditor(new SpinnerEditor(spin));
+               spin.setToolTipText(tip);
+               sub.add(spin, "w 65lp!");
+               
+               label = new JLabel(Chars.DEGREE + " E");
+               label.setToolTipText(tip);
+               sub.add(label, "growx");
+               slider = new BasicSlider(m.getSliderModel(-180, 180));
+               slider.setToolTipText(tip);
+               sub.add(slider, "w 75lp, wrap");
                
+
                // Altitude:
                label = new JLabel(trans.get("simedtdlg.lbl.Altitude"));
                //// <html>The launch altitude above mean sea level.<br> 
@@ -439,32 +464,32 @@ public class SimulationEditDialog extends JDialog {
                label.setToolTipText(tip);
                sub.add(label);
                
-               m = new DoubleModel(conditions,"LaunchAltitude", UnitGroup.UNITS_DISTANCE,0);
+               m = new DoubleModel(conditions, "LaunchAltitude", UnitGroup.UNITS_DISTANCE, 0);
                
                spin = new JSpinner(m.getSpinnerModel());
                spin.setEditor(new SpinnerEditor(spin));
                spin.setToolTipText(tip);
-               sub.add(spin,"w 65lp!");
+               sub.add(spin, "w 65lp!");
                
                unit = new UnitSelector(m);
                unit.setToolTipText(tip);
-               sub.add(unit,"growx");
+               sub.add(unit, "growx");
                slider = new BasicSlider(m.getSliderModel(0, 250, 1000));
                slider.setToolTipText(tip);
-               sub.add(slider,"w 75lp, wrap");
+               sub.add(slider, "w 75lp, wrap");
                
 
-               
 
-               
+
+
                //// Launch rod
                sub = new JPanel(new MigLayout("fill, gap rel unrel",
-                               "[grow][65lp!][30lp!][75lp!]",""));
+                               "[grow][65lp!][30lp!][75lp!]", ""));
                //// Launch rod
                sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.border.Launchrod")));
                panel.add(sub, "growx, aligny 0, wrap");
-
                
+
                // Length:
                label = new JLabel(trans.get("simedtdlg.lbl.Length"));
                //// The length of the launch rod.
@@ -472,22 +497,22 @@ public class SimulationEditDialog extends JDialog {
                label.setToolTipText(tip);
                sub.add(label);
                
-               m = new DoubleModel(conditions,"LaunchRodLength", UnitGroup.UNITS_LENGTH, 0);
+               m = new DoubleModel(conditions, "LaunchRodLength", UnitGroup.UNITS_LENGTH, 0);
                
                spin = new JSpinner(m.getSpinnerModel());
                spin.setEditor(new SpinnerEditor(spin));
                spin.setToolTipText(tip);
-               sub.add(spin,"w 65lp!");
+               sub.add(spin, "w 65lp!");
                
                unit = new UnitSelector(m);
                unit.setToolTipText(tip);
-               sub.add(unit,"growx");
+               sub.add(unit, "growx");
                slider = new BasicSlider(m.getSliderModel(0, 1, 5));
                slider.setToolTipText(tip);
-               sub.add(slider,"w 75lp, wrap");
+               sub.add(slider, "w 75lp, wrap");
                
 
-               
+
                // Angle:
                label = new JLabel(trans.get("simedtdlg.lbl.Angle"));
                //// The angle of the launch rod from vertical.
@@ -495,24 +520,24 @@ public class SimulationEditDialog extends JDialog {
                label.setToolTipText(tip);
                sub.add(label);
                
-               m = new DoubleModel(conditions,"LaunchRodAngle", UnitGroup.UNITS_ANGLE,
+               m = new DoubleModel(conditions, "LaunchRodAngle", UnitGroup.UNITS_ANGLE,
                                0, SimulationOptions.MAX_LAUNCH_ROD_ANGLE);
                
                spin = new JSpinner(m.getSpinnerModel());
                spin.setEditor(new SpinnerEditor(spin));
                spin.setToolTipText(tip);
-               sub.add(spin,"w 65lp!");
+               sub.add(spin, "w 65lp!");
                
                unit = new UnitSelector(m);
                unit.setToolTipText(tip);
-               sub.add(unit,"growx");
-               slider = new BasicSlider(m.getSliderModel(0, Math.PI/9, 
+               sub.add(unit, "growx");
+               slider = new BasicSlider(m.getSliderModel(0, Math.PI / 9,
                                SimulationOptions.MAX_LAUNCH_ROD_ANGLE));
                slider.setToolTipText(tip);
-               sub.add(slider,"w 75lp, wrap");
+               sub.add(slider, "w 75lp, wrap");
                
 
-               
+
                // Direction:
                label = new JLabel(trans.get("simedtdlg.lbl.Direction"));
                //// <html>Direction of the launch rod relative to the wind.<br>
@@ -520,30 +545,30 @@ public class SimulationEditDialog extends JDialog {
                ////  = downwind.
                tip = trans.get("simedtdlg.lbl.ttip.Direction1") +
                                UnitGroup.UNITS_ANGLE.toStringUnit(0) +
-                               " " + trans.get("simedtdlg.lbl.ttip.Direction2") + " "+
+                               " " + trans.get("simedtdlg.lbl.ttip.Direction2") + " " +
                                UnitGroup.UNITS_ANGLE.toStringUnit(Math.PI) +
                                " " + trans.get("simedtdlg.lbl.ttip.Direction3");
                label.setToolTipText(tip);
                sub.add(label);
                
-               m = new DoubleModel(conditions,"LaunchRodDirection", UnitGroup.UNITS_ANGLE,
+               m = new DoubleModel(conditions, "LaunchRodDirection", UnitGroup.UNITS_ANGLE,
                                -Math.PI, Math.PI);
                
                spin = new JSpinner(m.getSpinnerModel());
                spin.setEditor(new SpinnerEditor(spin));
                spin.setToolTipText(tip);
-               sub.add(spin,"w 65lp!");
+               sub.add(spin, "w 65lp!");
                
                unit = new UnitSelector(m);
                unit.setToolTipText(tip);
-               sub.add(unit,"growx");
+               sub.add(unit, "growx");
                slider = new BasicSlider(m.getSliderModel(-Math.PI, Math.PI));
                slider.setToolTipText(tip);
-               sub.add(slider,"w 75lp, wrap");
+               sub.add(slider, "w 75lp, wrap");
                
                return panel;
        }
-
+       
        
        private String getIntensityDescription(double i) {
                if (i < 0.001)
@@ -567,115 +592,105 @@ public class SimulationEditDialog extends JDialog {
                //// Extreme
                return trans.get("simedtdlg.IntensityDesc.Extreme");
        }
-
        
        
+
        private JPanel simulationOptionsTab() {
                JPanel panel = new JPanel(new MigLayout("fill"));
-               JPanel sub;
+               JPanel sub, subsub;
                String tip;
                JLabel label;
                DoubleModel m;
                JSpinner spin;
                UnitSelector unit;
                BasicSlider slider;
-
                
+
                //// Simulation options
                sub = new JPanel(new MigLayout("fill, gap rel unrel",
-                               "[grow][65lp!][30lp!][75lp!]",""));
+                               "[grow][65lp!][30lp!][75lp!]", ""));
                //// Simulator options
                sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.border.Simopt")));
                panel.add(sub, "w 330lp!, growy, aligny 0");
+               
 
+               // Separate panel for computation methods, as they use a different layout
+               subsub = new JPanel(new MigLayout("fill"));
                
-               //  Calculation method
-               //// <html>The Extended Barrowman method calculates aerodynamic forces according <br>
-               //// to the Barrowman equations extended to accommodate more components.
-               tip = trans.get("simedtdlg.lbl.ttip.Calcmethod");
 
                //// Calculation method:
+               tip = trans.get("simedtdlg.lbl.ttip.Calcmethod");
                label = new JLabel(trans.get("simedtdlg.lbl.Calcmethod"));
                label.setToolTipText(tip);
-               sub.add(label, "gaptop unrel, gapright para, spanx, split 2, w 150lp!");
+               subsub.add(label, "gapright para");
                
                //// Extended Barrowman
                label = new JLabel(trans.get("simedtdlg.lbl.ExtBarrowman"));
                label.setToolTipText(tip);
-               sub.add(label, "growx, wrap para");
-               
+               subsub.add(label, "growx, wrap para");
                
+
                //  Simulation method
-               //// <html>The six degree-of-freedom simulator allows the rocket total freedom during flight.<br>
-               //// Integration is performed using a 4<sup>th</sup> order Runge-Kutta 4 numerical integration.
                tip = trans.get("simedtdlg.lbl.ttip.Simmethod1") +
-               trans.get("simedtdlg.lbl.ttip.Simmethod2");
-
-               //// Simulation method:
+                               trans.get("simedtdlg.lbl.ttip.Simmethod2");
                label = new JLabel(trans.get("simedtdlg.lbl.Simmethod"));
                label.setToolTipText(tip);
-               sub.add(label, "gaptop unrel, gapright para, spanx, split 2, w 150lp!");
+               subsub.add(label, "gapright para");
                
                label = new JLabel("6-DOF Runge-Kutta 4");
                label.setToolTipText(tip);
-               sub.add(label, "growx, wrap 35lp");
+               subsub.add(label, "growx, wrap para");
                
+
+               //// Geodetic calculation method:
+               label = new JLabel(trans.get("simedtdlg.lbl.GeodeticMethod"));
+               label.setToolTipText(trans.get("simedtdlg.lbl.ttip.GeodeticMethodTip"));
+               subsub.add(label, "gapright para");
+               
+               EnumModel<GeodeticComputationStrategy> gcsModel = new EnumModel<GeodeticComputationStrategy>(conditions, "GeodeticComputation");
+               final JComboBox gcsCombo = new JComboBox(gcsModel);
+               ActionListener gcsTTipListener = new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               GeodeticComputationStrategy gcs = (GeodeticComputationStrategy) gcsCombo.getSelectedItem();
+                               gcsCombo.setToolTipText(gcs.getDescription());
+                       }
+               };
+               gcsCombo.addActionListener(gcsTTipListener);
+               gcsTTipListener.actionPerformed(null);
+               subsub.add(gcsCombo, "growx, wrap para");
                
-               // Wind average
+               sub.add(subsub, "spanx, wrap para");
+               
+
                //// Time step:
                label = new JLabel(trans.get("simedtdlg.lbl.Timestep"));
-               //// <html>The time between simulation steps.<br>
-               //// A smaller time step results in a more accurate but slower simulation.<br>
-               //// The 4<sup>th</sup> order simulation method is quite accurate with a time step of 
                tip = trans.get("simedtdlg.lbl.ttip.Timestep1") +
-               trans.get("simedtdlg.lbl.ttip.Timestep2") + " " +
+                               trans.get("simedtdlg.lbl.ttip.Timestep2") + " " +
                                UnitGroup.UNITS_TIME_STEP.toStringUnit(RK4SimulationStepper.RECOMMENDED_TIME_STEP) +
                                ".";
                label.setToolTipText(tip);
                sub.add(label);
                
-               m = new DoubleModel(conditions,"TimeStep", UnitGroup.UNITS_TIME_STEP, 0, 1);
+               m = new DoubleModel(conditions, "TimeStep", UnitGroup.UNITS_TIME_STEP, 0, 1);
                
                spin = new JSpinner(m.getSpinnerModel());
                spin.setEditor(new SpinnerEditor(spin));
                spin.setToolTipText(tip);
-               sub.add(spin,"w 65lp!");
+               sub.add(spin, "w 65lp!");
+               //sub.add(spin, "nogrid");
                
                unit = new UnitSelector(m);
                unit.setToolTipText(tip);
-               sub.add(unit,"growx");
+               sub.add(unit, "w 25");
+               //sub.add(unit, "nogrid");
                slider = new BasicSlider(m.getSliderModel(0, 0.2));
                slider.setToolTipText(tip);
-               sub.add(slider,"w 75lp, wrap");
+               sub.add(slider, "w 75lp, wrap");
+               //sub.add(slider,"wrap");
                
 
 
-               // Maximum angle step
-               /*
-               label = new JLabel("Max. angle step:");
-               tip = "<html>" +
-                               "This defines the maximum angle the rocket will turn during one time step.<br>"+
-                               "Smaller values result in a more accurate but possibly slower simulation.<br>"+
-                               "A recommended value is " +
-                               UnitGroup.UNITS_ANGLE.toStringUnit(RK4Simulator.RECOMMENDED_ANGLE_STEP) + ".";
-               label.setToolTipText(tip);
-               sub.add(label);
-               
-               m = new DoubleModel(conditions,"MaximumStepAngle", UnitGroup.UNITS_ANGLE, 
-                               1*Math.PI/180, Math.PI/9);
-               
-               spin = new JSpinner(m.getSpinnerModel());
-               spin.setEditor(new SpinnerEditor(spin));
-               spin.setToolTipText(tip);
-               sub.add(spin,"w 65lp!");
-               
-               unit = new UnitSelector(m);
-               unit.setToolTipText(tip);
-               sub.add(unit,"growx");
-               slider = new BasicSlider(m.getSliderModel(0, Math.toRadians(10)));
-               slider.setToolTipText(tip);
-               sub.add(slider,"w 75lp, wrap para");
-               */
 
                //// Reset to default button
                JButton button = new JButton(trans.get("simedtdlg.but.resettodefault"));
@@ -687,26 +702,22 @@ public class SimulationEditDialog extends JDialog {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                                conditions.setTimeStep(RK4SimulationStepper.RECOMMENDED_TIME_STEP);
+                               conditions.setGeodeticComputation(GeodeticComputationStrategy.SPHERICAL);
                        }
                });
-                               
-//             button.setToolTipText("<html>Reset the step value to its default:<br>" +
-//                             "Time step " +
-//                             UnitGroup.UNITS_SHORT_TIME.toStringUnit(RK4Simulator.RECOMMENDED_TIME_STEP) +
-//                             "; maximum angle step " +
-//                             UnitGroup.UNITS_ANGLE.toStringUnit(RK4Simulator.RECOMMENDED_ANGLE_STEP) + ".");
-               sub.add(button, "spanx, tag right, wrap para");
-               
-               
                
+               sub.add(button, "align left");
                
+
+
+
                //// Simulation listeners
                sub = new JPanel(new MigLayout("fill, gap 0 0"));
                //// Simulator listeners
                sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.border.Simlist")));
                panel.add(sub, "growx, growy");
                
-               
+
                DescriptionArea desc = new DescriptionArea(5);
                //// <html><i>Simulation listeners</i> is an advanced feature that allows user-written code to listen to and interact with the simulation.  
                //// For details on writing simulation listeners, see the OpenRocket technical documentation.
@@ -717,12 +728,12 @@ public class SimulationEditDialog extends JDialog {
                //// Current listeners:
                label = new JLabel(trans.get("simedtdlg.lbl.Curlist"));
                sub.add(label, "spanx, wrap rel");
-
+               
                final ListenerListModel listenerModel = new ListenerListModel();
                final JList list = new JList(listenerModel);
                list.setCellRenderer(new ListenerCellRenderer());
                JScrollPane scroll = new JScrollPane(list);
-//             scroll.setPreferredSize(new Dimension(1,1));
+               //              scroll.setPreferredSize(new Dimension(1,1));
                sub.add(scroll, "height 1px, grow, wrap rel");
                
                //// Add button
@@ -731,20 +742,20 @@ public class SimulationEditDialog extends JDialog {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                                String previous = Prefs.NODE.get("previousListenerName", "");
-                               String input = (String)JOptionPane.showInputDialog(SimulationEditDialog.this,
+                               String input = (String) JOptionPane.showInputDialog(SimulationEditDialog.this,
                                                new Object[] {
-                                               //// Type the full Java class name of the simulation listener, for example:
-                                               "Type the full Java class name of the simulation listener, for example:",
-                                               "<html><tt>" + CSVSaveListener.class.getName() + "</tt>" },
+                                                               //// Type the full Java class name of the simulation listener, for example:
+                                                               "Type the full Java class name of the simulation listener, for example:",
+                                                               "<html><tt>" + CSVSaveListener.class.getName() + "</tt>" },
                                                //// Add simulation listener
                                                trans.get("simedtdlg.lbl.Addsimlist"),
                                                JOptionPane.QUESTION_MESSAGE,
                                                null, null,
                                                previous
-                               );
+                                               );
                                if (input == null || input.equals(""))
                                        return;
-
+                               
                                Prefs.NODE.put("previousListenerName", input);
                                simulation.getSimulationListeners().add(input);
                                listenerModel.fireContentsChanged();
@@ -759,7 +770,7 @@ public class SimulationEditDialog extends JDialog {
                        public void actionPerformed(ActionEvent e) {
                                int[] selected = list.getSelectedIndices();
                                Arrays.sort(selected);
-                               for (int i=selected.length-1; i>=0; i--) {
+                               for (int i = selected.length - 1; i >= 0; i--) {
                                        simulation.getSimulationListeners().remove(selected[i]);
                                }
                                listenerModel.fireContentsChanged();
@@ -770,8 +781,8 @@ public class SimulationEditDialog extends JDialog {
 
                return panel;
        }
-
-
+       
+       
        private class ListenerListModel extends AbstractListModel {
                @Override
                public String getElementAt(int index) {
@@ -779,25 +790,27 @@ public class SimulationEditDialog extends JDialog {
                                return null;
                        return simulation.getSimulationListeners().get(index);
                }
+               
                @Override
                public int getSize() {
                        return simulation.getSimulationListeners().size();
                }
+               
                public void fireContentsChanged() {
                        super.fireContentsChanged(this, 0, getSize());
                }
        }
        
        
-       
-       
+
+
        /**
         * A panel for plotting the previously calculated data.
         */
        private JPanel plotTab() {
-
+               
                // Check that data exists
-               if (simulation.getSimulatedData() == null  ||
+               if (simulation.getSimulatedData() == null ||
                                simulation.getSimulatedData().getBranchCount() == 0) {
                        return noDataPanel();
                }
@@ -806,15 +819,15 @@ public class SimulationEditDialog extends JDialog {
        }
        
        
-       
+
        /**
         * A panel for exporting the data.
         */
        private JPanel exportTab() {
                FlightData data = simulation.getSimulatedData();
-
+               
                // Check that data exists
-               if (data == null  || data.getBranchCount() == 0 ||
+               if (data == null || data.getBranchCount() == 0 ||
                                data.getBranch(0).getTypes().length == 0) {
                        return noDataPanel();
                }
@@ -823,8 +836,8 @@ public class SimulationEditDialog extends JDialog {
        }
        
        
-       
-       
+
+
 
        /**
         * Return a panel stating that there is no data available, and that the user
@@ -836,28 +849,28 @@ public class SimulationEditDialog extends JDialog {
                // No data available
                //// No flight data available.
                panel.add(new JLabel(trans.get("simedtdlg.lbl.Noflightdata")),
-               "alignx 50%, aligny 100%, wrap para");
+                               "alignx 50%, aligny 100%, wrap para");
                //// Please run the simulation first.
                panel.add(new JLabel(trans.get("simedtdlg.lbl.runsimfirst")),
-               "alignx 50%, aligny 0%, wrap");
+                               "alignx 50%, aligny 0%, wrap");
                return panel;
        }
        
-
+       
        private void performPlot(PlotConfiguration config) {
-
+               
                // Fill the auto-selections
                FlightDataBranch branch = simulation.getSimulatedData().getBranch(0);
                PlotConfiguration filled = config.fillAutoAxes(branch);
                List<Axis> axes = filled.getAllAxes();
-
+               
 
                // Create the data series for both axes
                XYSeriesCollection[] data = new XYSeriesCollection[2];
                data[0] = new XYSeriesCollection();
                data[1] = new XYSeriesCollection();
                
-               
+
                // Get the domain axis type
                final FlightDataType domainType = filled.getDomainAxisType();
                final Unit domainUnit = filled.getDomainAxisUnit();
@@ -865,8 +878,8 @@ public class SimulationEditDialog extends JDialog {
                        throw new IllegalArgumentException("Domain axis type not specified.");
                }
                List<Double> x = branch.get(domainType);
-
                
+
                // Create the XYSeries objects from the flight data and store into the collections
                int length = filled.getTypeCount();
                String[] axisLabel = new String[2];
@@ -880,11 +893,11 @@ public class SimulationEditDialog extends JDialog {
                        // Store data in provided units
                        List<Double> y = branch.get(type);
                        XYSeries series = new XYSeries(name, false, true);
-                       for (int j=0; j<x.size(); j++) {
+                       for (int j = 0; j < x.size(); j++) {
                                series.add(domainUnit.toUnit(x.get(j)), unit.toUnit(y.get(j)));
                        }
                        data[axis].addSeries(series);
-
+                       
                        // Update axis label
                        if (axisLabel[axis] == null)
                                axisLabel[axis] = type.getName();
@@ -892,25 +905,25 @@ public class SimulationEditDialog extends JDialog {
                                axisLabel[axis] += "; " + type.getName();
                }
                
-               
+
                // Create the chart using the factory to get all default settings
-        JFreeChart chart = ChartFactory.createXYLineChart(
-            //// Simulated flight
-               trans.get("simedtdlg.chart.Simflight"),
-            null, 
-            null, 
-            null,
-            PlotOrientation.VERTICAL,
-            true,
-            true,
-            false
-        );
-               
-        
+               JFreeChart chart = ChartFactory.createXYLineChart(
+                               //// Simulated flight
+                               trans.get("simedtdlg.chart.Simflight"),
+                               null,
+                               null,
+                               null,
+                               PlotOrientation.VERTICAL,
+                               true,
+                               true,
+                               false
+                               );
+               
+
                // Add the data and formatting to the plot
                XYPlot plot = chart.getXYPlot();
                int axisno = 0;
-               for (int i=0; i<2; i++) {
+               for (int i = 0; i < 2; i++) {
                        // Check whether axis has any data
                        if (data[i].getSeriesCount() > 0) {
                                // Create and set axis
@@ -918,7 +931,7 @@ public class SimulationEditDialog extends JDialog {
                                double max = axes.get(i).getMaxValue();
                                NumberAxis axis = new PresetNumberAxis(min, max);
                                axis.setLabel(axisLabel[i]);
-//                             axis.setRange(axes.get(i).getMinValue(), axes.get(i).getMaxValue());
+                               //                              axis.setRange(axes.get(i).getMinValue(), axes.get(i).getMaxValue());
                                plot.setRangeAxis(axisno, axis);
                                
                                // Add data and map to the axis
@@ -929,11 +942,11 @@ public class SimulationEditDialog extends JDialog {
                        }
                }
                
-               plot.getDomainAxis().setLabel(getLabel(domainType,domainUnit));
+               plot.getDomainAxis().setLabel(getLabel(domainType, domainUnit));
                plot.addDomainMarker(new ValueMarker(0));
                plot.addRangeMarker(new ValueMarker(0));
                
-               
+
                // Create the dialog
                //// Simulation results
                final JDialog dialog = new JDialog(this, trans.get("simedtdlg.dlg.Simres"));
@@ -944,9 +957,9 @@ public class SimulationEditDialog extends JDialog {
                
                ChartPanel chartPanel = new ChartPanel(chart,
                                false, // properties
-                               true,  // save
+                               true, // save
                                false, // print
-                               true,  // zoom
+                               true, // zoom
                                true); // tooltips
                chartPanel.setMouseWheelEnabled(true);
                chartPanel.setEnforceFileExtensions(true);
@@ -955,7 +968,7 @@ public class SimulationEditDialog extends JDialog {
                chartPanel.setBorder(BorderFactory.createLineBorder(Color.GRAY, 1));
                
                panel.add(chartPanel, "grow, wrap 20lp");
-
+               
                //// Close button
                JButton button = new JButton(trans.get("dlg.but.close"));
                button.addActionListener(new ActionListener() {
@@ -965,15 +978,15 @@ public class SimulationEditDialog extends JDialog {
                        }
                });
                panel.add(button, "right");
-
+               
                dialog.setLocationByPlatform(true);
                dialog.pack();
                
                GUIUtil.setDisposableDialogOptions(dialog, button);
-
+               
                dialog.setVisible(true);
        }
-
+       
        
        private class PresetNumberAxis extends NumberAxis {
                private final double min;
@@ -994,31 +1007,32 @@ public class SimulationEditDialog extends JDialog {
        
        private String getLabel(FlightDataType type, Unit unit) {
                String name = type.getName();
-               if (unit != null  &&  !UnitGroup.UNITS_NONE.contains(unit)  &&
+               if (unit != null && !UnitGroup.UNITS_NONE.contains(unit) &&
                                !UnitGroup.UNITS_COEFFICIENT.contains(unit) && unit.getUnit().length() > 0)
-                       name += " ("+unit.getUnit() + ")";
+                       name += " (" + unit.getUnit() + ")";
                return name;
        }
        
-
+       
 
        private class ListenerCellRenderer extends JLabel implements ListCellRenderer {
-
+               
+               @Override
                public Component getListCellRendererComponent(JList list, Object value,
                                int index, boolean isSelected, boolean cellHasFocus) {
                        String s = value.toString();
                        setText(s);
-
+                       
                        // Attempt instantiating, catch any exceptions
                        Exception ex = null;
                        try {
                                Class<?> c = Class.forName(s);
                                @SuppressWarnings("unused")
-                               SimulationListener l = (SimulationListener)c.newInstance();
+                               SimulationListener l = (SimulationListener) c.newInstance();
                        } catch (Exception e) {
                                ex = e;
                        }
-
+                       
                        if (ex == null) {
                                setIcon(Icons.SIMULATION_LISTENER_OK);
                                //// Listener instantiated successfully.
@@ -1029,7 +1043,7 @@ public class SimulationEditDialog extends JDialog {
                                setToolTipText("<html>Unable to instantiate listener due to exception:<br>" +
                                                ex.toString());
                        }
-
+                       
                        if (isSelected) {
                                setBackground(list.getSelectionBackground());
                                setForeground(list.getSelectionForeground());
index a216528bec7b6fc90fd987dd57773963c800e3c5..80b65ea2b5ae47b09f8b503fdb1a097eece510e7 100644 (file)
@@ -1,5 +1,9 @@
 package net.sf.openrocket.models.gravity;
 
+import net.sf.openrocket.util.WorldCoordinate;
+
+@Deprecated
+
 /**
  * A gravity model based on the International Gravity Formula of 1967.  The gravity
  * value is computed when the object is constructed and later returned as a static
@@ -7,6 +11,7 @@ package net.sf.openrocket.models.gravity;
  * 
  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
  */
+
 public class BasicGravityModel implements GravityModel {
        
        private final double g;
@@ -22,15 +27,21 @@ public class BasicGravityModel implements GravityModel {
                g = 9.780327 * (1 + 0.0053024 * sin - 0.0000058 * sin2);
        }
        
-       @Override
+       //@Override
        public double getGravity(double altitude) {
                return g;
        }
        
-       @Override
+       //@Override
        public int getModID() {
                // Return constant mod ID
                return (int) (g * 1000000);
        }
+
+       @Override
+       public double getGravity(WorldCoordinate wc) {
+               // TODO Auto-generated method stub
+               return 0;
+       }
        
 }
index bf38d520923ed0d0478bbf4959e7af3ae58f0d5e..7feba89246ac3a78d4b75620ebb171a5806ff9ad 100644 (file)
@@ -1,20 +1,29 @@
 package net.sf.openrocket.models.gravity;
 
-import net.sf.openrocket.util.Monitorable;
+//import net.sf.openrocket.util.Monitorable;
+import net.sf.openrocket.util.WorldCoordinate;
 
 /**
  * An interface to modelling gravitational acceleration.
  * 
  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
  */
-public interface GravityModel extends Monitorable {
+public interface GravityModel { //extends Monitorable {
 
        /**
-        * Compute the gravity at a specific altitude.
+        * Compute the gravity at a specific altitude above equator.
         * 
         * @param altitude      the altitude at which to compute the gravity
         * @return                      the gravitational acceleration
         */
-       public double getGravity(double altitude);
+       //public double getGravity(double altitude);
+       
+       
+       /**
+        * Compute the gravity at a given world coordinate
+        * @param wc
+        * @return gravitational acceleration in m/s/s
+        */
+       public double getGravity(WorldCoordinate wc);
        
 }
diff --git a/src/net/sf/openrocket/models/gravity/WGSGravityModel.java b/src/net/sf/openrocket/models/gravity/WGSGravityModel.java
new file mode 100644 (file)
index 0000000..3312df2
--- /dev/null
@@ -0,0 +1,43 @@
+package net.sf.openrocket.models.gravity;
+
+import net.sf.openrocket.util.MathUtil;
+import net.sf.openrocket.util.WorldCoordinate;
+
+/**
+ * A gravity model based on the WGS84 elipsoid.
+ * 
+ * @author Richard Graham <richard@rdg.cc>
+ */
+
+public class WGSGravityModel implements GravityModel {
+       
+       private WorldCoordinate lastWorldCoordinate;
+       private double lastg;
+       
+       @Override
+       public double getGravity(WorldCoordinate wc) {
+               
+               // This is a proxy method to calcGravity, to avoid repeated calculation
+               if (wc != this.lastWorldCoordinate) {
+                       this.lastg = calcGravity(wc);
+                       this.lastWorldCoordinate = wc;
+               }
+               
+               return this.lastg;
+               
+       }
+       
+       private double calcGravity(WorldCoordinate wc) {
+               
+               double sin2lat = MathUtil.pow2(Math.sin(wc.getLatitudeRad()));
+               double g_0 = 9.7803267714 * ((1.0 + 0.00193185138639 * sin2lat) / Math.sqrt(1.0 - 0.00669437999013 * sin2lat));
+               
+               // Apply correction due to altitude. Note this assumes a spherical earth, but it is a small correction
+               // so it probably doesn't really matter. Also does not take into account gravity of the atmosphere, again
+               // correction could be done but not really necessary.
+               double g_alt = g_0 * Math.pow(WorldCoordinate.REARTH / (WorldCoordinate.REARTH + wc.getAltitude()), 2);
+               
+               return g_alt;
+       }
+       
+}
index e39d6e6a77a4eec4b1d5c2e553e082e0e0778fe5..ea94f3b43eada649814a8f4593aad7aa6ce1baa5 100644 (file)
@@ -30,7 +30,7 @@ public abstract class AbstractSimulationStepper implements SimulationStepper {
                }
                
                // Compute conditions
-               double altitude = status.getRocketPosition().z + status.getSimulationConditions().getLaunchAltitude();
+               double altitude = status.getRocketPosition().z + status.getSimulationConditions().getLaunchSite().getAltitude();
                conditions = status.getSimulationConditions().getAtmosphericModel().getConditions(altitude);
                
                // Call post-listener
@@ -61,7 +61,7 @@ public abstract class AbstractSimulationStepper implements SimulationStepper {
                }
                
                // Compute conditions
-               double altitude = status.getRocketPosition().z + status.getSimulationConditions().getLaunchAltitude();
+               double altitude = status.getRocketPosition().z + status.getSimulationConditions().getLaunchSite().getAltitude();
                wind = status.getSimulationConditions().getWindModel().getWindVelocity(status.getSimulationTime(), altitude);
                
                // Call post-listener
@@ -91,8 +91,9 @@ public abstract class AbstractSimulationStepper implements SimulationStepper {
                }
                
                // Compute conditions
-               double altitude = status.getRocketPosition().z + status.getSimulationConditions().getLaunchAltitude();
-               gravity = status.getSimulationConditions().getGravityModel().getGravity(altitude);
+               //double altitude = status.getRocketPosition().z + status.getSimulationConditions().getLaunchAltitude();
+               //gravity = status.getSimulationConditions().getGravityModel().getGravity(altitude);
+               gravity = status.getSimulationConditions().getGravityModel().getGravity(status.getRocketWorldPosition());
                
                // Call post-listener
                gravity = SimulationListenerHelper.firePostGravityModel(status, gravity);
index 7bc635c715d64c2cc5114bfc210717ebf36b0f8e..a37a823eb23b77328cbf25b3cfb7d8659c2e1466 100644 (file)
@@ -185,8 +185,8 @@ public class BasicEventSimulationEngine implements SimulationEngine {
                init.setPreviousTimeStep(simulationConditions.getTimeStep());
                init.setRocketPosition(Coordinate.NUL);
                init.setRocketVelocity(Coordinate.NUL);
+               init.setRocketWorldPosition(simulationConditions.getLaunchSite());
                
-
                // Initialize to roll angle with least stability w.r.t. the wind
                Quaternion o;
                FlightConditions cond = new FlightConditions(configuration);
index eea84dde568097948457201cb71bf6dfdd4d83da..26862420cd9b1cd0e70b4fecccb1ef17ef548e64 100644 (file)
@@ -22,7 +22,7 @@ import net.sf.openrocket.unit.UnitGroup;
  */
 public class FlightDataType implements Comparable<FlightDataType> {
        private static final Translator trans = Application.getTranslator();
-
+       
        /** Priority of custom-created variables */
        private static final int DEFAULT_PRIORITY = 999;
        
@@ -64,8 +64,11 @@ public class FlightDataType implements Comparable<FlightDataType> {
        public static final FlightDataType TYPE_VELOCITY_XY = newType(trans.get("FlightDataType.TYPE_VELOCITY_XY"), UnitGroup.UNITS_VELOCITY, 34);
        //// Lateral acceleration
        public static final FlightDataType TYPE_ACCELERATION_XY = newType(trans.get("FlightDataType.TYPE_ACCELERATION_XY"), UnitGroup.UNITS_ACCELERATION, 35);
+       //// Latitude
+       public static final FlightDataType TYPE_LATITUDE = newType(trans.get("FlightDataType.TYPE_LATITUDE"), UnitGroup.UNITS_ANGLE, 36);
+       //// Longitude
+       public static final FlightDataType TYPE_LONGITUDE = newType(trans.get("FlightDataType.TYPE_LONGITUDE"), UnitGroup.UNITS_ANGLE, 37);
        
-
        //// Angular motion
        //// Angle of attack
        public static final FlightDataType TYPE_AOA = newType(trans.get("FlightDataType.TYPE_AOA"), UnitGroup.UNITS_ANGLE, 40);
@@ -140,6 +143,9 @@ public class FlightDataType implements Comparable<FlightDataType> {
        //// Yaw damping coefficient
        public static final FlightDataType TYPE_YAW_DAMPING_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_YAW_DAMPING_MOMENT_COEFF"), UnitGroup.UNITS_COEFFICIENT, 98);
        
+       //// Coriolis acceleration
+       public static final FlightDataType TYPE_CORIOLIS_ACCELERATION = newType(trans.get("FlightDataType.TYPE_CORIOLIS_ACCELERATION"), UnitGroup.UNITS_ACCELERATION, 99);
+       
 
        ////  Reference length + area
        //// Reference length
@@ -165,7 +171,6 @@ public class FlightDataType implements Comparable<FlightDataType> {
        //// Speed of sound
        public static final FlightDataType TYPE_SPEED_OF_SOUND = newType(trans.get("FlightDataType.TYPE_SPEED_OF_SOUND"), UnitGroup.UNITS_VELOCITY, 113);
        
-
        ////  Simulation information
        //// Simulation time step
        public static final FlightDataType TYPE_TIME_STEP = newType(trans.get("FlightDataType.TYPE_TIME_STEP"), UnitGroup.UNITS_TIME_STEP, 200);
index 07020f2252c0c3eda1a3c64755c4d083a4b7206b..4aa9494ce91b384af3ee8a6bfffc8f7265802edf 100644 (file)
@@ -13,10 +13,11 @@ import net.sf.openrocket.simulation.exception.SimulationException;
 import net.sf.openrocket.simulation.listeners.SimulationListenerHelper;
 import net.sf.openrocket.startup.Application;
 import net.sf.openrocket.util.Coordinate;
+import net.sf.openrocket.util.GeodeticComputationStrategy;
 import net.sf.openrocket.util.MathUtil;
 import net.sf.openrocket.util.Quaternion;
 import net.sf.openrocket.util.Rotation2D;
-
+import net.sf.openrocket.util.WorldCoordinate;
 
 public class RK4SimulationStepper extends AbstractSimulationStepper {
        
@@ -67,6 +68,7 @@ public class RK4SimulationStepper extends AbstractSimulationStepper {
                status.copyFrom(original);
                
                SimulationConditions sim = original.getSimulationConditions();
+               
                status.setLaunchRodDirection(new Coordinate(
                                Math.sin(sim.getLaunchRodAngle()) * Math.cos(sim.getLaunchRodDirection()),
                                Math.sin(sim.getLaunchRodAngle()) * Math.sin(sim.getLaunchRodDirection()),
@@ -87,7 +89,6 @@ public class RK4SimulationStepper extends AbstractSimulationStepper {
                RK4SimulationStatus status = (RK4SimulationStatus) simulationStatus;
                DataStore store = new DataStore();
                
-
                ////////  Perform RK4 integration:  ////////
                
                RK4SimulationStatus status2;
@@ -253,6 +254,10 @@ public class RK4SimulationStepper extends AbstractSimulationStepper {
                status.setRocketRotationVelocity(status.getRocketRotationVelocity().add(deltaR));
                status.setRocketOrientationQuaternion(status.getRocketOrientationQuaternion().multiplyLeft(Quaternion.rotation(deltaO)).normalizeIfNecessary());
                
+               WorldCoordinate w = status.getSimulationConditions().getLaunchSite();
+               w = status.getSimulationConditions().getGeodeticComputation().addCoordinate(w, status.getRocketPosition());
+               status.setRocketWorldPosition(w);
+               
                status.setSimulationTime(status.getSimulationTime() + store.timestep);
                
                status.setPreviousTimeStep(store.timestep);
@@ -338,13 +343,18 @@ public class RK4SimulationStepper extends AbstractSimulationStepper {
                
                store.linearAcceleration = store.thetaRotation.rotateZ(store.linearAcceleration);
                
-               // Convert into world coordinates and add effect of gravity
+               // Convert into rocket world coordinates
                store.linearAcceleration = status.getRocketOrientationQuaternion().rotate(store.linearAcceleration);
                
+               // add effect of gravity
                store.gravity = modelGravity(status);
                store.linearAcceleration = store.linearAcceleration.sub(0, 0, store.gravity);
                
-
+               // add effect of Coriolis acceleration
+               store.coriolisAcceleration = status.getSimulationConditions().getGeodeticComputation()
+                               .getCoriolisAcceleration(status.getRocketWorldPosition(), status.getRocketVelocity());
+               store.linearAcceleration = store.linearAcceleration.add(store.coriolisAcceleration);
+               
                // If still on the launch rod, project acceleration onto launch rod direction and
                // set angular acceleration to zero.
                if (!status.isLaunchRodCleared()) {
@@ -388,7 +398,6 @@ public class RK4SimulationStepper extends AbstractSimulationStepper {
        }
        
        
-
        /**
         * Calculate the aerodynamic forces into the data store.  This method also handles
         * whether to include aerodynamic computation warnings or not.
@@ -538,6 +547,12 @@ public class RK4SimulationStepper extends AbstractSimulationStepper {
                data.setValue(FlightDataType.TYPE_POSITION_X, status.getRocketPosition().x);
                data.setValue(FlightDataType.TYPE_POSITION_Y, status.getRocketPosition().y);
                
+               if (status.getSimulationConditions().getGeodeticComputation() != GeodeticComputationStrategy.NONE) {
+                       data.setValue(FlightDataType.TYPE_LATITUDE, status.getRocketWorldPosition().getLatitudeRad());
+                       data.setValue(FlightDataType.TYPE_LONGITUDE, status.getRocketWorldPosition().getLongitudeRad());
+                       data.setValue(FlightDataType.TYPE_CORIOLIS_ACCELERATION, store.coriolisAcceleration.length());
+               }
+               
                if (extra) {
                        data.setValue(FlightDataType.TYPE_POSITION_XY,
                                        MathUtil.hypot(status.getRocketPosition().x, status.getRocketPosition().y));
@@ -684,6 +699,8 @@ public class RK4SimulationStepper extends AbstractSimulationStepper {
                
                public MassData massData;
                
+               public Coordinate coriolisAcceleration;
+               
                public Coordinate linearAcceleration;
                public Coordinate angularAcceleration;
                
index 3e5820862e9f0b217a78c8d4c5bd22516796f031..f2412717a34a55bcfe6200c1a924eecd09829267 100644 (file)
@@ -11,7 +11,9 @@ import net.sf.openrocket.models.wind.WindModel;
 import net.sf.openrocket.rocketcomponent.Rocket;
 import net.sf.openrocket.simulation.listeners.SimulationListener;
 import net.sf.openrocket.util.BugException;
+import net.sf.openrocket.util.GeodeticComputationStrategy;
 import net.sf.openrocket.util.Monitorable;
+import net.sf.openrocket.util.WorldCoordinate;
 
 /**
  * A holder class for the simulation conditions.  These include conditions that do not change
@@ -34,9 +36,12 @@ public class SimulationConditions implements Monitorable, Cloneable {
        /** Launch rod direction, 0 = upwind, PI = downwind. */
        private double launchRodDirection = 0;
        
-
-       private double launchAltitude = 0;
-       private double launchLatitude = 45;
+       // TODO: Depreciate these and use worldCoordinate only.
+       //private double launchAltitude = 0;
+       //private double launchLatitude = 45;
+       //private double launchLongitude = 0;
+       private WorldCoordinate launchSite = new WorldCoordinate(0, 0, 0);
+       private GeodeticComputationStrategy geodeticComputation = GeodeticComputationStrategy.SPHERICAL;
        
 
        private WindModel windModel;
@@ -64,6 +69,7 @@ public class SimulationConditions implements Monitorable, Cloneable {
        
        
 
+
        public AerodynamicCalculator getAerodynamicCalculator() {
                return aerodynamicCalculator;
        }
@@ -76,7 +82,6 @@ public class SimulationConditions implements Monitorable, Cloneable {
                this.aerodynamicCalculator = aerodynamicCalculator;
        }
        
-       
        public MassCalculator getMassCalculator() {
                return massCalculator;
        }
@@ -147,24 +152,29 @@ public class SimulationConditions implements Monitorable, Cloneable {
        }
        
        
-       public double getLaunchAltitude() {
-               return launchAltitude;
+       public WorldCoordinate getLaunchSite() {
+               return this.launchSite;
        }
        
-       
-       public void setLaunchAltitude(double launchAltitude) {
-               this.launchAltitude = launchAltitude;
+       public void setLaunchSite(WorldCoordinate site) {
+               if (this.launchSite.equals(site))
+                       return;
+               this.launchSite = site;
                this.modID++;
        }
        
        
-       public double getLaunchLatitude() {
-               return launchLatitude;
+       public GeodeticComputationStrategy getGeodeticComputation() {
+               return geodeticComputation;
        }
        
-       
-       public void setLaunchLatitude(double launchLatitude) {
-               this.launchLatitude = launchLatitude;
+       public void setGeodeticComputation(GeodeticComputationStrategy geodeticComputation) {
+               if (this.geodeticComputation == geodeticComputation)
+                       return;
+               if (geodeticComputation == null) {
+                       throw new IllegalArgumentException("strategy cannot be null");
+               }
+               this.geodeticComputation = geodeticComputation;
                this.modID++;
        }
        
@@ -201,8 +211,8 @@ public class SimulationConditions implements Monitorable, Cloneable {
        
        
        public void setGravityModel(GravityModel gravityModel) {
-               if (this.gravityModel != null)
-                       this.modIDadd += this.gravityModel.getModID();
+               //if (this.gravityModel != null)
+               //      this.modIDadd += this.gravityModel.getModID();
                this.modID++;
                this.gravityModel = gravityModel;
        }
@@ -253,6 +263,8 @@ public class SimulationConditions implements Monitorable, Cloneable {
        }
        
        
+
+
        // TODO: HIGH: Make cleaner
        public List<SimulationListener> getSimulationListenerList() {
                return simulationListeners;
@@ -261,8 +273,10 @@ public class SimulationConditions implements Monitorable, Cloneable {
        
        @Override
        public int getModID() {
+               //return (modID + modIDadd + rocket.getModID() + windModel.getModID() + atmosphericModel.getModID() +
+               //              gravityModel.getModID() + aerodynamicCalculator.getModID() + massCalculator.getModID());
                return (modID + modIDadd + rocket.getModID() + windModel.getModID() + atmosphericModel.getModID() +
-                               gravityModel.getModID() + aerodynamicCalculator.getModID() + massCalculator.getModID());
+                               aerodynamicCalculator.getModID() + massCalculator.getModID());
        }
        
        
index a2b39854bbfed73b0ef8a93944b15a2aed59575d..d939869364b9f768da7fce7b53b1c41041f387e4 100644 (file)
@@ -11,13 +11,16 @@ import net.sf.openrocket.aerodynamics.BarrowmanCalculator;
 import net.sf.openrocket.masscalc.BasicMassCalculator;
 import net.sf.openrocket.models.atmosphere.AtmosphericModel;
 import net.sf.openrocket.models.atmosphere.ExtendedISAModel;
-import net.sf.openrocket.models.gravity.BasicGravityModel;
+import net.sf.openrocket.models.gravity.GravityModel;
+import net.sf.openrocket.models.gravity.WGSGravityModel;
 import net.sf.openrocket.models.wind.PinkNoiseWindModel;
 import net.sf.openrocket.rocketcomponent.Rocket;
 import net.sf.openrocket.util.BugException;
 import net.sf.openrocket.util.ChangeSource;
+import net.sf.openrocket.util.GeodeticComputationStrategy;
 import net.sf.openrocket.util.MathUtil;
 import net.sf.openrocket.util.Utils;
+import net.sf.openrocket.util.WorldCoordinate;
 
 /**
  * A class holding simulation options in basic parameter form and which functions
@@ -59,8 +62,14 @@ public class SimulationOptions implements ChangeSource, Cloneable {
        private double windAverage = 2.0;
        private double windTurbulence = 0.1;
        
+       /*
+        * SimulationOptions maintains the launch site parameters as separate double values,
+        * and converts them into a WorldCoordinate when converting to SimulationConditions.
+        */
        private double launchAltitude = 0;
        private double launchLatitude = 45;
+       private double launchLongitude = 0;
+       private GeodeticComputationStrategy geodeticComputation = GeodeticComputationStrategy.SPHERICAL;
        
        private boolean useISA = true;
        private double launchTemperature = ExtendedISAModel.STANDARD_TEMPERATURE;
@@ -84,7 +93,6 @@ public class SimulationOptions implements ChangeSource, Cloneable {
        }
        
        
-
        public Rocket getRocket() {
                return rocket;
        }
@@ -223,10 +231,34 @@ public class SimulationOptions implements ChangeSource, Cloneable {
                fireChangeEvent();
        }
        
+       public double getLaunchLongitude() {
+               return launchLongitude;
+       }
+       
+       public void setLaunchLongitude(double launchLongitude) {
+               launchLongitude = MathUtil.clamp(launchLongitude, -180, 180);
+               if (MathUtil.equals(this.launchLongitude, launchLongitude))
+                       return;
+               this.launchLongitude = launchLongitude;
+               fireChangeEvent();
+       }
+       
+       
+       public GeodeticComputationStrategy getGeodeticComputation() {
+               return geodeticComputation;
+       }
+       
+       public void setGeodeticComputation(GeodeticComputationStrategy geodeticComputation) {
+               if (this.geodeticComputation == geodeticComputation)
+                       return;
+               if (geodeticComputation == null) {
+                       throw new IllegalArgumentException("strategy cannot be null");
+               }
+               this.geodeticComputation = geodeticComputation;
+               fireChangeEvent();
+       }
+       
        
-
-
-
        public boolean isISAAtmosphere() {
                return useISA;
        }
@@ -278,7 +310,7 @@ public class SimulationOptions implements ChangeSource, Cloneable {
                if (useISA) {
                        return ISA_ATMOSPHERIC_MODEL;
                }
-               return new ExtendedISAModel(launchAltitude, launchTemperature, launchPressure);
+               return new ExtendedISAModel(getLaunchAltitude(), launchTemperature, launchPressure);
        }
        
        
@@ -389,6 +421,7 @@ public class SimulationOptions implements ChangeSource, Cloneable {
                
                this.launchAltitude = src.launchAltitude;
                this.launchLatitude = src.launchLatitude;
+               this.launchLongitude = src.launchLongitude;
                this.launchPressure = src.launchPressure;
                this.launchRodAngle = src.launchRodAngle;
                this.launchRodDirection = src.launchRodDirection;
@@ -419,6 +452,7 @@ public class SimulationOptions implements ChangeSource, Cloneable {
                                Utils.equals(this.motorID, o.motorID) &&
                                MathUtil.equals(this.launchAltitude, o.launchAltitude) &&
                                MathUtil.equals(this.launchLatitude, o.launchLatitude) &&
+                               MathUtil.equals(this.launchLongitude, o.launchLongitude) &&
                                MathUtil.equals(this.launchPressure, o.launchPressure) &&
                                MathUtil.equals(this.launchRodAngle, o.launchRodAngle) &&
                                MathUtil.equals(this.launchRodDirection, o.launchRodDirection) &&
@@ -471,8 +505,8 @@ public class SimulationOptions implements ChangeSource, Cloneable {
                conditions.setLaunchRodLength(getLaunchRodLength());
                conditions.setLaunchRodAngle(getLaunchRodAngle());
                conditions.setLaunchRodDirection(getLaunchRodDirection());
-               conditions.setLaunchAltitude(getLaunchAltitude());
-               conditions.setLaunchLatitude(getLaunchLatitude());
+               conditions.setLaunchSite(new WorldCoordinate(getLaunchLatitude(), getLaunchLongitude(), getLaunchAltitude()));
+               conditions.setGeodeticComputation(getGeodeticComputation());
                conditions.setRandomSeed(randomSeed);
                
                PinkNoiseWindModel windModel = new PinkNoiseWindModel(randomSeed);
@@ -482,7 +516,9 @@ public class SimulationOptions implements ChangeSource, Cloneable {
                
                conditions.setAtmosphericModel(getAtmosphericModel());
                
-               BasicGravityModel gravityModel = new BasicGravityModel(getLaunchLatitude());
+               //BasicGravityModel gravityModel = new BasicGravityModel(getLaunchLatitude());
+               GravityModel gravityModel = new WGSGravityModel();
+               
                conditions.setGravityModel(gravityModel);
                
                conditions.setAerodynamicCalculator(new BarrowmanCalculator());
index cc77117ba9f8b791d0fd9835e33fd84a87fc6d14..9a39cffda958875960a5bd8853eff51415edddc0 100644 (file)
@@ -13,6 +13,7 @@ import net.sf.openrocket.util.Coordinate;
 import net.sf.openrocket.util.Monitorable;
 import net.sf.openrocket.util.MonitorableSet;
 import net.sf.openrocket.util.Quaternion;
+import net.sf.openrocket.util.WorldCoordinate;
 
 /**
  * A holder class for the dynamic status during the rocket's flight.
@@ -35,6 +36,7 @@ public class SimulationStatus implements Cloneable, Monitorable {
        private double previousTimeStep;
        
        private Coordinate position;
+       private WorldCoordinate worldPosition;
        private Coordinate velocity;
        
        private Quaternion orientation;
@@ -146,6 +148,14 @@ public class SimulationStatus implements Cloneable, Monitorable {
                return position;
        }
        
+       public void setRocketWorldPosition(WorldCoordinate wc) {
+               this.worldPosition = wc;
+               this.modID++;
+       }
+       
+       public WorldCoordinate getRocketWorldPosition() {
+               return worldPosition;
+       }
        
        public void setRocketVelocity(Coordinate velocity) {
                this.velocity = velocity;
@@ -341,6 +351,7 @@ public class SimulationStatus implements Cloneable, Monitorable {
                this.time = orig.time;
                this.previousTimeStep = orig.previousTimeStep;
                this.position = orig.position;
+               this.worldPosition = orig.worldPosition;
                this.velocity = orig.velocity;
                this.orientation = orig.orientation;
                this.rotationVelocity = orig.rotationVelocity;
index 920e766484c0a3e05b27cba80d43e364e483ae7a..4f1c5b13bde8934e72e05168ea9b9d21937a3941 100644 (file)
@@ -99,6 +99,7 @@ public class UnitGroup {
                UNITS_DISTANCE.addUnit(new GeneralUnit(0.3048, "ft"));
                UNITS_DISTANCE.addUnit(new GeneralUnit(0.9144, "yd"));
                UNITS_DISTANCE.addUnit(new GeneralUnit(1609.344, "mi"));
+               UNITS_DISTANCE.addUnit(new GeneralUnit(1852, "nmi"));
                
                UNITS_AREA = new UnitGroup();
                UNITS_AREA.addUnit(new GeneralUnit(pow2(0.001), "mm" + SQUARED));
@@ -148,6 +149,7 @@ public class UnitGroup {
                UNITS_ANGLE = new UnitGroup();
                UNITS_ANGLE.addUnit(new DegreeUnit());
                UNITS_ANGLE.addUnit(new FixedPrecisionUnit("rad", 0.01));
+               UNITS_ANGLE.addUnit(new GeneralUnit(1.0/3437.74677078, "arcmin"));
                
                UNITS_DENSITY_BULK = new UnitGroup();
                UNITS_DENSITY_BULK.addUnit(new GeneralUnit(1000, "g/cm" + CUBED));
diff --git a/src/net/sf/openrocket/util/GeodeticComputationStrategy.java b/src/net/sf/openrocket/util/GeodeticComputationStrategy.java
new file mode 100644 (file)
index 0000000..8e7d2f2
--- /dev/null
@@ -0,0 +1,265 @@
+package net.sf.openrocket.util;
+
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.startup.Application;
+
+/**
+ * A strategy that performs computations on WorldCoordinates.
+ */
+public enum GeodeticComputationStrategy {
+       
+
+       /**
+        * Perform no geodetic computations.  The addCoordinate method does nothing and
+        * getCoriolisAcceleration returns Coordinate.NUL.
+        */
+       NONE {
+               @Override
+               public WorldCoordinate addCoordinate(WorldCoordinate latlon, Coordinate delta) {
+                       return latlon;
+               }
+               
+               @Override
+               public Coordinate getCoriolisAcceleration(WorldCoordinate latlon, Coordinate velocity) {
+                       return Coordinate.NUL;
+               }
+       },
+       
+       /**
+        * Perform geodetic computations with a spherical Earch approximation.
+        */
+       SPHERICAL {
+               
+               @Override
+               public WorldCoordinate addCoordinate(WorldCoordinate location, Coordinate delta) {
+                       double newAlt = location.getAltitude() + delta.z;
+                       
+                       // bearing (in radians, clockwise from north);
+                       // d/R is the angular distance (in radians), where d is the distance traveled and R is the earth’s radius
+                       double d = MathUtil.hypot(delta.x, delta.y);
+                       double bearing = Math.atan(delta.x / delta.y);
+                       if (delta.y < 0)
+                               bearing = bearing + Math.PI;
+                       
+                       // Calculate the new lat and lon                
+                       double newLat, newLon;
+                       double sinLat = Math.sin(location.getLatitudeRad());
+                       double cosLat = Math.cos(location.getLatitudeRad());
+                       double sinDR = Math.sin(d / WorldCoordinate.REARTH);
+                       double cosDR = Math.cos(d / WorldCoordinate.REARTH);
+                       
+                       newLat = Math.asin(sinLat * cosDR + cosLat * sinDR * Math.cos(bearing));
+                       newLon = location.getLongitudeRad() + Math.atan2(Math.sin(bearing) * sinDR * cosLat, cosDR - sinLat * Math.sin(newLat));
+                       
+                       if (Double.isNaN(newLat)) {
+                               newLat = location.getLatitudeRad();
+                       }
+                       
+                       if (Double.isNaN(newLon)) {
+                               newLon = location.getLongitudeRad();
+                       }
+                       
+                       return new WorldCoordinate(Math.toDegrees(newLat), Math.toDegrees(newLon), newAlt);
+               }
+               
+               @Override
+               public Coordinate getCoriolisAcceleration(WorldCoordinate latlon, Coordinate velocity) {
+                       return computeCoriolisAcceleration(latlon, velocity);
+               }
+               
+       },
+       
+       /**
+        * Perform geodetic computations on a WGS84 reference ellipsoid using Vincenty Direct Solution.
+        */
+       WGS84 {
+               
+               @Override
+               public WorldCoordinate addCoordinate(WorldCoordinate location, Coordinate delta) {
+                       double newAlt = location.getAltitude() + delta.z;
+                       
+                       // bearing (in radians, clockwise from north);
+                       // d/R is the angular distance (in radians), where d is the distance traveled and R is the earth’s radius
+                       double d = MathUtil.hypot(delta.x, delta.y);
+                       double bearing = Math.atan(delta.x / delta.y);
+                       if (delta.y < 0)
+                               bearing = bearing + Math.PI;
+                       
+                       // Calculate the new lat and lon                
+                       double newLat, newLon;
+                       double ret[] = dirct1(location.getLatitudeRad(), location.getLongitudeRad(), bearing, d, 6378137, 1.0 / 298.25722210088);
+                       newLat = ret[0];
+                       newLon = ret[1];
+                       
+                       if (Double.isNaN(newLat)) {
+                               newLat = location.getLatitudeRad();
+                       }
+                       
+                       if (Double.isNaN(newLon)) {
+                               newLon = location.getLongitudeRad();
+                       }
+                       return new WorldCoordinate(newLat, newLon, newAlt);
+               }
+               
+               @Override
+               public Coordinate getCoriolisAcceleration(WorldCoordinate latlon, Coordinate velocity) {
+                       return computeCoriolisAcceleration(latlon, velocity);
+               }
+       };
+       
+
+       private static final Translator trans = Application.getTranslator();
+       
+       private static final double PRECISION_LIMIT = 0.5e-13;
+       
+       
+       /**
+        * Return the name of this geodetic computation method.
+        */
+       public String getName() {
+               return trans.get(name().toLowerCase() + ".name");
+       }
+       
+       /**
+        * Return a description of the geodetic computation methods.
+        */
+       public String getDescription() {
+               return trans.get(name().toLowerCase() + ".desc");
+       }
+       
+       @Override
+       public String toString() {
+               return getName();
+       }
+       
+       
+       /**
+        * Add a cartesian movement coordinate to a WorldCoordinate.
+        */
+       public abstract WorldCoordinate addCoordinate(WorldCoordinate latlon, Coordinate delta);
+       
+       
+       /**
+        * Compute the coriolis acceleration at a specified WorldCoordinate and velocity.
+        */
+       public abstract Coordinate getCoriolisAcceleration(WorldCoordinate latlon, Coordinate velocity);
+       
+       
+
+
+
+       private static Coordinate computeCoriolisAcceleration(WorldCoordinate latlon, Coordinate velocity) {
+               
+               double sinlat = Math.sin(latlon.getLatitudeRad());
+               double coslat = Math.cos(latlon.getLatitudeRad());
+               
+               double v_n = velocity.y;
+               double v_e = -1 * velocity.x;
+               double v_u = velocity.z;
+               
+               // Not exactly sure why I have to reverse the x direction, but this gives the precession in the
+               // correct direction (e.g, flying north in northern hemisphere should cause defection to the east (+ve x))
+               // All the directions are very confusing because they are tied to the wind direction (to/from?), in which
+               // +ve x or east according to WorldCoordinate is what everything is relative to.
+               // The directions of everything need so thought, ideally the wind direction and launch rod should be
+               // able to be set independently and in terms of bearing with north == +ve y.
+               
+               Coordinate coriolis = new Coordinate(2.0 * WorldCoordinate.EROT * (v_n * sinlat - v_u * coslat),
+                                                                                               2.0 * WorldCoordinate.EROT * (-1.0 * v_e * sinlat),
+                                                                                               2.0 * WorldCoordinate.EROT * (v_e * coslat)
+                                                                                       );
+               return coriolis;
+       }
+       
+       
+
+       // ******************************************************************** //
+       // The Vincenty Direct Solution.
+       // Code from GeoConstants.java, Ian Cameron Smith, GPL
+       // ******************************************************************** //
+       
+       /**
+        * Solution of the geodetic direct problem after T. Vincenty.
+        * Modified Rainsford's method with Helmert's elliptical terms.
+        * Effective in any azimuth and at any distance short of antipodal.
+        *
+        * Programmed for the CDC-6600 by lcdr L. Pfeifer, NGS Rockville MD,
+        * 20 Feb 1975.
+        *
+        * @param       glat1           The latitude of the starting point, in radians,
+        *                                              positive north.
+        * @param       glon1           The latitude of the starting point, in radians,
+        *                                              positive east.
+        * @param       azimuth         The azimuth to the desired location, in radians
+        *                                              clockwise from north.
+        * @param       dist            The distance to the desired location, in meters.
+        * @param       axis            The semi-major axis of the reference ellipsoid,
+        *                                              in meters.
+        * @param       flat            The flattening of the reference ellipsoid.
+        * @return                              An array containing the latitude and longitude
+        *                                              of the desired point, in radians, and the
+        *                                              azimuth back from that point to the starting
+        *                                              point, in radians clockwise from north.
+        */
+       private static double[] dirct1(double glat1, double glon1,
+                                                                       double azimuth, double dist,
+                                                                       double axis, double flat) {
+               double r = 1.0 - flat;
+               
+               double tu = r * Math.sin(glat1) / Math.cos(glat1);
+               
+               double sf = Math.sin(azimuth);
+               double cf = Math.cos(azimuth);
+               
+               double baz = 0.0;
+               
+               if (cf != 0.0)
+                       baz = Math.atan2(tu, cf) * 2.0;
+               
+               double cu = 1.0 / Math.sqrt(tu * tu + 1.0);
+               double su = tu * cu;
+               double sa = cu * sf;
+               double c2a = -sa * sa + 1.0;
+               
+               double x = Math.sqrt((1.0 / r / r - 1.0) * c2a + 1.0) + 1.0;
+               x = (x - 2.0) / x;
+               double c = 1.0 - x;
+               c = (x * x / 4.0 + 1) / c;
+               double d = (0.375 * x * x - 1.0) * x;
+               tu = dist / r / axis / c;
+               double y = tu;
+               
+               double sy, cy, cz, e;
+               do {
+                       sy = Math.sin(y);
+                       cy = Math.cos(y);
+                       cz = Math.cos(baz + y);
+                       e = cz * cz * 2.0 - 1.0;
+                       
+                       c = y;
+                       x = e * cy;
+                       y = e + e - 1.0;
+                       y = (((sy * sy * 4.0 - 3.0) * y * cz * d / 6.0 + x) *
+                                                                       d / 4.0 - cz) * sy * d + tu;
+               } while (Math.abs(y - c) > PRECISION_LIMIT);
+               
+               baz = cu * cy * cf - su * sy;
+               c = r * Math.sqrt(sa * sa + baz * baz);
+               d = su * cy + cu * sy * cf;
+               double glat2 = Math.atan2(d, c);
+               c = cu * cy - su * sy * cf;
+               x = Math.atan2(sy * sf, c);
+               c = ((-3.0 * c2a + 4.0) * flat + 4.0) * c2a * flat / 16.0;
+               d = ((e * cy * c + cz) * sy * c + y) * sa;
+               double glon2 = glon1 + x - (1.0 - c) * d * flat;
+               baz = Math.atan2(sa, baz) + Math.PI;
+               
+               double[] ret = new double[3];
+               ret[0] = glat2;
+               ret[1] = glon2;
+               ret[2] = baz;
+               return ret;
+       }
+       
+
+}
diff --git a/src/net/sf/openrocket/util/WorldCoordinate.java b/src/net/sf/openrocket/util/WorldCoordinate.java
new file mode 100644 (file)
index 0000000..04a88f7
--- /dev/null
@@ -0,0 +1,85 @@
+package net.sf.openrocket.util;
+
+/**
+ * A WorldCoordinate contains the latitude, longitude and altitude position of a rocket.
+ */
+public class WorldCoordinate {
+       
+       /** Mean Earth radius */
+       public static final double REARTH = 6371000.0;
+       /** Sidearial Earth rotation rate  */
+       public static final double EROT = 7.2921150e-5;
+       
+
+       private final double lat, lon, alt;
+       
+       /**
+        * Constructs a new WorldCoordinate
+        * @param lat latitude in degrees north. From -90 to 90, values outside are clamped.
+        * @param lon longitude in degrees east. From -180 to 180, values outside are reduced to the range.
+        * @param alt altitude in m. Unbounded.
+        */
+       public WorldCoordinate(double lat, double lon, double alt) {
+               this.lat = MathUtil.clamp(Math.toRadians(lat), -Math.PI / 2, Math.PI / 2);
+               this.lon = MathUtil.reduce180(Math.toRadians(lon));
+               this.alt = alt;
+       }
+       
+       
+
+       public double getAltitude() {
+               return this.alt;
+       }
+       
+       /*
+        * Returns Longitude in radians
+        */
+       public double getLongitudeRad() {
+               return this.lon;
+       }
+       
+       /*
+        * Returns Longitude in degrees
+        */
+       public double getLongitudeDeg() {
+               return Math.toDegrees(this.lon);
+       }
+       
+       /*
+        * Returns latitude in radians
+        */
+       public double getLatitudeRad() {
+               return this.lat;
+       }
+       
+       /*
+        * Returns latitude in degrees
+        */
+       public double getLatitudeDeg() {
+               return Math.toDegrees(this.lat);
+       }
+       
+       
+       @Override
+       public String toString() {
+               return "WorldCoordinate[lat=" + lat + ", lon=" + lon + ", alt=" + alt + "]";
+       }
+       
+       
+
+       @Override
+       public boolean equals(Object obj) {
+               if (!(obj instanceof WorldCoordinate)) {
+                       return false;
+               }
+               WorldCoordinate other = (WorldCoordinate) obj;
+               return (MathUtil.equals(this.lat, other.lat) &&
+                               MathUtil.equals(this.lon, other.lon) && MathUtil.equals(this.alt, other.alt));
+       }
+       
+       @Override
+       public int hashCode() {
+               return ((int) (1000 * lat * lon * alt));
+       }
+       
+}
diff --git a/test/net/sf/openrocket/util/GeodeticComputationStrategyTest.java b/test/net/sf/openrocket/util/GeodeticComputationStrategyTest.java
new file mode 100644 (file)
index 0000000..7adf21c
--- /dev/null
@@ -0,0 +1,51 @@
+package net.sf.openrocket.util;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class GeodeticComputationStrategyTest {
+       
+       @Test
+       public void testAddCoordinate() {
+               
+               double arcmin = (1.0 / 60.0);
+               double arcsec = (1.0 / (60.0 * 60.0));
+               
+               double lat1 = 50.0 + 3 * arcmin + 59 * arcsec;
+               double lon1 = -1.0 * (5 + 42 * arcmin + 53 * arcsec); //W 
+               
+               double lat2 = 58 + 38 * arcmin + 38 * arcsec;
+               double lon2 = -1.0 * (3 + 4 * arcmin + 12 * arcsec);
+               
+               double range = 968.9 * 1000.0;
+               double bearing = (9.0 + 7 * arcmin + 11 * arcsec) * (Math.PI / 180.0);
+               
+               Coordinate coord = new Coordinate(range * Math.sin(bearing), range * Math.cos(bearing), 1000.0);
+               WorldCoordinate wc = new WorldCoordinate(lat1, lon1, 0.0);
+               wc = GeodeticComputationStrategy.SPHERICAL.addCoordinate(wc, coord);
+               
+               System.out.println(wc.getLatitudeDeg());
+               System.out.println(lat2);
+               
+               System.out.println(wc.getLongitudeDeg());
+               System.out.println(lon2);
+               
+               assertEquals(lat2, wc.getLatitudeDeg(), 0.001);
+               assertEquals(lon2, wc.getLongitudeDeg(), 0.001);
+               assertEquals(1000.0, wc.getAltitude(), 0.0);
+       }
+       
+       @Test
+       public void testGetCoriolisAcceleration1() {
+               
+               // For positive latitude and rotational velocity, a movement due east results in an acceleration due south
+               Coordinate velocity = new Coordinate(-1000, 0, 0);
+               WorldCoordinate wc = new WorldCoordinate(45, 0, 0);
+               double north_accel = GeodeticComputationStrategy.SPHERICAL.getCoriolisAcceleration(wc, velocity).y;
+               System.out.println("North accel " + north_accel);
+               assertTrue(north_accel < 0.0);
+               
+       }
+       
+}
diff --git a/test/net/sf/openrocket/util/WorldCoordinateTest.java b/test/net/sf/openrocket/util/WorldCoordinateTest.java
new file mode 100644 (file)
index 0000000..ce67dc1
--- /dev/null
@@ -0,0 +1,47 @@
+package net.sf.openrocket.util;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class WorldCoordinateTest {
+       
+       private static final double EPS = 1e-10;
+       
+       @Test
+       public void testConstructor() {
+               WorldCoordinate wc;
+               
+               wc = new WorldCoordinate(10, 15, 130);
+               assertEquals(10, wc.getLatitudeDeg(), EPS);
+               assertEquals(15, wc.getLongitudeDeg(), EPS);
+               assertEquals(130, wc.getAltitude(), 0);
+               
+               wc = new WorldCoordinate(100, 190, 13000);
+               assertEquals(90, wc.getLatitudeDeg(), EPS);
+               assertEquals(-170, wc.getLongitudeDeg(), EPS);
+               assertEquals(13000, wc.getAltitude(), 0);
+               
+               wc = new WorldCoordinate(-100, -200, -13000);
+               assertEquals(-90, wc.getLatitudeDeg(), EPS);
+               assertEquals(160, wc.getLongitudeDeg(), EPS);
+               assertEquals(-13000, wc.getAltitude(), 0);
+       }
+       
+       @Test
+       public void testGetLatitude() {
+               WorldCoordinate wc;
+               wc = new WorldCoordinate(10, 15, 130);
+               assertEquals(10, wc.getLatitudeDeg(), EPS);
+               assertEquals(Math.toRadians(10), wc.getLatitudeRad(), EPS);
+       }
+       
+       @Test
+       public void testGetLongitude() {
+               WorldCoordinate wc;
+               wc = new WorldCoordinate(10, 15, 130);
+               assertEquals(15, wc.getLongitudeDeg(), EPS);
+               assertEquals(Math.toRadians(15), wc.getLongitudeRad(), EPS);
+       }
+       
+}
index ba061b5dcab364ba7b838cc97b32c72408af25bc..c4e340b48562947d10fddfdeb2c8c2ee5b213fb8 100644 (file)
@@ -65,8 +65,8 @@
     <div class="separated">
       <p>
        <span class="licenseimage"><a rel="license" href="http://creativecommons.org/licenses/by-sa/3.0/"><img alt="CC BY-SA" src="cc-by-sa-80x15.png" /></a></span>
-       <a href="techdoc.pdf">OpenRocket technical documentation</a> (2010-04-06)
-       &nbsp;&nbsp; <span class="note">(PDF&nbsp;1.3MB)</span>
+       <a href="techdoc.pdf">OpenRocket technical documentation</a> (2011-07-18)
+       &nbsp;&nbsp; <span class="note">(PDF&nbsp;1.4MB)</span>
       </p>
       <p>
        <span class="licenseimage"><a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/"><img alt="CC BY-NC-ND" src="cc-by-nc-nd-80x15.png" /></a></span>
index 2a21c45aa47ed797a4c97d6f444fb020b82331cc..1731f3f3e3ece047eaabdf1d1bedbea26b06c6ed 100644 (file)
@@ -32,8 +32,8 @@
     <div class="separated">
       <p>
        <span class="licenseimage"><a rel="license" href="http://creativecommons.org/licenses/by-sa/3.0/"><img alt="CC BY-SA" src="cc-by-sa-80x15.png" /></a></span>
-       <a href="techdoc.pdf">OpenRocket technical documentation</a> (2010-04-06)
-       &nbsp;&nbsp; <span class="note">(PDF&nbsp;1.3MB)</span>
+       <a href="techdoc.pdf">OpenRocket technical documentation</a> (2011-07-18)
+       &nbsp;&nbsp; <span class="note">(PDF&nbsp;1.4MB)</span>
       </p>
 
       <p>