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.Locale;
12 import java.util.zip.GZIPOutputStream;
14 import net.sf.openrocket.aerodynamics.Warning;
15 import net.sf.openrocket.document.OpenRocketDocument;
16 import net.sf.openrocket.document.Simulation;
17 import net.sf.openrocket.document.StorageOptions;
18 import net.sf.openrocket.rocketcomponent.Rocket;
19 import net.sf.openrocket.rocketcomponent.RocketComponent;
20 import net.sf.openrocket.simulation.FlightData;
21 import net.sf.openrocket.simulation.FlightDataBranch;
22 import net.sf.openrocket.simulation.FlightEvent;
23 import net.sf.openrocket.simulation.SimulationConditions;
24 import net.sf.openrocket.util.MathUtil;
25 import net.sf.openrocket.util.Pair;
26 import net.sf.openrocket.util.Prefs;
27 import net.sf.openrocket.util.Reflection;
29 public class OpenRocketSaver extends RocketSaver {
31 /* Remember to update OpenRocketLoader as well! */
32 public static final String FILE_VERSION = "1.0";
34 private static final String OPENROCKET_CHARSET = "UTF-8";
36 private static final String METHOD_PACKAGE = "net.sf.openrocket.file.openrocket";
37 private static final String METHOD_SUFFIX = "Saver";
40 // Estimated storage used by different portions
41 // These have been hand-estimated from saved files
42 private static final int BYTES_PER_COMPONENT_UNCOMPRESSED = 590;
43 private static final int BYTES_PER_COMPONENT_COMPRESSED = 80;
44 private static final int BYTES_PER_SIMULATION_UNCOMPRESSED = 1000;
45 private static final int BYTES_PER_SIMULATION_COMPRESSED = 100;
46 private static final int BYTES_PER_DATAPOINT_UNCOMPRESSED = 350;
47 private static final int BYTES_PER_DATAPOINT_COMPRESSED = 100;
54 public void save(OutputStream output, OpenRocketDocument document, StorageOptions options)
57 if (options.isCompressionEnabled()) {
58 output = new GZIPOutputStream(output);
61 dest = new BufferedWriter(new OutputStreamWriter(output, OPENROCKET_CHARSET));
66 System.out.println("Writing...");
68 writeln("<?xml version='1.0' encoding='utf-8'?>");
69 writeln("<openrocket version=\""+FILE_VERSION+"\" creator=\"OpenRocket "
70 +Prefs.getVersion()+ "\">");
73 // Recursively save the rocket structure
74 saveComponent(document.getRocket());
78 // Save all simulations
79 writeln("<simulations>");
82 for (Simulation s: document.getSimulations()) {
86 saveSimulation(s, options.getSimulationTimeSkip());
89 writeln("</simulations>");
92 writeln("</openrocket>");
95 if (output instanceof GZIPOutputStream)
96 ((GZIPOutputStream)output).finish();
102 public long estimateFileSize(OpenRocketDocument doc, StorageOptions options) {
106 // Size per component
107 int componentCount = 0;
108 Rocket rocket = doc.getRocket();
109 Iterator<RocketComponent> iterator = rocket.deepIterator(true);
110 while (iterator.hasNext()) {
115 if (options.isCompressionEnabled())
116 size += componentCount * BYTES_PER_COMPONENT_COMPRESSED;
118 size += componentCount * BYTES_PER_COMPONENT_UNCOMPRESSED;
121 // Size per simulation
122 if (options.isCompressionEnabled())
123 size += doc.getSimulationCount() * BYTES_PER_SIMULATION_COMPRESSED;
125 size += doc.getSimulationCount() * BYTES_PER_SIMULATION_UNCOMPRESSED;
128 // Size per flight data point
130 double timeSkip = options.getSimulationTimeSkip();
131 if (timeSkip != StorageOptions.SIMULATION_DATA_NONE) {
132 for (Simulation s: doc.getSimulations()) {
133 FlightData data = s.getSimulatedData();
134 for (int i=0; i < data.getBranchCount(); i++) {
135 pointCount += countFlightDataBranchPoints(data.getBranch(i), timeSkip);
140 if (options.isCompressionEnabled())
141 size += pointCount * BYTES_PER_DATAPOINT_COMPRESSED;
143 size += pointCount * BYTES_PER_DATAPOINT_UNCOMPRESSED;
150 @SuppressWarnings("unchecked")
151 private void saveComponent(RocketComponent component) throws IOException {
153 Reflection.Method m = Reflection.findMethod(METHOD_PACKAGE, component, METHOD_SUFFIX,
154 "getElements", RocketComponent.class);
156 throw new RuntimeException("Unable to find saving class for component "+
157 component.getComponentName());
160 // Get the strings to save
161 List<String> list = (List<String>) m.invokeStatic(component);
162 int length = list.size();
164 if (length == 0) // Nothing to do
168 throw new RuntimeException("BUG, component data length less than two lines.");
172 writeln(list.get(0));
176 for (int i=1; i<length-1; i++) {
177 writeln(list.get(i));
180 // Recursively write subcomponents
181 if (component.getChildCount() > 0) {
183 writeln("<subcomponents>");
185 boolean emptyline = false;
186 for (RocketComponent subcomponent: component) {
190 saveComponent(subcomponent);
193 writeln("</subcomponents>");
198 writeln(list.get(length-1));
203 private void saveSimulation(Simulation simulation, double timeSkip) throws IOException {
204 SimulationConditions cond = simulation.getConditions();
206 writeln("<simulation status=\"" + enumToXMLName(simulation.getStatus()) +"\">");
209 writeln("<name>" + escapeXML(simulation.getName()) + "</name>");
210 // TODO: MEDIUM: Other simulators/calculators
211 writeln("<simulator>RK4Simulator</simulator>");
212 writeln("<calculator>BarrowmanCalculator</calculator>");
213 writeln("<conditions>");
216 writeElement("configid", cond.getMotorConfigurationID());
217 writeElement("launchrodlength", cond.getLaunchRodLength());
218 writeElement("launchrodangle", cond.getLaunchRodAngle() * 180.0/Math.PI);
219 writeElement("launchroddirection", cond.getLaunchRodDirection() * 180.0/Math.PI);
220 writeElement("windaverage", cond.getWindSpeedAverage());
221 writeElement("windturbulence", cond.getWindTurbulenceIntensity());
222 writeElement("launchaltitude", cond.getLaunchAltitude());
223 writeElement("launchlatitude", cond.getLaunchLatitude());
225 if (cond.isISAAtmosphere()) {
226 writeln("<atmosphere model=\"isa\"/>");
228 writeln("<atmosphere model=\"extendedisa\">");
230 writeElement("basetemperature", cond.getLaunchTemperature());
231 writeElement("basepressure", cond.getLaunchPressure());
233 writeln("</atmosphere>");
236 writeElement("timestep", cond.getTimeStep());
239 writeln("</conditions>");
242 for (String s: simulation.getSimulationListeners()) {
243 writeElement("listener", escapeXML(s));
247 // Write basic simulation data
249 FlightData data = simulation.getSimulatedData();
251 String str = "<flightdata";
252 if (!Double.isNaN(data.getMaxAltitude()))
253 str += " maxaltitude=\"" + doubleToString(data.getMaxAltitude()) + "\"";
254 if (!Double.isNaN(data.getMaxVelocity()))
255 str += " maxvelocity=\"" + doubleToString(data.getMaxVelocity()) + "\"";
256 if (!Double.isNaN(data.getMaxAcceleration()))
257 str += " maxacceleration=\"" + doubleToString(data.getMaxAcceleration()) + "\"";
258 if (!Double.isNaN(data.getMaxMachNumber()))
259 str += " maxmach=\"" + doubleToString(data.getMaxMachNumber()) + "\"";
260 if (!Double.isNaN(data.getTimeToApogee()))
261 str += " timetoapogee=\"" + doubleToString(data.getTimeToApogee()) + "\"";
262 if (!Double.isNaN(data.getFlightTime()))
263 str += " flighttime=\"" + doubleToString(data.getFlightTime()) + "\"";
264 if (!Double.isNaN(data.getGroundHitVelocity()))
265 str += " groundhitvelocity=\"" + doubleToString(data.getGroundHitVelocity()) + "\"";
270 for (Warning w: data.getWarningSet()) {
271 writeElement("warning", escapeXML(w.toString()));
274 // Check whether to store data
275 if (simulation.getStatus() == Simulation.Status.EXTERNAL) // Always store external data
278 if (timeSkip != StorageOptions.SIMULATION_DATA_NONE) {
279 for (int i=0; i<data.getBranchCount(); i++) {
280 FlightDataBranch branch = data.getBranch(i);
281 saveFlightDataBranch(branch, timeSkip);
286 writeln("</flightdata>");
290 writeln("</simulation>");
296 private void saveFlightDataBranch(FlightDataBranch branch, double timeSkip)
298 double previousTime = -100000;
303 // Retrieve the types from the branch
304 FlightDataBranch.Type[] types = branch.getTypes();
306 if (types.length == 0)
309 // Retrieve the data from the branch
310 List<List<Double>> data = new ArrayList<List<Double>>(types.length);
311 for (int i=0; i<types.length; i++) {
312 data.add(branch.get(types[i]));
314 List<Double> timeData = branch.get(FlightDataBranch.TYPE_TIME);
315 if (timeData == null) {
316 // TODO: MEDIUM: External data may not have time data
317 throw new IllegalArgumentException("Data did not contain time data");
320 // Build the <databranch> tag
321 StringBuilder sb = new StringBuilder();
322 sb.append("<databranch name=\"");
323 sb.append(escapeXML(branch.getBranchName()));
324 sb.append("\" types=\"");
325 for (int i=0; i<types.length; i++) {
328 sb.append(escapeXML(types[i].getName()));
331 writeln(sb.toString());
335 for (Pair<Double,FlightEvent> p: branch.getEvents()) {
336 writeln("<event time=\"" + doubleToString(p.getU())
337 + "\" type=\"" + enumToXMLName(p.getV().getType()) + "\"/>");
341 int length = branch.getLength();
343 writeDataPointString(data, 0, sb);
344 previousTime = timeData.get(0);
347 for (int i=1; i < length-1; i++) {
348 if (Math.abs(timeData.get(i) - previousTime - timeSkip) <
349 Math.abs(timeData.get(i+1) - previousTime - timeSkip)) {
350 writeDataPointString(data, i, sb);
351 previousTime = timeData.get(i);
356 writeDataPointString(data, length-1, sb);
360 writeln("</databranch>");
365 /* TODO: LOW: This is largely duplicated from above! */
366 private int countFlightDataBranchPoints(FlightDataBranch branch, double timeSkip) {
369 double previousTime = -100000;
374 // Retrieve the types from the branch
375 FlightDataBranch.Type[] types = branch.getTypes();
377 if (types.length == 0)
380 List<Double> timeData = branch.get(FlightDataBranch.TYPE_TIME);
381 if (timeData == null) {
382 // TODO: MEDIUM: External data may not have time data
383 throw new IllegalArgumentException("Data did not contain time data");
387 int length = branch.getLength();
390 previousTime = timeData.get(0);
393 for (int i=1; i < length-1; i++) {
394 if (Math.abs(timeData.get(i) - previousTime - timeSkip) <
395 Math.abs(timeData.get(i+1) - previousTime - timeSkip)) {
397 previousTime = timeData.get(i);
410 private void writeDataPointString(List<List<Double>> data, int index, StringBuilder sb)
413 sb.append("<datapoint>");
414 for (int j=0; j < data.size(); j++) {
417 sb.append(doubleToString(data.get(j).get(index)));
419 sb.append("</datapoint>");
420 writeln(sb.toString());
425 private void writeElement(String element, Object content) throws IOException {
428 writeln("<"+element+">"+content+"</"+element+">");
433 private void writeln(String str) throws IOException {
434 if (str.length() == 0) {
439 for (int i=0; i<indent; i++)
447 * Return a string of the double value with suitable precision.
448 * The string is the shortest representation of the value including the
449 * required precision.
451 * @param d the value to present.
452 * @return a representation with suitable precision.
454 public static final String doubleToString(double d) {
456 // Check for special cases
457 if (MathUtil.equals(d, 0))
463 if (Double.isInfinite(d)) {
471 double abs = Math.abs(d);
474 // Compact exponential notation
482 String sign = (d < 0) ? "-" : "";
483 return sign + String.format((Locale)null, "%.4fe-%d", abs, exp);
486 return String.format((Locale)null, "%.7f", d);
488 return String.format((Locale)null, "%.6f", d);
490 return String.format((Locale)null, "%.5f", d);
492 return String.format((Locale)null, "%.4f", d);
494 return String.format((Locale)null, "%.3f", d);
496 return String.format((Locale)null, "%.2f", d);
498 return String.format((Locale)null, "%.1f", d);
499 if (abs < 100000000.0)
500 return String.format((Locale)null, "%.0f", d);
502 // Compact exponential notation
504 while (abs >= 10.0) {
509 String sign = (d < 0) ? "-" : "";
510 return sign + String.format((Locale)null, "%.4fe%d", abs, exp);
515 public static void main(String[] arg) {
516 double d = -0.000000123456789123;
519 for (int i=0; i< 20; i++) {
520 String str = doubleToString(d);
521 System.out.println(str + " -> " + Double.parseDouble(str));
526 System.out.println("Value: "+ Double.parseDouble("1.2345e9"));
532 * Return the XML equivalent of an enum name.
534 * @param e the enum to save.
535 * @return the corresponding XML name.
537 public static String enumToXMLName(Enum<?> e) {
538 return e.name().toLowerCase().replace("_", "");