package net.sf.openrocket.simulation; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import net.sf.openrocket.util.ArrayList; import net.sf.openrocket.util.Monitorable; import net.sf.openrocket.util.Mutable; /** * A single branch of flight data. The data is ordered based on some variable, typically time. * It also contains flight events that have occurred during simulation. * <p> * After instantiating a FlightDataBranch data and new variable types can be added to the branch. * A new data point (a value for each variable defined) is created using {@link #addPoint()} after * which the value for each variable type can be set using {@link #setValue(FlightDataType, double)}. * Each variable type does NOT have to be set, unset values will default to NaN. New variable types * not defined in the constructor can be added using {@link #setValue(FlightDataType, double)}, they * will be created and all previous values will be set to NaN. * <p> * After populating a FlightDataBranch object it can be made immutable by calling {@link #immute()}. * * @author Sampo Niskanen <sampo.niskanen@iki.fi> */ public class FlightDataBranch implements Monitorable { /** The name of this flight data branch. */ private final String branchName; private final Map<FlightDataType, ArrayList<Double>> values = new LinkedHashMap<FlightDataType, ArrayList<Double>>(); private final Map<FlightDataType, Double> maxValues = new HashMap<FlightDataType, Double>(); private final Map<FlightDataType, Double> minValues = new HashMap<FlightDataType, Double>(); /** * time for the rocket to reach apogee if the flight had been no recovery deployment */ private double timeToOptimumAltitude = Double.NaN; /** * Altitude the rocket would reach if there had been no recovery deployment. */ private double optimumAltitude = Double.NaN; private final ArrayList<FlightEvent> events = new ArrayList<FlightEvent>(); private Mutable mutable = new Mutable(); private int modID = 0; /** * Sole constructor. Defines the name of the FlightDataBranch and at least one variable type. * * @param name the name of this FlightDataBranch. * @param types data types to include (must include at least one type). */ public FlightDataBranch(String name, FlightDataType... types) { if (types.length == 0) { throw new IllegalArgumentException("Must specify at least one data type."); } this.branchName = name; for (FlightDataType t : types) { if (values.containsKey(t)) { throw new IllegalArgumentException("Value type " + t + " specified multiple " + "times in constructor."); } values.put(t, new ArrayList<Double>()); minValues.put(t, Double.NaN); maxValues.put(t, Double.NaN); } } /** * Makes an 'empty' flight data branch which has no data but all built in data types are defined. */ public FlightDataBranch() { branchName = "Empty branch"; for (FlightDataType type : FlightDataType.ALL_TYPES) { this.setValue(type, Double.NaN); } this.immute(); } /** * Adds a new point into the data branch. The value for all types is set to NaN by default. * * @throws IllegalStateException if this object has been made immutable. */ public void addPoint() { mutable.check(); for (FlightDataType t : values.keySet()) { values.get(t).add(Double.NaN); } modID++; } /** * Set the value for a specific data type at the latest point. New variable types can be * added to the FlightDataBranch transparently. * * @param type the variable to set. * @param value the value to set. * @throws IllegalStateException if this object has been made immutable. */ public void setValue(FlightDataType type, double value) { mutable.check(); ArrayList<Double> list = values.get(type); if (list == null) { list = new ArrayList<Double>(); int n = getLength(); for (int i = 0; i < n; i++) { list.add(Double.NaN); } values.put(type, list); minValues.put(type, value); maxValues.put(type, value); } if (list.size() > 0) { list.set(list.size() - 1, value); } double min = minValues.get(type); double max = maxValues.get(type); if (Double.isNaN(min) || (value < min)) { minValues.put(type, value); } if (Double.isNaN(max) || (value > max)) { maxValues.put(type, value); } modID++; } /** * Return the branch name. */ public String getBranchName() { return branchName; } /** * Return the variable types included in this branch. The types are sorted in their * natural order. */ public FlightDataType[] getTypes() { FlightDataType[] array = values.keySet().toArray(new FlightDataType[0]); Arrays.sort(array); return array; } /** * Return the number of data points in this branch. */ public int getLength() { for (FlightDataType t : values.keySet()) { return values.get(t).size(); } return 0; } /** * Return an array of values for the specified variable type. * * @param type the variable type. * @return a list of the variable values, or <code>null</code> if * the variable type hasn't been added to this branch. */ public List<Double> get(FlightDataType type) { ArrayList<Double> list = values.get(type); if (list == null) return null; return list.clone(); } /** * Return the last value of the specified type in the branch, or NaN if the type is * unavailable. * * @param type the parameter type. * @return the last value in this branch, or NaN. */ public double getLast(FlightDataType type) { ArrayList<Double> list = values.get(type); if (list == null || list.isEmpty()) return Double.NaN; return list.get(list.size() - 1); } /** * Return the minimum value of the specified type in the branch, or NaN if the type * is unavailable. * * @param type the parameter type. * @return the minimum value in this branch, or NaN. */ public double getMinimum(FlightDataType type) { Double v = minValues.get(type); if (v == null) return Double.NaN; return v; } /** * Return the maximum value of the specified type in the branch, or NaN if the type * is unavailable. * * @param type the parameter type. * @return the maximum value in this branch, or NaN. */ public double getMaximum(FlightDataType type) { Double v = maxValues.get(type); if (v == null) return Double.NaN; return v; } /** * @return the timeToOptimumAltitude */ public double getTimeToOptimumAltitude() { return timeToOptimumAltitude; } /** * @param timeToOptimumAltitude the timeToOptimumAltitude to set */ public void setTimeToOptimumAltitude(double timeToOptimumAltitude) { this.timeToOptimumAltitude = timeToOptimumAltitude; } /** * @return the optimumAltitude */ public double getOptimumAltitude() { return optimumAltitude; } /** * @param optimumAltitude the optimumAltitude to set */ public void setOptimumAltitude(double optimumAltitude) { this.optimumAltitude = optimumAltitude; } public double getOptimumDelay() { if (Double.isNaN(timeToOptimumAltitude)) { return Double.NaN; } // TODO - we really want the first burnout of this stage. which // could be computed as the first burnout after the last stage separation event. // however, that's not quite so concise FlightEvent e = getLastEvent(FlightEvent.Type.BURNOUT); if (e != null) { return timeToOptimumAltitude - e.getTime(); } return Double.NaN; } /** * Add a flight event to this branch. * * @param event the event to add. * @throws IllegalStateException if this branch has been made immutable. */ public void addEvent(FlightEvent event) { mutable.check(); events.add(event.resetSourceAndData()); modID++; } /** * Return the list of events. * * @return the list of events during the flight. */ public List<FlightEvent> getEvents() { return events.clone(); } /** * Return the first event of the given type. * @param type * @return */ public FlightEvent getFirstEvent(FlightEvent.Type type) { for (FlightEvent e : events) { if (e.getType() == type) { return e; } } return null; } /** * Return the last event of the given type. * @param type * @return */ public FlightEvent getLastEvent(FlightEvent.Type type) { FlightEvent retval = null; for (FlightEvent e : events) { if (e.getType() == type) { retval = e; } } return retval; } /** * Make this FlightDataBranch immutable. Any calls to the set methods that would * modify this object will after this call throw an <code>IllegalStateException</code>. */ public void immute() { mutable.immute(); } /** * Return whether this branch is still mutable. */ public boolean isMutable() { return mutable.isMutable(); } @Override public int getModID() { return modID; } }