import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.document.StorageOptions;
import net.sf.openrocket.file.RocketSaver;
+import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.rocketcomponent.FinSet;
+import net.sf.openrocket.rocketcomponent.MotorMount;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.rocketcomponent.TubeCoupler;
import net.sf.openrocket.simulation.FlightData;
import net.sf.openrocket.simulation.FlightDataBranch;
+import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.FlightEvent;
-import net.sf.openrocket.simulation.SimulationConditions;
+import net.sf.openrocket.simulation.SimulationOptions;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.MathUtil;
-import net.sf.openrocket.util.Pair;
import net.sf.openrocket.util.Prefs;
import net.sf.openrocket.util.Reflection;
import net.sf.openrocket.util.TextUtil;
public class OpenRocketSaver extends RocketSaver {
+ private static final LogHelper log = Application.getLogger();
+
/**
* Divisor used in converting an integer version to the point-represented version.
* The integer version divided by this value is the major version and the remainder is
* the minor version. For example 101 corresponds to file version "1.1".
*/
public static final int FILE_VERSION_DIVISOR = 100;
-
+
private static final String OPENROCKET_CHARSET = "UTF-8";
private static final String METHOD_PACKAGE = "net.sf.openrocket.file.openrocket.savers";
private static final String METHOD_SUFFIX = "Saver";
-
+
// Estimated storage used by different portions
// These have been hand-estimated from saved files
private static final int BYTES_PER_COMPONENT_UNCOMPRESSED = 590;
private static final int BYTES_PER_DATAPOINT_UNCOMPRESSED = 350;
private static final int BYTES_PER_DATAPOINT_COMPRESSED = 100;
-
+
private int indent;
private Writer dest;
@Override
public void save(OutputStream output, OpenRocketDocument document, StorageOptions options)
- throws IOException {
+ throws IOException {
+
+ log.info("Saving .ork file");
if (options.isCompressionEnabled()) {
+ log.debug("Enabling compression");
output = new GZIPOutputStream(output);
}
- dest = new BufferedWriter(new OutputStreamWriter(output, OPENROCKET_CHARSET));
+ dest = new BufferedWriter(new OutputStreamWriter(output, OPENROCKET_CHARSET));
+ // Select file version number
final int fileVersion = calculateNecessaryFileVersion(document, options);
- final String fileVersionString =
- (fileVersion / FILE_VERSION_DIVISOR) + "." + (fileVersion % FILE_VERSION_DIVISOR);
-
+ final String fileVersionString =
+ (fileVersion / FILE_VERSION_DIVISOR) + "." + (fileVersion % FILE_VERSION_DIVISOR);
+ log.debug("Storing file version " + fileVersionString);
+
this.indent = 0;
- System.out.println("Writing...");
-
+
writeln("<?xml version='1.0' encoding='utf-8'?>");
writeln("<openrocket version=\"" + fileVersionString + "\" creator=\"OpenRocket "
+ Prefs.getVersion() + "\">");
writeln("<simulations>");
indent++;
boolean first = true;
- for (Simulation s: document.getSimulations()) {
+ for (Simulation s : document.getSimulations()) {
if (!first)
writeln("");
first = false;
indent--;
writeln("</openrocket>");
+ log.debug("Writing complete, flushing buffers");
dest.flush();
if (options.isCompressionEnabled()) {
- ((GZIPOutputStream)output).finish();
+ ((GZIPOutputStream) output).finish();
}
}
-
+
@Override
public long estimateFileSize(OpenRocketDocument doc, StorageOptions options) {
// Size per component
int componentCount = 0;
Rocket rocket = doc.getRocket();
- Iterator<RocketComponent> iterator = rocket.deepIterator(true);
+ Iterator<RocketComponent> iterator = rocket.iterator(true);
while (iterator.hasNext()) {
iterator.next();
componentCount++;
else
size += componentCount * BYTES_PER_COMPONENT_UNCOMPRESSED;
-
+
// Size per simulation
if (options.isCompressionEnabled())
size += doc.getSimulationCount() * BYTES_PER_SIMULATION_COMPRESSED;
else
size += doc.getSimulationCount() * BYTES_PER_SIMULATION_UNCOMPRESSED;
-
+
// Size per flight data point
int pointCount = 0;
double timeSkip = options.getSimulationTimeSkip();
if (timeSkip != StorageOptions.SIMULATION_DATA_NONE) {
- for (Simulation s: doc.getSimulations()) {
+ for (Simulation s : doc.getSimulations()) {
FlightData data = s.getSimulatedData();
if (data != null) {
- for (int i=0; i < data.getBranchCount(); i++) {
+ for (int i = 0; i < data.getBranchCount(); i++) {
pointCount += countFlightDataBranchPoints(data.getBranch(i), timeSkip);
}
}
return size;
}
-
+
/**
* Determine which file version is required in order to store all the features of the
* current design. By default the oldest version that supports all the necessary features
*/
private int calculateNecessaryFileVersion(OpenRocketDocument document, StorageOptions opts) {
/*
+ * File version 1.2 is required for:
+ * - saving motor data
+ *
* File version 1.1 is required for:
* - fin tabs
* - components attached to tube coupler
*
* Otherwise use version 1.0.
*/
+
+ // Check if design has simulations defined (version 1.3)
+ if (document.getSimulationCount() > 0) {
+ return FILE_VERSION_DIVISOR + 3;
+ }
+
+ // Check for motor definitions (version 1.2)
+ Iterator<RocketComponent> iterator = document.getRocket().iterator();
+ while (iterator.hasNext()) {
+ RocketComponent c = iterator.next();
+ if (!(c instanceof MotorMount))
+ continue;
+
+ MotorMount mount = (MotorMount) c;
+ for (String id : document.getRocket().getMotorConfigurationIDs()) {
+ if (mount.getMotor(id) != null) {
+ return FILE_VERSION_DIVISOR + 2;
+ }
+ }
+ }
// Check for fin tabs (version 1.1)
- Iterator<RocketComponent> iterator = document.getRocket().deepIterator();
+ iterator = document.getRocket().iterator();
while (iterator.hasNext()) {
RocketComponent c = iterator.next();
// Check for fin tabs
if (c instanceof FinSet) {
- FinSet fin = (FinSet)c;
- if (!MathUtil.equals(fin.getTabHeight(),0) &&
+ FinSet fin = (FinSet) c;
+ if (!MathUtil.equals(fin.getTabHeight(), 0) &&
!MathUtil.equals(fin.getTabLength(), 0)) {
return FILE_VERSION_DIVISOR + 1;
}
}
-
+
@SuppressWarnings("unchecked")
private void saveComponent(RocketComponent component) throws IOException {
+ log.debug("Saving component " + component.getComponentName());
+
Reflection.Method m = Reflection.findMethod(METHOD_PACKAGE, component, METHOD_SUFFIX,
"getElements", RocketComponent.class);
- if (m==null) {
- throw new RuntimeException("Unable to find saving class for component "+
+ if (m == null) {
+ throw new BugException("Unable to find saving class for component " +
component.getComponentName());
}
-
+
// Get the strings to save
List<String> list = (List<String>) m.invokeStatic(component);
int length = list.size();
- if (length == 0) // Nothing to do
+ if (length == 0) // Nothing to do
return;
-
+
if (length < 2) {
throw new RuntimeException("BUG, component data length less than two lines.");
}
indent++;
// Write parameters
- for (int i=1; i<length-1; i++) {
+ for (int i = 1; i < length - 1; i++) {
writeln(list.get(i));
}
writeln("<subcomponents>");
indent++;
boolean emptyline = false;
- for (RocketComponent subcomponent: component) {
+ for (RocketComponent subcomponent : component.getChildren()) {
if (emptyline)
writeln("");
emptyline = true;
// Close element
indent--;
- writeln(list.get(length-1));
+ writeln(list.get(length - 1));
}
-
private void saveSimulation(Simulation simulation, double timeSkip) throws IOException {
- SimulationConditions cond = simulation.getConditions();
+ SimulationOptions cond = simulation.getOptions();
- writeln("<simulation status=\"" + enumToXMLName(simulation.getStatus()) +"\">");
+ writeln("<simulation status=\"" + enumToXMLName(simulation.getStatus()) + "\">");
indent++;
writeln("<name>" + escapeXML(simulation.getName()) + "</name>");
writeElement("configid", cond.getMotorConfigurationID());
writeElement("launchrodlength", cond.getLaunchRodLength());
- writeElement("launchrodangle", cond.getLaunchRodAngle() * 180.0/Math.PI);
- writeElement("launchroddirection", cond.getLaunchRodDirection() * 180.0/Math.PI);
+ writeElement("launchrodangle", cond.getLaunchRodAngle() * 180.0 / Math.PI);
+ writeElement("launchroddirection", cond.getLaunchRodDirection() * 180.0 / Math.PI);
writeElement("windaverage", cond.getWindSpeedAverage());
writeElement("windturbulence", cond.getWindTurbulenceIntensity());
writeElement("launchaltitude", cond.getLaunchAltitude());
writeElement("launchlatitude", cond.getLaunchLatitude());
+ writeElement("launchlongitude", cond.getLaunchLongitude());
+ writeElement("geodeticmethod", cond.getGeodeticComputation().name().toLowerCase());
if (cond.isISAAtmosphere()) {
writeln("<atmosphere model=\"isa\"/>");
indent--;
writeln("</atmosphere>");
}
-
+
writeElement("timestep", cond.getTimeStep());
indent--;
writeln("</conditions>");
-
- for (String s: simulation.getSimulationListeners()) {
+
+ for (String s : simulation.getSimulationListeners()) {
writeElement("listener", escapeXML(s));
}
-
+
// Write basic simulation data
FlightData data = simulation.getSimulatedData();
writeln(str);
indent++;
- for (Warning w: data.getWarningSet()) {
+ for (Warning w : data.getWarningSet()) {
writeElement("warning", escapeXML(w.toString()));
}
timeSkip = 0;
if (timeSkip != StorageOptions.SIMULATION_DATA_NONE) {
- for (int i=0; i<data.getBranchCount(); i++) {
+ for (int i = 0; i < data.getBranchCount(); i++) {
FlightDataBranch branch = data.getBranch(i);
saveFlightDataBranch(branch, timeSkip);
}
}
-
- private void saveFlightDataBranch(FlightDataBranch branch, double timeSkip)
- throws IOException {
+
+ private void saveFlightDataBranch(FlightDataBranch branch, double timeSkip)
+ throws IOException {
double previousTime = -100000;
if (branch == null)
return;
// Retrieve the types from the branch
- FlightDataBranch.Type[] types = branch.getTypes();
+ FlightDataType[] types = branch.getTypes();
if (types.length == 0)
return;
// Retrieve the data from the branch
List<List<Double>> data = new ArrayList<List<Double>>(types.length);
- for (int i=0; i<types.length; i++) {
+ for (int i = 0; i < types.length; i++) {
data.add(branch.get(types[i]));
}
- List<Double> timeData = branch.get(FlightDataBranch.TYPE_TIME);
+ List<Double> timeData = branch.get(FlightDataType.TYPE_TIME);
if (timeData == null) {
// TODO: MEDIUM: External data may not have time data
throw new IllegalArgumentException("Data did not contain time data");
sb.append("<databranch name=\"");
sb.append(escapeXML(branch.getBranchName()));
sb.append("\" types=\"");
- for (int i=0; i<types.length; i++) {
+ for (int i = 0; i < types.length; i++) {
if (i > 0)
sb.append(",");
sb.append(escapeXML(types[i].getName()));
indent++;
// Write events
- for (Pair<Double,FlightEvent> p: branch.getEvents()) {
- writeln("<event time=\"" + TextUtil.doubleToString(p.getU())
- + "\" type=\"" + enumToXMLName(p.getV().getType()) + "\"/>");
+ for (FlightEvent event : branch.getEvents()) {
+ writeln("<event time=\"" + TextUtil.doubleToString(event.getTime())
+ + "\" type=\"" + enumToXMLName(event.getType()) + "\"/>");
}
// Write the data
previousTime = timeData.get(0);
}
- for (int i=1; i < length-1; i++) {
- if (Math.abs(timeData.get(i) - previousTime - timeSkip) <
- Math.abs(timeData.get(i+1) - previousTime - timeSkip)) {
+ for (int i = 1; i < length - 1; i++) {
+ if (Math.abs(timeData.get(i) - previousTime - timeSkip) < Math.abs(timeData.get(i + 1) - previousTime - timeSkip)) {
writeDataPointString(data, i, sb);
previousTime = timeData.get(i);
}
}
if (length > 1) {
- writeDataPointString(data, length-1, sb);
+ writeDataPointString(data, length - 1, sb);
}
indent--;
}
-
+
/* TODO: LOW: This is largely duplicated from above! */
private int countFlightDataBranchPoints(FlightDataBranch branch, double timeSkip) {
int count = 0;
-
+
double previousTime = -100000;
if (branch == null)
return 0;
// Retrieve the types from the branch
- FlightDataBranch.Type[] types = branch.getTypes();
+ FlightDataType[] types = branch.getTypes();
if (types.length == 0)
return 0;
- List<Double> timeData = branch.get(FlightDataBranch.TYPE_TIME);
+ List<Double> timeData = branch.get(FlightDataType.TYPE_TIME);
if (timeData == null) {
// TODO: MEDIUM: External data may not have time data
throw new IllegalArgumentException("Data did not contain time data");
previousTime = timeData.get(0);
}
- for (int i=1; i < length-1; i++) {
- if (Math.abs(timeData.get(i) - previousTime - timeSkip) <
- Math.abs(timeData.get(i+1) - previousTime - timeSkip)) {
+ for (int i = 1; i < length - 1; i++) {
+ if (Math.abs(timeData.get(i) - previousTime - timeSkip) < Math.abs(timeData.get(i + 1) - previousTime - timeSkip)) {
count++;
previousTime = timeData.get(i);
}
if (length > 1) {
count++;
}
-
+
return count;
}
-
+
private void writeDataPointString(List<List<Double>> data, int index, StringBuilder sb)
- throws IOException {
+ throws IOException {
sb.setLength(0);
sb.append("<datapoint>");
- for (int j=0; j < data.size(); j++) {
+ for (int j = 0; j < data.size(); j++) {
if (j > 0)
sb.append(",");
sb.append(TextUtil.doubleToString(data.get(j).get(index)));
}
-
+
private void writeElement(String element, Object content) throws IOException {
if (content == null)
content = "";
- writeln("<"+element+">"+content+"</"+element+">");
+ writeln("<" + element + ">" + content + "</" + element + ">");
}
-
-
+
+
private void writeln(String str) throws IOException {
if (str.length() == 0) {
dest.write("\n");
return;
}
- String s="";
- for (int i=0; i<indent; i++)
- s=s+" ";
- s = s+str+"\n";
+ String s = "";
+ for (int i = 0; i < indent; i++)
+ s = s + " ";
+ s = s + str + "\n";
dest.write(s);
}
- public static void main(String[] arg) {
- double d = -0.000000123456789123;
-
-
- for (int i=0; i< 20; i++) {
- String str = TextUtil.doubleToString(d);
- System.out.println(str + " -> " + Double.parseDouble(str));
- d *= 10;
- }
-
-
- System.out.println("Value: "+ Double.parseDouble("1.2345e9"));
-
- }
-
+
/**
* Return the XML equivalent of an enum name.
*