1 package net.sf.openrocket.file;
3 import java.io.BufferedWriter;
4 import java.io.IOException;
5 import java.io.OutputStream;
6 import java.io.OutputStreamWriter;
8 import java.util.ArrayList;
9 import java.util.Iterator;
10 import java.util.List;
11 import java.util.zip.GZIPOutputStream;
13 import net.sf.openrocket.aerodynamics.Warning;
14 import net.sf.openrocket.document.OpenRocketDocument;
15 import net.sf.openrocket.document.Simulation;
16 import net.sf.openrocket.document.StorageOptions;
17 import net.sf.openrocket.rocketcomponent.Rocket;
18 import net.sf.openrocket.rocketcomponent.RocketComponent;
19 import net.sf.openrocket.simulation.FlightData;
20 import net.sf.openrocket.simulation.FlightDataBranch;
21 import net.sf.openrocket.simulation.FlightEvent;
22 import net.sf.openrocket.simulation.SimulationConditions;
23 import net.sf.openrocket.util.Pair;
24 import net.sf.openrocket.util.Prefs;
25 import net.sf.openrocket.util.Reflection;
26 import net.sf.openrocket.util.TextUtil;
28 public class OpenRocketSaver extends RocketSaver {
30 /* Remember to update OpenRocketLoader as well! */
31 public static final String FILE_VERSION = "1.0";
33 private static final String OPENROCKET_CHARSET = "UTF-8";
35 private static final String METHOD_PACKAGE = "net.sf.openrocket.file.openrocket";
36 private static final String METHOD_SUFFIX = "Saver";
39 // Estimated storage used by different portions
40 // These have been hand-estimated from saved files
41 private static final int BYTES_PER_COMPONENT_UNCOMPRESSED = 590;
42 private static final int BYTES_PER_COMPONENT_COMPRESSED = 80;
43 private static final int BYTES_PER_SIMULATION_UNCOMPRESSED = 1000;
44 private static final int BYTES_PER_SIMULATION_COMPRESSED = 100;
45 private static final int BYTES_PER_DATAPOINT_UNCOMPRESSED = 350;
46 private static final int BYTES_PER_DATAPOINT_COMPRESSED = 100;
53 public void save(OutputStream output, OpenRocketDocument document, StorageOptions options)
56 if (options.isCompressionEnabled()) {
57 output = new GZIPOutputStream(output);
60 dest = new BufferedWriter(new OutputStreamWriter(output, OPENROCKET_CHARSET));
65 System.out.println("Writing...");
67 writeln("<?xml version='1.0' encoding='utf-8'?>");
68 writeln("<openrocket version=\""+FILE_VERSION+"\" creator=\"OpenRocket "
69 +Prefs.getVersion()+ "\">");
72 // Recursively save the rocket structure
73 saveComponent(document.getRocket());
77 // Save all simulations
78 writeln("<simulations>");
81 for (Simulation s: document.getSimulations()) {
85 saveSimulation(s, options.getSimulationTimeSkip());
88 writeln("</simulations>");
91 writeln("</openrocket>");
94 if (output instanceof GZIPOutputStream)
95 ((GZIPOutputStream)output).finish();
101 public long estimateFileSize(OpenRocketDocument doc, StorageOptions options) {
105 // Size per component
106 int componentCount = 0;
107 Rocket rocket = doc.getRocket();
108 Iterator<RocketComponent> iterator = rocket.deepIterator(true);
109 while (iterator.hasNext()) {
114 if (options.isCompressionEnabled())
115 size += componentCount * BYTES_PER_COMPONENT_COMPRESSED;
117 size += componentCount * BYTES_PER_COMPONENT_UNCOMPRESSED;
120 // Size per simulation
121 if (options.isCompressionEnabled())
122 size += doc.getSimulationCount() * BYTES_PER_SIMULATION_COMPRESSED;
124 size += doc.getSimulationCount() * BYTES_PER_SIMULATION_UNCOMPRESSED;
127 // Size per flight data point
129 double timeSkip = options.getSimulationTimeSkip();
130 if (timeSkip != StorageOptions.SIMULATION_DATA_NONE) {
131 for (Simulation s: doc.getSimulations()) {
132 FlightData data = s.getSimulatedData();
134 for (int i=0; i < data.getBranchCount(); i++) {
135 pointCount += countFlightDataBranchPoints(data.getBranch(i), timeSkip);
141 if (options.isCompressionEnabled())
142 size += pointCount * BYTES_PER_DATAPOINT_COMPRESSED;
144 size += pointCount * BYTES_PER_DATAPOINT_UNCOMPRESSED;
151 @SuppressWarnings("unchecked")
152 private void saveComponent(RocketComponent component) throws IOException {
154 Reflection.Method m = Reflection.findMethod(METHOD_PACKAGE, component, METHOD_SUFFIX,
155 "getElements", RocketComponent.class);
157 throw new RuntimeException("Unable to find saving class for component "+
158 component.getComponentName());
161 // Get the strings to save
162 List<String> list = (List<String>) m.invokeStatic(component);
163 int length = list.size();
165 if (length == 0) // Nothing to do
169 throw new RuntimeException("BUG, component data length less than two lines.");
173 writeln(list.get(0));
177 for (int i=1; i<length-1; i++) {
178 writeln(list.get(i));
181 // Recursively write subcomponents
182 if (component.getChildCount() > 0) {
184 writeln("<subcomponents>");
186 boolean emptyline = false;
187 for (RocketComponent subcomponent: component) {
191 saveComponent(subcomponent);
194 writeln("</subcomponents>");
199 writeln(list.get(length-1));
204 private void saveSimulation(Simulation simulation, double timeSkip) throws IOException {
205 SimulationConditions cond = simulation.getConditions();
207 writeln("<simulation status=\"" + enumToXMLName(simulation.getStatus()) +"\">");
210 writeln("<name>" + escapeXML(simulation.getName()) + "</name>");
211 // TODO: MEDIUM: Other simulators/calculators
212 writeln("<simulator>RK4Simulator</simulator>");
213 writeln("<calculator>BarrowmanCalculator</calculator>");
214 writeln("<conditions>");
217 writeElement("configid", cond.getMotorConfigurationID());
218 writeElement("launchrodlength", cond.getLaunchRodLength());
219 writeElement("launchrodangle", cond.getLaunchRodAngle() * 180.0/Math.PI);
220 writeElement("launchroddirection", cond.getLaunchRodDirection() * 180.0/Math.PI);
221 writeElement("windaverage", cond.getWindSpeedAverage());
222 writeElement("windturbulence", cond.getWindTurbulenceIntensity());
223 writeElement("launchaltitude", cond.getLaunchAltitude());
224 writeElement("launchlatitude", cond.getLaunchLatitude());
226 if (cond.isISAAtmosphere()) {
227 writeln("<atmosphere model=\"isa\"/>");
229 writeln("<atmosphere model=\"extendedisa\">");
231 writeElement("basetemperature", cond.getLaunchTemperature());
232 writeElement("basepressure", cond.getLaunchPressure());
234 writeln("</atmosphere>");
237 writeElement("timestep", cond.getTimeStep());
240 writeln("</conditions>");
243 for (String s: simulation.getSimulationListeners()) {
244 writeElement("listener", escapeXML(s));
248 // Write basic simulation data
250 FlightData data = simulation.getSimulatedData();
252 String str = "<flightdata";
253 if (!Double.isNaN(data.getMaxAltitude()))
254 str += " maxaltitude=\"" + TextUtil.doubleToString(data.getMaxAltitude()) + "\"";
255 if (!Double.isNaN(data.getMaxVelocity()))
256 str += " maxvelocity=\"" + TextUtil.doubleToString(data.getMaxVelocity()) + "\"";
257 if (!Double.isNaN(data.getMaxAcceleration()))
258 str += " maxacceleration=\"" + TextUtil.doubleToString(data.getMaxAcceleration()) + "\"";
259 if (!Double.isNaN(data.getMaxMachNumber()))
260 str += " maxmach=\"" + TextUtil.doubleToString(data.getMaxMachNumber()) + "\"";
261 if (!Double.isNaN(data.getTimeToApogee()))
262 str += " timetoapogee=\"" + TextUtil.doubleToString(data.getTimeToApogee()) + "\"";
263 if (!Double.isNaN(data.getFlightTime()))
264 str += " flighttime=\"" + TextUtil.doubleToString(data.getFlightTime()) + "\"";
265 if (!Double.isNaN(data.getGroundHitVelocity()))
266 str += " groundhitvelocity=\"" + TextUtil.doubleToString(data.getGroundHitVelocity()) + "\"";
271 for (Warning w: data.getWarningSet()) {
272 writeElement("warning", escapeXML(w.toString()));
275 // Check whether to store data
276 if (simulation.getStatus() == Simulation.Status.EXTERNAL) // Always store external data
279 if (timeSkip != StorageOptions.SIMULATION_DATA_NONE) {
280 for (int i=0; i<data.getBranchCount(); i++) {
281 FlightDataBranch branch = data.getBranch(i);
282 saveFlightDataBranch(branch, timeSkip);
287 writeln("</flightdata>");
291 writeln("</simulation>");
297 private void saveFlightDataBranch(FlightDataBranch branch, double timeSkip)
299 double previousTime = -100000;
304 // Retrieve the types from the branch
305 FlightDataBranch.Type[] types = branch.getTypes();
307 if (types.length == 0)
310 // Retrieve the data from the branch
311 List<List<Double>> data = new ArrayList<List<Double>>(types.length);
312 for (int i=0; i<types.length; i++) {
313 data.add(branch.get(types[i]));
315 List<Double> timeData = branch.get(FlightDataBranch.TYPE_TIME);
316 if (timeData == null) {
317 // TODO: MEDIUM: External data may not have time data
318 throw new IllegalArgumentException("Data did not contain time data");
321 // Build the <databranch> tag
322 StringBuilder sb = new StringBuilder();
323 sb.append("<databranch name=\"");
324 sb.append(escapeXML(branch.getBranchName()));
325 sb.append("\" types=\"");
326 for (int i=0; i<types.length; i++) {
329 sb.append(escapeXML(types[i].getName()));
332 writeln(sb.toString());
336 for (Pair<Double,FlightEvent> p: branch.getEvents()) {
337 writeln("<event time=\"" + TextUtil.doubleToString(p.getU())
338 + "\" type=\"" + enumToXMLName(p.getV().getType()) + "\"/>");
342 int length = branch.getLength();
344 writeDataPointString(data, 0, sb);
345 previousTime = timeData.get(0);
348 for (int i=1; i < length-1; i++) {
349 if (Math.abs(timeData.get(i) - previousTime - timeSkip) <
350 Math.abs(timeData.get(i+1) - previousTime - timeSkip)) {
351 writeDataPointString(data, i, sb);
352 previousTime = timeData.get(i);
357 writeDataPointString(data, length-1, sb);
361 writeln("</databranch>");
366 /* TODO: LOW: This is largely duplicated from above! */
367 private int countFlightDataBranchPoints(FlightDataBranch branch, double timeSkip) {
370 double previousTime = -100000;
375 // Retrieve the types from the branch
376 FlightDataBranch.Type[] types = branch.getTypes();
378 if (types.length == 0)
381 List<Double> timeData = branch.get(FlightDataBranch.TYPE_TIME);
382 if (timeData == null) {
383 // TODO: MEDIUM: External data may not have time data
384 throw new IllegalArgumentException("Data did not contain time data");
388 int length = branch.getLength();
391 previousTime = timeData.get(0);
394 for (int i=1; i < length-1; i++) {
395 if (Math.abs(timeData.get(i) - previousTime - timeSkip) <
396 Math.abs(timeData.get(i+1) - previousTime - timeSkip)) {
398 previousTime = timeData.get(i);
411 private void writeDataPointString(List<List<Double>> data, int index, StringBuilder sb)
414 sb.append("<datapoint>");
415 for (int j=0; j < data.size(); j++) {
418 sb.append(TextUtil.doubleToString(data.get(j).get(index)));
420 sb.append("</datapoint>");
421 writeln(sb.toString());
426 private void writeElement(String element, Object content) throws IOException {
429 writeln("<"+element+">"+content+"</"+element+">");
434 private void writeln(String str) throws IOException {
435 if (str.length() == 0) {
440 for (int i=0; i<indent; i++)
447 public static void main(String[] arg) {
448 double d = -0.000000123456789123;
451 for (int i=0; i< 20; i++) {
452 String str = TextUtil.doubleToString(d);
453 System.out.println(str + " -> " + Double.parseDouble(str));
458 System.out.println("Value: "+ Double.parseDouble("1.2345e9"));
464 * Return the XML equivalent of an enum name.
466 * @param e the enum to save.
467 * @return the corresponding XML name.
469 public static String enumToXMLName(Enum<?> e) {
470 return e.name().toLowerCase().replace("_", "");