]> git.gag.com Git - debian/openrocket/blob - src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java
294fff7f96f6c5b4910625c01bc666c1ecfc9953
[debian/openrocket] / src / net / sf / openrocket / file / openrocket / OpenRocketSaver.java
1 package net.sf.openrocket.file.openrocket;
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.Iterator;
10 import java.util.List;
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.file.RocketSaver;
18 import net.sf.openrocket.rocketcomponent.FinSet;
19 import net.sf.openrocket.rocketcomponent.Rocket;
20 import net.sf.openrocket.rocketcomponent.RocketComponent;
21 import net.sf.openrocket.rocketcomponent.TubeCoupler;
22 import net.sf.openrocket.simulation.FlightData;
23 import net.sf.openrocket.simulation.FlightDataBranch;
24 import net.sf.openrocket.simulation.FlightDataType;
25 import net.sf.openrocket.simulation.FlightEvent;
26 import net.sf.openrocket.simulation.GUISimulationConditions;
27 import net.sf.openrocket.util.BugException;
28 import net.sf.openrocket.util.MathUtil;
29 import net.sf.openrocket.util.Prefs;
30 import net.sf.openrocket.util.Reflection;
31 import net.sf.openrocket.util.TextUtil;
32
33 public class OpenRocketSaver extends RocketSaver {
34         
35         /**
36          * Divisor used in converting an integer version to the point-represented version.
37          * The integer version divided by this value is the major version and the remainder is
38          * the minor version.  For example 101 corresponds to file version "1.1".
39          */
40         public static final int FILE_VERSION_DIVISOR = 100;
41         
42
43         private static final String OPENROCKET_CHARSET = "UTF-8";
44         
45         private static final String METHOD_PACKAGE = "net.sf.openrocket.file.openrocket.savers";
46         private static final String METHOD_SUFFIX = "Saver";
47         
48
49         // Estimated storage used by different portions
50         // These have been hand-estimated from saved files
51         private static final int BYTES_PER_COMPONENT_UNCOMPRESSED = 590;
52         private static final int BYTES_PER_COMPONENT_COMPRESSED = 80;
53         private static final int BYTES_PER_SIMULATION_UNCOMPRESSED = 1000;
54         private static final int BYTES_PER_SIMULATION_COMPRESSED = 100;
55         private static final int BYTES_PER_DATAPOINT_UNCOMPRESSED = 350;
56         private static final int BYTES_PER_DATAPOINT_COMPRESSED = 100;
57         
58
59         private int indent;
60         private Writer dest;
61         
62         @Override
63         public void save(OutputStream output, OpenRocketDocument document, StorageOptions options)
64                         throws IOException {
65                 
66                 if (options.isCompressionEnabled()) {
67                         output = new GZIPOutputStream(output);
68                 }
69                 
70                 dest = new BufferedWriter(new OutputStreamWriter(output, OPENROCKET_CHARSET));
71                 
72                 final int fileVersion = calculateNecessaryFileVersion(document, options);
73                 final String fileVersionString =
74                                 (fileVersion / FILE_VERSION_DIVISOR) + "." + (fileVersion % FILE_VERSION_DIVISOR);
75                 
76
77                 this.indent = 0;
78                 
79                 System.out.println("Writing...");
80                 
81                 writeln("<?xml version='1.0' encoding='utf-8'?>");
82                 writeln("<openrocket version=\"" + fileVersionString + "\" creator=\"OpenRocket "
83                                 + Prefs.getVersion() + "\">");
84                 indent++;
85                 
86                 // Recursively save the rocket structure
87                 saveComponent(document.getRocket());
88                 
89                 writeln("");
90                 
91                 // Save all simulations
92                 writeln("<simulations>");
93                 indent++;
94                 boolean first = true;
95                 for (Simulation s : document.getSimulations()) {
96                         if (!first)
97                                 writeln("");
98                         first = false;
99                         saveSimulation(s, options.getSimulationTimeSkip());
100                 }
101                 indent--;
102                 writeln("</simulations>");
103                 
104                 indent--;
105                 writeln("</openrocket>");
106                 
107                 dest.flush();
108                 if (options.isCompressionEnabled()) {
109                         ((GZIPOutputStream) output).finish();
110                 }
111         }
112         
113         
114
115         @Override
116         public long estimateFileSize(OpenRocketDocument doc, StorageOptions options) {
117                 
118                 long size = 0;
119                 
120                 // Size per component
121                 int componentCount = 0;
122                 Rocket rocket = doc.getRocket();
123                 Iterator<RocketComponent> iterator = rocket.deepIterator(true);
124                 while (iterator.hasNext()) {
125                         iterator.next();
126                         componentCount++;
127                 }
128                 
129                 if (options.isCompressionEnabled())
130                         size += componentCount * BYTES_PER_COMPONENT_COMPRESSED;
131                 else
132                         size += componentCount * BYTES_PER_COMPONENT_UNCOMPRESSED;
133                 
134
135                 // Size per simulation
136                 if (options.isCompressionEnabled())
137                         size += doc.getSimulationCount() * BYTES_PER_SIMULATION_COMPRESSED;
138                 else
139                         size += doc.getSimulationCount() * BYTES_PER_SIMULATION_UNCOMPRESSED;
140                 
141
142                 // Size per flight data point
143                 int pointCount = 0;
144                 double timeSkip = options.getSimulationTimeSkip();
145                 if (timeSkip != StorageOptions.SIMULATION_DATA_NONE) {
146                         for (Simulation s : doc.getSimulations()) {
147                                 FlightData data = s.getSimulatedData();
148                                 if (data != null) {
149                                         for (int i = 0; i < data.getBranchCount(); i++) {
150                                                 pointCount += countFlightDataBranchPoints(data.getBranch(i), timeSkip);
151                                         }
152                                 }
153                         }
154                 }
155                 
156                 if (options.isCompressionEnabled())
157                         size += pointCount * BYTES_PER_DATAPOINT_COMPRESSED;
158                 else
159                         size += pointCount * BYTES_PER_DATAPOINT_UNCOMPRESSED;
160                 
161                 return size;
162         }
163         
164         
165         /**
166          * Determine which file version is required in order to store all the features of the
167          * current design.  By default the oldest version that supports all the necessary features
168          * will be used.
169          * 
170          * @param document      the document to output.
171          * @param opts          the storage options.
172          * @return                      the integer file version to use.
173          */
174         private int calculateNecessaryFileVersion(OpenRocketDocument document, StorageOptions opts) {
175                 /*
176                  * File version 1.1 is required for:
177                  *  - fin tabs
178                  *  - components attached to tube coupler
179                  * 
180                  * Otherwise use version 1.0.
181                  */
182
183                 // Check for fin tabs (version 1.1)
184                 Iterator<RocketComponent> iterator = document.getRocket().deepIterator();
185                 while (iterator.hasNext()) {
186                         RocketComponent c = iterator.next();
187                         
188                         // Check for fin tabs
189                         if (c instanceof FinSet) {
190                                 FinSet fin = (FinSet) c;
191                                 if (!MathUtil.equals(fin.getTabHeight(), 0) &&
192                                                 !MathUtil.equals(fin.getTabLength(), 0)) {
193                                         return FILE_VERSION_DIVISOR + 1;
194                                 }
195                         }
196                         
197                         // Check for components attached to tube coupler
198                         if (c instanceof TubeCoupler) {
199                                 if (c.getChildCount() > 0) {
200                                         return FILE_VERSION_DIVISOR + 1;
201                                 }
202                         }
203                 }
204                 
205                 // Default (version 1.0)
206                 return FILE_VERSION_DIVISOR + 0;
207         }
208         
209         
210
211         @SuppressWarnings("unchecked")
212         private void saveComponent(RocketComponent component) throws IOException {
213                 
214                 Reflection.Method m = Reflection.findMethod(METHOD_PACKAGE, component, METHOD_SUFFIX,
215                                 "getElements", RocketComponent.class);
216                 if (m == null) {
217                         throw new BugException("Unable to find saving class for component " +
218                                         component.getComponentName());
219                 }
220                 
221                 // Get the strings to save
222                 List<String> list = (List<String>) m.invokeStatic(component);
223                 int length = list.size();
224                 
225                 if (length == 0) // Nothing to do
226                         return;
227                 
228                 if (length < 2) {
229                         throw new RuntimeException("BUG, component data length less than two lines.");
230                 }
231                 
232                 // Open element
233                 writeln(list.get(0));
234                 indent++;
235                 
236                 // Write parameters
237                 for (int i = 1; i < length - 1; i++) {
238                         writeln(list.get(i));
239                 }
240                 
241                 // Recursively write subcomponents
242                 if (component.getChildCount() > 0) {
243                         writeln("");
244                         writeln("<subcomponents>");
245                         indent++;
246                         boolean emptyline = false;
247                         for (RocketComponent subcomponent : component) {
248                                 if (emptyline)
249                                         writeln("");
250                                 emptyline = true;
251                                 saveComponent(subcomponent);
252                         }
253                         indent--;
254                         writeln("</subcomponents>");
255                 }
256                 
257                 // Close element
258                 indent--;
259                 writeln(list.get(length - 1));
260         }
261         
262         
263
264         private void saveSimulation(Simulation simulation, double timeSkip) throws IOException {
265                 GUISimulationConditions cond = simulation.getConditions();
266                 
267                 writeln("<simulation status=\"" + enumToXMLName(simulation.getStatus()) + "\">");
268                 indent++;
269                 
270                 writeln("<name>" + escapeXML(simulation.getName()) + "</name>");
271                 // TODO: MEDIUM: Other simulators/calculators
272                 writeln("<simulator>RK4Simulator</simulator>");
273                 writeln("<calculator>BarrowmanCalculator</calculator>");
274                 writeln("<conditions>");
275                 indent++;
276                 
277                 writeElement("configid", cond.getMotorConfigurationID());
278                 writeElement("launchrodlength", cond.getLaunchRodLength());
279                 writeElement("launchrodangle", cond.getLaunchRodAngle() * 180.0 / Math.PI);
280                 writeElement("launchroddirection", cond.getLaunchRodDirection() * 180.0 / Math.PI);
281                 writeElement("windaverage", cond.getWindSpeedAverage());
282                 writeElement("windturbulence", cond.getWindTurbulenceIntensity());
283                 writeElement("launchaltitude", cond.getLaunchAltitude());
284                 writeElement("launchlatitude", cond.getLaunchLatitude());
285                 
286                 if (cond.isISAAtmosphere()) {
287                         writeln("<atmosphere model=\"isa\"/>");
288                 } else {
289                         writeln("<atmosphere model=\"extendedisa\">");
290                         indent++;
291                         writeElement("basetemperature", cond.getLaunchTemperature());
292                         writeElement("basepressure", cond.getLaunchPressure());
293                         indent--;
294                         writeln("</atmosphere>");
295                 }
296                 
297                 writeElement("timestep", cond.getTimeStep());
298                 
299                 indent--;
300                 writeln("</conditions>");
301                 
302
303                 for (String s : simulation.getSimulationListeners()) {
304                         writeElement("listener", escapeXML(s));
305                 }
306                 
307
308                 // Write basic simulation data
309                 
310                 FlightData data = simulation.getSimulatedData();
311                 if (data != null) {
312                         String str = "<flightdata";
313                         if (!Double.isNaN(data.getMaxAltitude()))
314                                 str += " maxaltitude=\"" + TextUtil.doubleToString(data.getMaxAltitude()) + "\"";
315                         if (!Double.isNaN(data.getMaxVelocity()))
316                                 str += " maxvelocity=\"" + TextUtil.doubleToString(data.getMaxVelocity()) + "\"";
317                         if (!Double.isNaN(data.getMaxAcceleration()))
318                                 str += " maxacceleration=\"" + TextUtil.doubleToString(data.getMaxAcceleration()) + "\"";
319                         if (!Double.isNaN(data.getMaxMachNumber()))
320                                 str += " maxmach=\"" + TextUtil.doubleToString(data.getMaxMachNumber()) + "\"";
321                         if (!Double.isNaN(data.getTimeToApogee()))
322                                 str += " timetoapogee=\"" + TextUtil.doubleToString(data.getTimeToApogee()) + "\"";
323                         if (!Double.isNaN(data.getFlightTime()))
324                                 str += " flighttime=\"" + TextUtil.doubleToString(data.getFlightTime()) + "\"";
325                         if (!Double.isNaN(data.getGroundHitVelocity()))
326                                 str += " groundhitvelocity=\"" + TextUtil.doubleToString(data.getGroundHitVelocity()) + "\"";
327                         str += ">";
328                         writeln(str);
329                         indent++;
330                         
331                         for (Warning w : data.getWarningSet()) {
332                                 writeElement("warning", escapeXML(w.toString()));
333                         }
334                         
335                         // Check whether to store data
336                         if (simulation.getStatus() == Simulation.Status.EXTERNAL) // Always store external data
337                                 timeSkip = 0;
338                         
339                         if (timeSkip != StorageOptions.SIMULATION_DATA_NONE) {
340                                 for (int i = 0; i < data.getBranchCount(); i++) {
341                                         FlightDataBranch branch = data.getBranch(i);
342                                         saveFlightDataBranch(branch, timeSkip);
343                                 }
344                         }
345                         
346                         indent--;
347                         writeln("</flightdata>");
348                 }
349                 
350                 indent--;
351                 writeln("</simulation>");
352                 
353         }
354         
355         
356
357         private void saveFlightDataBranch(FlightDataBranch branch, double timeSkip)
358                         throws IOException {
359                 double previousTime = -100000;
360                 
361                 if (branch == null)
362                         return;
363                 
364                 // Retrieve the types from the branch
365                 FlightDataType[] types = branch.getTypes();
366                 
367                 if (types.length == 0)
368                         return;
369                 
370                 // Retrieve the data from the branch
371                 List<List<Double>> data = new ArrayList<List<Double>>(types.length);
372                 for (int i = 0; i < types.length; i++) {
373                         data.add(branch.get(types[i]));
374                 }
375                 List<Double> timeData = branch.get(FlightDataType.TYPE_TIME);
376                 if (timeData == null) {
377                         // TODO: MEDIUM: External data may not have time data
378                         throw new IllegalArgumentException("Data did not contain time data");
379                 }
380                 
381                 // Build the <databranch> tag
382                 StringBuilder sb = new StringBuilder();
383                 sb.append("<databranch name=\"");
384                 sb.append(escapeXML(branch.getBranchName()));
385                 sb.append("\" types=\"");
386                 for (int i = 0; i < types.length; i++) {
387                         if (i > 0)
388                                 sb.append(",");
389                         sb.append(escapeXML(types[i].getName()));
390                 }
391                 sb.append("\">");
392                 writeln(sb.toString());
393                 indent++;
394                 
395                 // Write events
396                 for (FlightEvent event : branch.getEvents()) {
397                         writeln("<event time=\"" + TextUtil.doubleToString(event.getTime())
398                                         + "\" type=\"" + enumToXMLName(event.getType()) + "\"/>");
399                 }
400                 
401                 // Write the data
402                 int length = branch.getLength();
403                 if (length > 0) {
404                         writeDataPointString(data, 0, sb);
405                         previousTime = timeData.get(0);
406                 }
407                 
408                 for (int i = 1; i < length - 1; i++) {
409                         if (Math.abs(timeData.get(i) - previousTime - timeSkip) < Math.abs(timeData.get(i + 1) - previousTime - timeSkip)) {
410                                 writeDataPointString(data, i, sb);
411                                 previousTime = timeData.get(i);
412                         }
413                 }
414                 
415                 if (length > 1) {
416                         writeDataPointString(data, length - 1, sb);
417                 }
418                 
419                 indent--;
420                 writeln("</databranch>");
421         }
422         
423         
424
425         /* TODO: LOW: This is largely duplicated from above! */
426         private int countFlightDataBranchPoints(FlightDataBranch branch, double timeSkip) {
427                 int count = 0;
428                 
429                 double previousTime = -100000;
430                 
431                 if (branch == null)
432                         return 0;
433                 
434                 // Retrieve the types from the branch
435                 FlightDataType[] types = branch.getTypes();
436                 
437                 if (types.length == 0)
438                         return 0;
439                 
440                 List<Double> timeData = branch.get(FlightDataType.TYPE_TIME);
441                 if (timeData == null) {
442                         // TODO: MEDIUM: External data may not have time data
443                         throw new IllegalArgumentException("Data did not contain time data");
444                 }
445                 
446                 // Write the data
447                 int length = branch.getLength();
448                 if (length > 0) {
449                         count++;
450                         previousTime = timeData.get(0);
451                 }
452                 
453                 for (int i = 1; i < length - 1; i++) {
454                         if (Math.abs(timeData.get(i) - previousTime - timeSkip) < Math.abs(timeData.get(i + 1) - previousTime - timeSkip)) {
455                                 count++;
456                                 previousTime = timeData.get(i);
457                         }
458                 }
459                 
460                 if (length > 1) {
461                         count++;
462                 }
463                 
464                 return count;
465         }
466         
467         
468
469         private void writeDataPointString(List<List<Double>> data, int index, StringBuilder sb)
470                         throws IOException {
471                 sb.setLength(0);
472                 sb.append("<datapoint>");
473                 for (int j = 0; j < data.size(); j++) {
474                         if (j > 0)
475                                 sb.append(",");
476                         sb.append(TextUtil.doubleToString(data.get(j).get(index)));
477                 }
478                 sb.append("</datapoint>");
479                 writeln(sb.toString());
480         }
481         
482         
483
484         private void writeElement(String element, Object content) throws IOException {
485                 if (content == null)
486                         content = "";
487                 writeln("<" + element + ">" + content + "</" + element + ">");
488         }
489         
490         
491
492         private void writeln(String str) throws IOException {
493                 if (str.length() == 0) {
494                         dest.write("\n");
495                         return;
496                 }
497                 String s = "";
498                 for (int i = 0; i < indent; i++)
499                         s = s + "  ";
500                 s = s + str + "\n";
501                 dest.write(s);
502         }
503         
504         
505         public static void main(String[] arg) {
506                 double d = -0.000000123456789123;
507                 
508
509                 for (int i = 0; i < 20; i++) {
510                         String str = TextUtil.doubleToString(d);
511                         System.out.println(str + "   ->   " + Double.parseDouble(str));
512                         d *= 10;
513                 }
514                 
515
516                 System.out.println("Value: " + Double.parseDouble("1.2345e9"));
517                 
518         }
519         
520         
521         /**
522          * Return the XML equivalent of an enum name.
523          * 
524          * @param e             the enum to save.
525          * @return              the corresponding XML name.
526          */
527         public static String enumToXMLName(Enum<?> e) {
528                 return e.name().toLowerCase().replace("_", "");
529         }
530         
531 }