From 601e76c57135056b8e0997019c49f3c695d61e45 Mon Sep 17 00:00:00 2001 From: plaa Date: Sun, 28 Aug 2011 13:14:27 +0000 Subject: [PATCH] geodetic computations git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@165 180e2498-e6e9-4542-8430-84ac67f01cd8 --- ChangeLog | 4 + l10n/messages.properties | 17 + .../openrocket/gui/adaptors/DoubleModel.java | 2 +- .../gui/main/SimulationEditDialog.java | 434 +++++++++--------- .../models/gravity/BasicGravityModel.java | 15 +- .../models/gravity/GravityModel.java | 17 +- .../models/gravity/WGSGravityModel.java | 43 ++ .../simulation/AbstractSimulationStepper.java | 9 +- .../BasicEventSimulationEngine.java | 2 +- .../openrocket/simulation/FlightDataType.java | 11 +- .../simulation/RK4SimulationStepper.java | 27 +- .../simulation/SimulationConditions.java | 48 +- .../simulation/SimulationOptions.java | 54 ++- .../simulation/SimulationStatus.java | 11 + src/net/sf/openrocket/unit/UnitGroup.java | 2 + .../util/GeodeticComputationStrategy.java | 265 +++++++++++ .../sf/openrocket/util/WorldCoordinate.java | 85 ++++ .../util/GeodeticComputationStrategyTest.java | 51 ++ .../openrocket/util/WorldCoordinateTest.java | 47 ++ web/html/documentation.html | 4 +- web/htp/documentation.htp | 4 +- 21 files changed, 892 insertions(+), 260 deletions(-) create mode 100644 src/net/sf/openrocket/models/gravity/WGSGravityModel.java create mode 100644 src/net/sf/openrocket/util/GeodeticComputationStrategy.java create mode 100644 src/net/sf/openrocket/util/WorldCoordinate.java create mode 100644 test/net/sf/openrocket/util/GeodeticComputationStrategyTest.java create mode 100644 test/net/sf/openrocket/util/WorldCoordinateTest.java diff --git a/ChangeLog b/ChangeLog index 51b08c9e..334f0c9a 100644 --- 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 diff --git a/l10n/messages.properties b/l10n/messages.properties index 34e12adb..49ff9976 100644 --- a/l10n/messages.properties +++ b/l10n/messages.properties @@ -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 = The launch site latitude affects the gravitational pull of Earth.
Positive values are on the Northern hemisphere, negative values on the Southern hemisphere. + +simedtdlg.lbl.Longitude = Longitude: +simedtdlg.lbl.ttip.Longitude = Required for weather prediction and elevation models. + simedtdlg.lbl.Altitude = Altitude: simedtdlg.lbl.ttip.Altitude = The launch altitude above mean sea level.
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 = The six degree-of-freedom simulator allows the rocket total freedom during flight.
simedtdlg.lbl.ttip.Simmethod2 = Integration is performed using a 4th 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 = The time between simulation steps.
A smaller time step results in a more accurate but slower simulation.
simedtdlg.lbl.ttip.Timestep2 = The 4th 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 = Perform geodetic computations assuming a spherical Earth.
This is sufficiently accurate for almost all purposes. +GeodeticComputationStrategy.wgs84.name = WGS84 ellipsoid +GeodeticComputationStrategy.wgs84.desc = Perform geodetic computations on the WGS84 reference ellipsoid using Vincenty's method.
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 diff --git a/src/net/sf/openrocket/gui/adaptors/DoubleModel.java b/src/net/sf/openrocket/gui/adaptors/DoubleModel.java index e1fc3071..3b4e0dae 100644 --- a/src/net/sf/openrocket/gui/adaptors/DoubleModel.java +++ b/src/net/sf/openrocket/gui/adaptors/DoubleModel.java @@ -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) diff --git a/src/net/sf/openrocket/gui/main/SimulationEditDialog.java b/src/net/sf/openrocket/gui/main/SimulationEditDialog.java index 90561b49..92c2dac8 100644 --- a/src/net/sf/openrocket/gui/main/SimulationEditDialog.java +++ b/src/net/sf/openrocket/gui/main/SimulationEditDialog.java @@ -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 { ////
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")); //// The launch site latitude affects the gravitational pull of Earth.
@@ -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")); //// The launch altitude above mean sea level.
@@ -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")); //// Direction of the launch rod relative to the wind.
@@ -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 - //// The Extended Barrowman method calculates aerodynamic forces according
- //// 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 - //// The six degree-of-freedom simulator allows the rocket total freedom during flight.
- //// Integration is performed using a 4th 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 gcsModel = new EnumModel(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")); - //// The time between simulation steps.
- //// A smaller time step results in a more accurate but slower simulation.
- //// The 4th 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 = "" + - "This defines the maximum angle the rocket will turn during one time step.
"+ - "Smaller values result in a more accurate but possibly slower simulation.
"+ - "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("Reset the step value to its default:
" + -// "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); //// Simulation listeners 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:", - "" + CSVSaveListener.class.getName() + "" }, + //// Type the full Java class name of the simulation listener, for example: + "Type the full Java class name of the simulation listener, for example:", + "" + CSVSaveListener.class.getName() + "" }, //// 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 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 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 y = branch.get(type); XYSeries series = new XYSeries(name, false, true); - for (int j=0; j 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("Unable to instantiate listener due to exception:
" + ex.toString()); } - + if (isSelected) { setBackground(list.getSelectionBackground()); setForeground(list.getSelectionForeground()); diff --git a/src/net/sf/openrocket/models/gravity/BasicGravityModel.java b/src/net/sf/openrocket/models/gravity/BasicGravityModel.java index a216528b..80b65ea2 100644 --- a/src/net/sf/openrocket/models/gravity/BasicGravityModel.java +++ b/src/net/sf/openrocket/models/gravity/BasicGravityModel.java @@ -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 */ + 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; + } } diff --git a/src/net/sf/openrocket/models/gravity/GravityModel.java b/src/net/sf/openrocket/models/gravity/GravityModel.java index bf38d520..7feba892 100644 --- a/src/net/sf/openrocket/models/gravity/GravityModel.java +++ b/src/net/sf/openrocket/models/gravity/GravityModel.java @@ -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 */ -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 index 00000000..3312df2f --- /dev/null +++ b/src/net/sf/openrocket/models/gravity/WGSGravityModel.java @@ -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 + */ + +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; + } + +} diff --git a/src/net/sf/openrocket/simulation/AbstractSimulationStepper.java b/src/net/sf/openrocket/simulation/AbstractSimulationStepper.java index e39d6e6a..ea94f3b4 100644 --- a/src/net/sf/openrocket/simulation/AbstractSimulationStepper.java +++ b/src/net/sf/openrocket/simulation/AbstractSimulationStepper.java @@ -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); diff --git a/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java b/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java index 7bc635c7..a37a823e 100644 --- a/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java +++ b/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java @@ -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); diff --git a/src/net/sf/openrocket/simulation/FlightDataType.java b/src/net/sf/openrocket/simulation/FlightDataType.java index eea84dde..26862420 100644 --- a/src/net/sf/openrocket/simulation/FlightDataType.java +++ b/src/net/sf/openrocket/simulation/FlightDataType.java @@ -22,7 +22,7 @@ import net.sf.openrocket.unit.UnitGroup; */ public class FlightDataType implements Comparable { 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 { 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 { //// 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 { //// 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); diff --git a/src/net/sf/openrocket/simulation/RK4SimulationStepper.java b/src/net/sf/openrocket/simulation/RK4SimulationStepper.java index 07020f22..4aa9494c 100644 --- a/src/net/sf/openrocket/simulation/RK4SimulationStepper.java +++ b/src/net/sf/openrocket/simulation/RK4SimulationStepper.java @@ -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; diff --git a/src/net/sf/openrocket/simulation/SimulationConditions.java b/src/net/sf/openrocket/simulation/SimulationConditions.java index 3e582086..f2412717 100644 --- a/src/net/sf/openrocket/simulation/SimulationConditions.java +++ b/src/net/sf/openrocket/simulation/SimulationConditions.java @@ -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 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()); } diff --git a/src/net/sf/openrocket/simulation/SimulationOptions.java b/src/net/sf/openrocket/simulation/SimulationOptions.java index a2b39854..d9398693 100644 --- a/src/net/sf/openrocket/simulation/SimulationOptions.java +++ b/src/net/sf/openrocket/simulation/SimulationOptions.java @@ -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()); diff --git a/src/net/sf/openrocket/simulation/SimulationStatus.java b/src/net/sf/openrocket/simulation/SimulationStatus.java index cc77117b..9a39cffd 100644 --- a/src/net/sf/openrocket/simulation/SimulationStatus.java +++ b/src/net/sf/openrocket/simulation/SimulationStatus.java @@ -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; diff --git a/src/net/sf/openrocket/unit/UnitGroup.java b/src/net/sf/openrocket/unit/UnitGroup.java index 920e7664..4f1c5b13 100644 --- a/src/net/sf/openrocket/unit/UnitGroup.java +++ b/src/net/sf/openrocket/unit/UnitGroup.java @@ -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 index 00000000..8e7d2f29 --- /dev/null +++ b/src/net/sf/openrocket/util/GeodeticComputationStrategy.java @@ -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 index 00000000..04a88f71 --- /dev/null +++ b/src/net/sf/openrocket/util/WorldCoordinate.java @@ -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 index 00000000..7adf21c8 --- /dev/null +++ b/test/net/sf/openrocket/util/GeodeticComputationStrategyTest.java @@ -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 index 00000000..ce67dc11 --- /dev/null +++ b/test/net/sf/openrocket/util/WorldCoordinateTest.java @@ -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); + } + +} diff --git a/web/html/documentation.html b/web/html/documentation.html index ba061b5d..c4e340b4 100644 --- a/web/html/documentation.html +++ b/web/html/documentation.html @@ -65,8 +65,8 @@

CC BY-SA - OpenRocket technical documentation (2010-04-06) -    (PDF 1.3MB) + OpenRocket technical documentation (2011-07-18) +    (PDF 1.4MB)

CC BY-NC-ND diff --git a/web/htp/documentation.htp b/web/htp/documentation.htp index 2a21c45a..1731f3f3 100644 --- a/web/htp/documentation.htp +++ b/web/htp/documentation.htp @@ -32,8 +32,8 @@

CC BY-SA - OpenRocket technical documentation (2010-04-06) -    (PDF 1.3MB) + OpenRocket technical documentation (2011-07-18) +    (PDF 1.4MB)

-- 2.30.2