package net.sf.openrocket.simulation; import java.util.ArrayList; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.sf.openrocket.aerodynamics.WarningSet; import net.sf.openrocket.util.MathUtil; import net.sf.openrocket.util.Mutable; /** * A collection of various flight data. This is the result of a simulation, or importing * data into the software. The data includes: * <ul> * <li>A number of generally interesting values of a simulation, such as max. altitude and velocity * <li>A number (or zero) of flight data branches containing the actual data * <li>A WarningSet including warnings that occurred during simulation * </ul> * <p> * A FlightData object can be made immutable by calling {@link #immute()}. * * @author Sampo Niskanen <sampo.niskanen@iki.fi> */ public class FlightData { private static final Logger log = LoggerFactory.getLogger(FlightData.class); /** * An immutable FlightData object with NaN data. */ public static final FlightData NaN_DATA; static { FlightData data = new FlightData(); data.immute(); NaN_DATA = data; } private Mutable mutable = new Mutable(); private final ArrayList<FlightDataBranch> branches = new ArrayList<FlightDataBranch>(); private final WarningSet warnings = new WarningSet(); private double maxAltitude = Double.NaN; private double maxVelocity = Double.NaN; private double maxAcceleration = Double.NaN; private double maxMachNumber = Double.NaN; private double timeToApogee = Double.NaN; private double flightTime = Double.NaN; private double groundHitVelocity = Double.NaN; private double launchRodVelocity = Double.NaN; private double deploymentVelocity = Double.NaN; /** * Create a FlightData object with no content. The resulting object is mutable. */ public FlightData() { } /** * Construct a FlightData object with no data branches but the specified * summary information. The resulting object is mutable. * * @param maxAltitude maximum altitude. * @param maxVelocity maximum velocity. * @param maxAcceleration maximum acceleration. * @param maxMachNumber maximum Mach number. * @param timeToApogee time to apogee. * @param flightTime total flight time. * @param groundHitVelocity ground hit velocity. * @param launchRodVelocity velocity at launch rod clearance * @param deploymentVelocity velocity at deployment */ public FlightData(double maxAltitude, double maxVelocity, double maxAcceleration, double maxMachNumber, double timeToApogee, double flightTime, double groundHitVelocity, double launchRodVelocity, double deploymentVelocity) { this.maxAltitude = maxAltitude; this.maxVelocity = maxVelocity; this.maxAcceleration = maxAcceleration; this.maxMachNumber = maxMachNumber; this.timeToApogee = timeToApogee; this.flightTime = flightTime; this.groundHitVelocity = groundHitVelocity; this.launchRodVelocity = launchRodVelocity; this.deploymentVelocity = deploymentVelocity; } /** * Create a FlightData object with the specified branches. The resulting object is mutable. * * @param branches the branches. */ public FlightData(FlightDataBranch... branches) { this(); for (FlightDataBranch b : branches) this.addBranch(b); calculateIntrestingValues(); } /** * Returns the warning set associated with this object. This WarningSet cannot be * set, so simulations must use this warning set to store their warnings. * The returned WarningSet should not be modified otherwise. * * @return the warnings generated during this simulation. */ public WarningSet getWarningSet() { return warnings; } public void addBranch(FlightDataBranch branch) { mutable.check(); branch.immute(); branches.add(branch); if (branches.size() == 1) { calculateIntrestingValues(); } } public int getBranchCount() { return branches.size(); } public FlightDataBranch getBranch(int n) { return branches.get(n); } public double getMaxAltitude() { return maxAltitude; } public double getMaxVelocity() { return maxVelocity; } /** * NOTE: This value only takes into account flight phase. */ public double getMaxAcceleration() { return maxAcceleration; } public double getMaxMachNumber() { return maxMachNumber; } public double getTimeToApogee() { return timeToApogee; } public double getFlightTime() { return flightTime; } public double getGroundHitVelocity() { return groundHitVelocity; } public double getLaunchRodVelocity() { return launchRodVelocity; } public double getDeploymentVelocity() { return deploymentVelocity; } /** * Calculate the max. altitude/velocity/acceleration, time to apogee, flight time * and ground hit velocity. */ private void calculateIntrestingValues() { if (branches.isEmpty()) return; FlightDataBranch branch = branches.get(0); maxAltitude = branch.getMaximum(FlightDataType.TYPE_ALTITUDE); maxVelocity = branch.getMaximum(FlightDataType.TYPE_VELOCITY_TOTAL); maxMachNumber = branch.getMaximum(FlightDataType.TYPE_MACH_NUMBER); flightTime = branch.getLast(FlightDataType.TYPE_TIME); if (branch.getLast(FlightDataType.TYPE_ALTITUDE) < 10) { groundHitVelocity = branch.getLast(FlightDataType.TYPE_VELOCITY_TOTAL); } else { groundHitVelocity = Double.NaN; } // Time to apogee List<Double> time = branch.get(FlightDataType.TYPE_TIME); List<Double> altitude = branch.get(FlightDataType.TYPE_ALTITUDE); if (time == null || altitude == null) { timeToApogee = Double.NaN; maxAcceleration = Double.NaN; return; } int index = 0; for (Double alt : altitude) { if (alt != null) { if (MathUtil.equals(alt, maxAltitude)) break; } index++; } if (index < time.size()) timeToApogee = time.get(index); else timeToApogee = Double.NaN; // Launch rod velocity for (FlightEvent event : branch.getEvents()) { if (event.getType() == FlightEvent.Type.LAUNCHROD) { double t = event.getTime(); List<Double> velocity = branch.get(FlightDataType.TYPE_VELOCITY_TOTAL); launchRodVelocity = MathUtil.interpolate( time, velocity, t); } else if ( event.getType() == FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT) { double t = event.getTime(); List<Double> velocity = branch.get(FlightDataType.TYPE_VELOCITY_TOTAL); deploymentVelocity = MathUtil.interpolate( time, velocity, t); } } // Max. acceleration (must be after apogee time) if (branch.get(FlightDataType.TYPE_ACCELERATION_TOTAL) != null) { maxAcceleration = calculateMaxAcceleration(); } else { maxAcceleration = Double.NaN; } log.debug("Computed flight values:" + " maxAltitude=" + maxAltitude + " maxVelocity=" + maxVelocity + " maxAcceleration=" + maxAcceleration + " maxMachNumber=" + maxMachNumber + " timeToApogee=" + timeToApogee + " flightTime=" + flightTime + " groundHitVelocity=" + groundHitVelocity + " launchRodVelocity=" + launchRodVelocity); } public void immute() { mutable.immute(); warnings.immute(); for (FlightDataBranch b : branches) { b.immute(); } } public boolean isMutable() { return mutable.isMutable(); } /** * Find the maximum acceleration before apogee. */ private double calculateMaxAcceleration() { // End check at first recovery device deployment double endTime = Double.MAX_VALUE; FlightDataBranch branch = this.getBranch(0); for (FlightEvent event : branch.getEvents()) { if (event.getType() == FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT) { if (event.getTime() < endTime) { endTime = event.getTime(); } } } List<Double> time = branch.get(FlightDataType.TYPE_TIME); List<Double> acceleration = branch.get(FlightDataType.TYPE_ACCELERATION_TOTAL); if (time == null || acceleration == null) { return Double.NaN; } double max = 0; for (int i = 0; i < time.size(); i++) { if (time.get(i) >= endTime) { break; } double a = acceleration.get(i); if (a > max) max = a; } return max; } }