970aa2a82c86dac3fdf53be4d358378b771ec861
[debian/openrocket] / src / net / sf / openrocket / file / OpenRocketSaver.java
1 package net.sf.openrocket.file;
2
3 import java.io.BufferedWriter;
4 import java.io.IOException;
5 import java.io.OutputStream;
6 import java.io.OutputStreamWriter;
7 import java.io.Writer;
8 import java.util.ArrayList;
9 import java.util.List;
10 import java.util.Locale;
11 import java.util.zip.GZIPOutputStream;
12
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;
26
27 public class OpenRocketSaver extends RocketSaver {
28         
29         /* Remember to update OpenRocketLoader as well! */
30         public static final String FILE_VERSION = "1.0";
31         
32         private static final String OPENROCKET_CHARSET = "UTF-8";
33         
34         private static final String METHOD_PACKAGE = "net.sf.openrocket.file.openrocket";
35         private static final String METHOD_SUFFIX = "Saver";
36         
37         private int indent;
38         private Writer dest;
39         
40         @Override
41         public void save(OutputStream output, OpenRocketDocument document, StorageOptions options)
42         throws IOException {
43                 
44                 if (options.isCompressionEnabled()) {
45                         output = new GZIPOutputStream(output);
46                 }
47                 
48                 dest = new BufferedWriter(new OutputStreamWriter(output, OPENROCKET_CHARSET)); 
49                 
50                 
51                 this.indent = 0;
52                 
53                 System.out.println("Writing...");
54                 
55                 writeln("<?xml version='1.0' encoding='utf-8'?>");
56                 writeln("<openrocket version=\""+FILE_VERSION+"\" creator=\"OpenRocket "
57                                 +Prefs.getVersion()+ "\">");
58                 indent++;
59                 
60                 // Recursively save the rocket structure
61                 saveComponent(document.getRocket());
62                 
63                 writeln("");
64                 
65                 // Save all simulations
66                 writeln("<simulations>");
67                 indent++;
68                 boolean first = true;
69                 for (Simulation s: document.getSimulations()) {
70                         if (!first)
71                                 writeln("");
72                         first = false;
73                         saveSimulation(s, options.getSimulationTimeSkip());
74                 }
75                 indent--;
76                 writeln("</simulations>");
77                 
78                 indent--;
79                 writeln("</openrocket>");
80                 
81                 dest.flush();
82                 if (output instanceof GZIPOutputStream)
83                         ((GZIPOutputStream)output).finish();
84         }
85         
86
87         
88         @SuppressWarnings("unchecked")
89         private void saveComponent(RocketComponent component) throws IOException {
90                 
91                 Reflection.Method m = Reflection.findMethod(METHOD_PACKAGE, component, METHOD_SUFFIX,
92                                 "getElements", RocketComponent.class);
93                 if (m==null) {
94                         throw new RuntimeException("Unable to find saving class for component "+
95                                         component.getComponentName());
96                 }
97
98                 // Get the strings to save
99                 List<String> list = (List<String>) m.invokeStatic(component);
100                 int length = list.size();
101                 
102                 if (length == 0)  // Nothing to do
103                         return;
104
105                 if (length < 2) {
106                         throw new RuntimeException("BUG, component data length less than two lines.");
107                 }
108                 
109                 // Open element
110                 writeln(list.get(0));
111                 indent++;
112                 
113                 // Write parameters
114                 for (int i=1; i<length-1; i++) {
115                         writeln(list.get(i));
116                 }
117                 
118                 // Recursively write subcomponents
119                 if (component.getChildCount() > 0) {
120                         writeln("");
121                         writeln("<subcomponents>");
122                         indent++;
123                         boolean emptyline = false;
124                         for (RocketComponent subcomponent: component) {
125                                 if (emptyline)
126                                         writeln("");
127                                 emptyline = true;
128                                 saveComponent(subcomponent);
129                         }
130                         indent--;
131                         writeln("</subcomponents>");
132                 }
133                 
134                 // Close element
135                 indent--;
136                 writeln(list.get(length-1));
137         }
138
139         
140         
141         private void saveSimulation(Simulation simulation, double timeSkip) throws IOException {
142                 SimulationConditions cond = simulation.getConditions();
143                 
144                 writeln("<simulation status=\"" + enumToXMLName(simulation.getStatus()) +"\">");
145                 indent++;
146                 
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>");
152                 indent++;
153                 
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());
162                 
163                 if (cond.isISAAtmosphere()) {
164                         writeln("<atmosphere model=\"isa\"/>");
165                 } else {
166                         writeln("<atmosphere model=\"extendedisa\">");
167                         indent++;
168                         writeElement("basetemperature", cond.getLaunchTemperature());
169                         writeElement("basepressure", cond.getLaunchPressure());
170                         indent--;
171                         writeln("</atmosphere>");
172                 }
173
174                 writeElement("timestep", cond.getTimeStep());
175                 
176                 indent--;
177                 writeln("</conditions>");
178                 
179                 
180                 for (String s: simulation.getSimulationListeners()) {
181                         writeElement("listener", escapeXML(s));
182                 }
183                 
184                 
185                 // Write basic simulation data
186                 
187                 FlightData data = simulation.getSimulatedData();
188                 if (data != null) {
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()) + "\"";
204                         str += ">";
205                         writeln(str);
206                         indent++;
207                         
208                         for (Warning w: data.getWarningSet()) {
209                                 writeElement("warning", escapeXML(w.toString()));
210                         }
211                         
212                         // Check whether to store data
213                         if (simulation.getStatus() == Simulation.Status.EXTERNAL) // Always store external data
214                                 timeSkip = 0;
215                         
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);
220                                 }
221                         }
222                         
223                         indent--;
224                         writeln("</flightdata>");
225                 }
226                 
227                 indent--;
228                 writeln("</simulation>");
229                 
230         }
231         
232         
233         
234         private void saveFlightDataBranch(FlightDataBranch branch, double timeSkip) throws IOException {
235                 double previousTime = -100;
236                 
237                 if (branch == null)
238                         return;
239                 
240                 // Retrieve the types from the branch
241                 FlightDataBranch.Type[] types = branch.getTypes();
242                 
243                 if (types.length == 0)
244                         return;
245                 
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]));
250                 }
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");
255                 }
256                 
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++) {
263                         if (i > 0)
264                                 sb.append(",");
265                         sb.append(escapeXML(types[i].getName()));
266                 }
267                 sb.append("\">");
268                 writeln(sb.toString());
269                 indent++;
270                 
271                 // Write events
272                 for (Pair<Double,FlightEvent> p: branch.getEvents()) {
273                         writeln("<event time=\"" + doubleToString(p.getU())
274                                         + "\" type=\"" + enumToXMLName(p.getV().getType()) + "\"/>");
275                 }
276                 
277                 // Write the data
278                 int length = branch.getLength();
279                 if (length > 0) {
280                         writeDataPointString(data, 0, sb);
281                         previousTime = timeData.get(0);
282                 }
283                 
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);
289                         }
290                 }
291                 
292                 if (length > 1) {
293                         writeDataPointString(data, length-1, sb);
294                 }
295                 
296                 indent--;
297                 writeln("</databranch>");
298         }
299         
300         private void writeDataPointString(List<List<Double>> data, int index, StringBuilder sb)
301         throws IOException {
302                 sb.setLength(0);
303                 sb.append("<datapoint>");
304                 for (int j=0; j < data.size(); j++) {
305                         if (j > 0)
306                                 sb.append(",");
307                         sb.append(doubleToString(data.get(j).get(index)));
308                 }
309                 sb.append("</datapoint>");
310                 writeln(sb.toString());
311         }
312         
313         
314         
315         private void writeElement(String element, Object content) throws IOException {
316                 if (content == null)
317                         content = "";
318                 writeln("<"+element+">"+content+"</"+element+">");
319         }
320
321
322         
323         private void writeln(String str) throws IOException {
324                 if (str.length() == 0) {
325                         dest.write("\n");
326                         return;
327                 }
328                 String s="";
329                 for (int i=0; i<indent; i++)
330                         s=s+"  ";
331                 s = s+str+"\n";
332                 dest.write(s);
333         }
334         
335         
336         /**
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.
340          * 
341          * @param d             the value to present.
342          * @return              a representation with suitable precision.
343          */
344         public static final String doubleToString(double d) {
345                 
346                 // Check for special cases
347                 if (MathUtil.equals(d, 0))
348                         return "0";
349                 
350                 if (Double.isNaN(d))
351                         return "NaN";
352                 
353                 if (Double.isInfinite(d)) {
354                         if (d < 0)
355                                 return "-Inf";
356                         else
357                                 return "Inf";
358                 }
359                 
360                 
361                 double abs = Math.abs(d);
362                 
363                 if (abs < 0.001) {
364                         // Compact exponential notation
365                         int exp = 0;
366                         
367                         while (abs < 1.0) {
368                                 abs *= 10;
369                                 exp++;
370                         }
371                         
372                         String sign = (d < 0) ? "-" : "";
373                         return sign + String.format((Locale)null, "%.4fe-%d", abs, exp);
374                 }
375                 if (abs < 0.01)
376                         return String.format((Locale)null, "%.7f", d);
377                 if (abs < 0.1)
378                         return String.format((Locale)null, "%.6f", d);
379                 if (abs < 1)
380                         return String.format((Locale)null, "%.5f", d);
381                 if (abs < 10)
382                         return String.format((Locale)null, "%.4f", d);
383                 if (abs < 100)
384                         return String.format((Locale)null, "%.3f", d);
385                 if (abs < 1000)
386                         return String.format((Locale)null, "%.2f", d);
387                 if (abs < 10000)
388                         return String.format((Locale)null, "%.1f", d);
389                 if (abs < 100000000.0)
390                         return String.format((Locale)null, "%.0f", d);
391                         
392                 // Compact exponential notation
393                 int exp = 0;
394                 while (abs >= 10.0) {
395                         abs /= 10;
396                         exp++;
397                 }
398                 
399                 String sign = (d < 0) ? "-" : "";
400                 return sign + String.format((Locale)null, "%.4fe%d", abs, exp);
401         }
402         
403         
404         
405         public static void main(String[] arg) {
406                 double d = -0.000000123456789123;
407                 
408                 
409                 for (int i=0; i< 20; i++) {
410                         String str = doubleToString(d);
411                         System.out.println(str + "   ->   " + Double.parseDouble(str));
412                         d *= 10;
413                 }
414                 
415                 
416                 System.out.println("Value: "+ Double.parseDouble("1.2345e9"));
417                 
418         }
419
420         
421         /**
422          * Return the XML equivalent of an enum name.
423          * 
424          * @param e             the enum to save.
425          * @return              the corresponding XML name.
426          */
427         public static String enumToXMLName(Enum<?> e) {
428                 return e.name().toLowerCase().replace("_", "");
429         }
430         
431 }