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;
10 import java.util.Locale;
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.RocketComponent;
18 import net.sf.openrocket.simulation.FlightData;
19 import net.sf.openrocket.simulation.FlightDataBranch;
20 import net.sf.openrocket.simulation.FlightEvent;
21 import net.sf.openrocket.simulation.SimulationConditions;
22 import net.sf.openrocket.util.MathUtil;
23 import net.sf.openrocket.util.Pair;
24 import net.sf.openrocket.util.Prefs;
25 import net.sf.openrocket.util.Reflection;
27 public class OpenRocketSaver extends RocketSaver {
29 /* Remember to update OpenRocketLoader as well! */
30 public static final String FILE_VERSION = "1.0";
32 private static final String OPENROCKET_CHARSET = "UTF-8";
34 private static final String METHOD_PACKAGE = "net.sf.openrocket.file.openrocket";
35 private static final String METHOD_SUFFIX = "Saver";
41 public void save(OutputStream output, OpenRocketDocument document, StorageOptions options)
44 if (options.isCompressionEnabled()) {
45 output = new GZIPOutputStream(output);
48 dest = new BufferedWriter(new OutputStreamWriter(output, OPENROCKET_CHARSET));
53 System.out.println("Writing...");
55 writeln("<?xml version='1.0' encoding='utf-8'?>");
56 writeln("<openrocket version=\""+FILE_VERSION+"\" creator=\"OpenRocket "
57 +Prefs.getVersion()+ "\">");
60 // Recursively save the rocket structure
61 saveComponent(document.getRocket());
65 // Save all simulations
66 writeln("<simulations>");
69 for (Simulation s: document.getSimulations()) {
73 saveSimulation(s, options.getSimulationTimeSkip());
76 writeln("</simulations>");
79 writeln("</openrocket>");
82 if (output instanceof GZIPOutputStream)
83 ((GZIPOutputStream)output).finish();
88 @SuppressWarnings("unchecked")
89 private void saveComponent(RocketComponent component) throws IOException {
91 Reflection.Method m = Reflection.findMethod(METHOD_PACKAGE, component, METHOD_SUFFIX,
92 "getElements", RocketComponent.class);
94 throw new RuntimeException("Unable to find saving class for component "+
95 component.getComponentName());
98 // Get the strings to save
99 List<String> list = (List<String>) m.invokeStatic(component);
100 int length = list.size();
102 if (length == 0) // Nothing to do
106 throw new RuntimeException("BUG, component data length less than two lines.");
110 writeln(list.get(0));
114 for (int i=1; i<length-1; i++) {
115 writeln(list.get(i));
118 // Recursively write subcomponents
119 if (component.getChildCount() > 0) {
121 writeln("<subcomponents>");
123 boolean emptyline = false;
124 for (RocketComponent subcomponent: component) {
128 saveComponent(subcomponent);
131 writeln("</subcomponents>");
136 writeln(list.get(length-1));
141 private void saveSimulation(Simulation simulation, double timeSkip) throws IOException {
142 SimulationConditions cond = simulation.getConditions();
144 writeln("<simulation status=\"" + enumToXMLName(simulation.getStatus()) +"\">");
147 writeln("<name>" + escapeXML(simulation.getName()) + "</name>");
148 // TODO: MEDIUM: Other simulators/calculators
149 writeln("<simulator>RK4Simulator</simulator>");
150 writeln("<calculator>BarrowmanCalculator</calculator>");
151 writeln("<conditions>");
154 writeElement("configid", cond.getMotorConfigurationID());
155 writeElement("launchrodlength", cond.getLaunchRodLength());
156 writeElement("launchrodangle", cond.getLaunchRodAngle() * 180.0/Math.PI);
157 writeElement("launchroddirection", cond.getLaunchRodDirection() * 180.0/Math.PI);
158 writeElement("windaverage", cond.getWindSpeedAverage());
159 writeElement("windturbulence", cond.getWindTurbulenceIntensity());
160 writeElement("launchaltitude", cond.getLaunchAltitude());
161 writeElement("launchlatitude", cond.getLaunchLatitude());
163 if (cond.isISAAtmosphere()) {
164 writeln("<atmosphere model=\"isa\"/>");
166 writeln("<atmosphere model=\"extendedisa\">");
168 writeElement("basetemperature", cond.getLaunchTemperature());
169 writeElement("basepressure", cond.getLaunchPressure());
171 writeln("</atmosphere>");
174 writeElement("timestep", cond.getTimeStep());
177 writeln("</conditions>");
180 for (String s: simulation.getSimulationListeners()) {
181 writeElement("listener", escapeXML(s));
185 // Write basic simulation data
187 FlightData data = simulation.getSimulatedData();
189 String str = "<flightdata";
190 if (!Double.isNaN(data.getMaxAltitude()))
191 str += " maxaltitude=\"" + doubleToString(data.getMaxAltitude()) + "\"";
192 if (!Double.isNaN(data.getMaxVelocity()))
193 str += " maxvelocity=\"" + doubleToString(data.getMaxVelocity()) + "\"";
194 if (!Double.isNaN(data.getMaxAcceleration()))
195 str += " maxacceleration=\"" + doubleToString(data.getMaxAcceleration()) + "\"";
196 if (!Double.isNaN(data.getMaxMachNumber()))
197 str += " maxmach=\"" + doubleToString(data.getMaxMachNumber()) + "\"";
198 if (!Double.isNaN(data.getTimeToApogee()))
199 str += " timetoapogee=\"" + doubleToString(data.getTimeToApogee()) + "\"";
200 if (!Double.isNaN(data.getFlightTime()))
201 str += " flighttime=\"" + doubleToString(data.getFlightTime()) + "\"";
202 if (!Double.isNaN(data.getGroundHitVelocity()))
203 str += " groundhitvelocity=\"" + doubleToString(data.getGroundHitVelocity()) + "\"";
208 for (Warning w: data.getWarningSet()) {
209 writeElement("warning", escapeXML(w.toString()));
212 // Check whether to store data
213 if (simulation.getStatus() == Simulation.Status.EXTERNAL) // Always store external data
216 if (timeSkip != StorageOptions.SIMULATION_DATA_NONE) {
217 for (int i=0; i<data.getBranchCount(); i++) {
218 FlightDataBranch branch = data.getBranch(i);
219 saveFlightDataBranch(branch, timeSkip);
224 writeln("</flightdata>");
228 writeln("</simulation>");
234 private void saveFlightDataBranch(FlightDataBranch branch, double timeSkip) throws IOException {
235 double previousTime = -100;
240 // Retrieve the types from the branch
241 FlightDataBranch.Type[] types = branch.getTypes();
243 if (types.length == 0)
246 // Retrieve the data from the branch
247 List<List<Double>> data = new ArrayList<List<Double>>(types.length);
248 for (int i=0; i<types.length; i++) {
249 data.add(branch.get(types[i]));
251 List<Double> timeData = branch.get(FlightDataBranch.TYPE_TIME);
252 if (timeData == null) {
253 // TODO: MEDIUM: External data may not have time data
254 throw new IllegalArgumentException("Data did not contain time data");
257 // Build the <databranch> tag
258 StringBuilder sb = new StringBuilder();
259 sb.append("<databranch name=\"");
260 sb.append(escapeXML(branch.getBranchName()));
261 sb.append("\" types=\"");
262 for (int i=0; i<types.length; i++) {
265 sb.append(escapeXML(types[i].getName()));
268 writeln(sb.toString());
272 for (Pair<Double,FlightEvent> p: branch.getEvents()) {
273 writeln("<event time=\"" + doubleToString(p.getU())
274 + "\" type=\"" + enumToXMLName(p.getV().getType()) + "\"/>");
278 int length = branch.getLength();
280 writeDataPointString(data, 0, sb);
281 previousTime = timeData.get(0);
284 for (int i=1; i < length-1; i++) {
285 if (Math.abs(timeData.get(i) - previousTime - timeSkip) <
286 Math.abs(timeData.get(i+1) - previousTime - timeSkip)) {
287 writeDataPointString(data, i, sb);
288 previousTime = timeData.get(i);
293 writeDataPointString(data, length-1, sb);
297 writeln("</databranch>");
300 private void writeDataPointString(List<List<Double>> data, int index, StringBuilder sb)
303 sb.append("<datapoint>");
304 for (int j=0; j < data.size(); j++) {
307 sb.append(doubleToString(data.get(j).get(index)));
309 sb.append("</datapoint>");
310 writeln(sb.toString());
315 private void writeElement(String element, Object content) throws IOException {
318 writeln("<"+element+">"+content+"</"+element+">");
323 private void writeln(String str) throws IOException {
324 if (str.length() == 0) {
329 for (int i=0; i<indent; i++)
337 * Return a string of the double value with suitable precision.
338 * The string is the shortest representation of the value including the
339 * required precision.
341 * @param d the value to present.
342 * @return a representation with suitable precision.
344 public static final String doubleToString(double d) {
346 // Check for special cases
347 if (MathUtil.equals(d, 0))
353 if (Double.isInfinite(d)) {
361 double abs = Math.abs(d);
364 // Compact exponential notation
372 String sign = (d < 0) ? "-" : "";
373 return sign + String.format((Locale)null, "%.4fe-%d", abs, exp);
376 return String.format((Locale)null, "%.7f", d);
378 return String.format((Locale)null, "%.6f", d);
380 return String.format((Locale)null, "%.5f", d);
382 return String.format((Locale)null, "%.4f", d);
384 return String.format((Locale)null, "%.3f", d);
386 return String.format((Locale)null, "%.2f", d);
388 return String.format((Locale)null, "%.1f", d);
389 if (abs < 100000000.0)
390 return String.format((Locale)null, "%.0f", d);
392 // Compact exponential notation
394 while (abs >= 10.0) {
399 String sign = (d < 0) ? "-" : "";
400 return sign + String.format((Locale)null, "%.4fe%d", abs, exp);
405 public static void main(String[] arg) {
406 double d = -0.000000123456789123;
409 for (int i=0; i< 20; i++) {
410 String str = doubleToString(d);
411 System.out.println(str + " -> " + Double.parseDouble(str));
416 System.out.println("Value: "+ Double.parseDouble("1.2345e9"));
422 * Return the XML equivalent of an enum name.
424 * @param e the enum to save.
425 * @return the corresponding XML name.
427 public static String enumToXMLName(Enum<?> e) {
428 return e.name().toLowerCase().replace("_", "");