056f23b7b086eda5c3ca9dcdda039245b79b7cd0
[debian/openrocket] / src / net / sf / openrocket / file / MotorLoader.java
1 package net.sf.openrocket.file;
2
3 import java.io.BufferedReader;
4 import java.io.File;
5 import java.io.FileInputStream;
6 import java.io.IOException;
7 import java.io.InputStream;
8 import java.io.InputStreamReader;
9 import java.io.Reader;
10 import java.util.ArrayList;
11 import java.util.Arrays;
12 import java.util.Collections;
13 import java.util.HashMap;
14 import java.util.List;
15 import java.util.Map;
16
17 import net.sf.openrocket.rocketcomponent.Motor;
18 import net.sf.openrocket.rocketcomponent.ThrustCurveMotor;
19 import net.sf.openrocket.util.Coordinate;
20
21
22 public class MotorLoader implements Loader<Motor> {
23         
24         /** The charset used when reading RASP files. */
25         public static final String RASP_CHARSET = "ISO-8859-1";
26
27         
28         
29         public List<Motor> load(InputStream stream, String filename) throws IOException {
30                 return loadMotor(stream, filename);
31         }
32         
33         
34         /**
35          * Load <code>Motor</code> objects from the specified <code>InputStream</code>.
36          * The file type is detected based on the filename extension. 
37          * 
38          * @param stream        the stream from which to read the file.
39          * @param filename      the file name, by which the format is detected.
40          * @return                      a list of <code>Motor</code> objects defined in the file.
41          * @throws IOException  if an I/O exception occurs, the file format is unknown
42          *                                              or illegal.
43          */
44         public static List<Motor> loadMotor(InputStream stream, String filename) throws IOException {
45                 if (filename == null) {
46                         throw new IOException("Unknown file type.");
47                 }
48                 
49                 String ext = "";
50                 int point = filename.lastIndexOf('.');
51                 
52                 if (point > 0)
53                         ext = filename.substring(point+1);
54                 
55                 if (ext.equalsIgnoreCase("eng")) {
56                         return loadRASP(stream);
57                 }
58                 
59                 throw new IOException("Unknown file type.");
60         }
61         
62         
63         
64         
65         //////////////  RASP file format  //////////////
66         
67         
68         /** Manufacturer codes to expand in RASP files */
69         private static final Map<String,String> manufacturerCodes =
70                 new HashMap<String,String>();
71         static {
72                 manufacturerCodes.put("A", "AeroTech");
73                 manufacturerCodes.put("AT", "AeroTech");
74                 manufacturerCodes.put("AT-RMS", "AeroTech");
75                 manufacturerCodes.put("AT/RCS", "AeroTech");
76                 manufacturerCodes.put("AERO", "AeroTech");
77                 manufacturerCodes.put("AEROT", "AeroTech");
78                 manufacturerCodes.put("ISP", "AeroTech");
79                 manufacturerCodes.put("AEROTECH", "AeroTech");
80                 manufacturerCodes.put("AEROTECH/APOGEE", "AeroTech");
81                 manufacturerCodes.put("AMW", "Animal Motor Works");
82                 manufacturerCodes.put("AW", "Animal Motor Works");
83                 manufacturerCodes.put("ANIMAL", "Animal Motor Works");
84                 manufacturerCodes.put("AP", "Apogee");
85                 manufacturerCodes.put("APOG", "Apogee");
86                 manufacturerCodes.put("P", "Apogee");
87                 manufacturerCodes.put("CES", "Cesaroni");
88                 manufacturerCodes.put("CTI", "Cesaroni");
89                 manufacturerCodes.put("CS", "Cesaroni");
90                 manufacturerCodes.put("CSR", "Cesaroni");
91                 manufacturerCodes.put("PRO38", "Cesaroni");
92                 manufacturerCodes.put("CR", "Contrail Rocket");
93                 manufacturerCodes.put("CONTR", "Contrail Rocket");
94                 manufacturerCodes.put("E", "Estes");
95                 manufacturerCodes.put("ES", "Estes");
96                 manufacturerCodes.put("EM", "Ellis Mountain");
97                 manufacturerCodes.put("ELLIS", "Ellis Mountain");
98                 manufacturerCodes.put("GR", "Gorilla Rocket Motors");
99                 manufacturerCodes.put("GORILLA", "Gorilla Rocket Motors");
100                 manufacturerCodes.put("H", "HyperTEK");
101                 manufacturerCodes.put("HT", "HyperTEK");
102                 manufacturerCodes.put("HYPER", "HyperTEK");
103                 manufacturerCodes.put("HYPERTEK", "HyperTEK");
104                 manufacturerCodes.put("K", "Kosdon by AeroTech");
105                 manufacturerCodes.put("KBA", "Kosdon by AeroTech");
106                 manufacturerCodes.put("K/AT", "Kosdon by AeroTech");
107                 manufacturerCodes.put("KOSDON", "Kosdon by AeroTech");
108                 manufacturerCodes.put("KOSDON/AT", "Kosdon by AeroTech");
109                 manufacturerCodes.put("KOSDON-BY-AEROTECH", "Kosdon by AeroTech");
110                 manufacturerCodes.put("LOKI", "Loki Research");
111                 manufacturerCodes.put("LR", "Loki Research");
112                 manufacturerCodes.put("PM", "Public Missiles");
113                 manufacturerCodes.put("PML", "Public Missiles");
114                 manufacturerCodes.put("PP", "Propulsion Polymers");
115                 manufacturerCodes.put("PROP", "Propulsion Polymers");
116                 manufacturerCodes.put("PROPULSION", "Propulsion Polymers");
117                 manufacturerCodes.put("PROPULSION-POLYMERS", "Propulsion Polymers");
118                 manufacturerCodes.put("Q", "Quest");
119                 manufacturerCodes.put("QU", "Quest");
120                 manufacturerCodes.put("RATT", "RATT Works");
121                 manufacturerCodes.put("RT", "RATT Works");
122                 manufacturerCodes.put("RTW", "RATT Works");
123                 manufacturerCodes.put("RR", "Roadrunner Rocketry");
124                 manufacturerCodes.put("ROADRUNNER", "Roadrunner Rocketry");
125                 manufacturerCodes.put("RV", "Rocketvision");
126                 manufacturerCodes.put("SR", "Sky Ripper Systems");
127                 manufacturerCodes.put("SRS", "Sky Ripper Systems");
128                 manufacturerCodes.put("SKYR", "Sky Ripper Systems");
129                 manufacturerCodes.put("SKYRIPPER", "Sky Ripper Systems");
130                 manufacturerCodes.put("WCH", "West Coast Hybrids");
131                 manufacturerCodes.put("WCR", "West Coast Hybrids");
132                 
133                 manufacturerCodes.put("SF", "WECO Feuerwerk");  // Previously Sachsen Feuerwerks
134                 manufacturerCodes.put("WECO", "WECO Feuerwerk");
135                 
136         }
137         
138         /**
139          * A helper method to load a <code>Motor</code> from a RASP file, read from the
140          * specified <code>InputStream</code>.  The charset used is defined in 
141          * {@link #RASP_CHARSET}.
142          * 
143          * @param stream        the InputStream to read.
144          * @return                      the <code>Motor</code> object. 
145          * @throws IOException  if an I/O error occurs or if the file format is illegal.
146          * @see #loadRASP(Reader)
147          */
148         public static List<Motor> loadRASP(InputStream stream) throws IOException {
149                 return loadRASP(new InputStreamReader(stream, RASP_CHARSET));
150         }
151         
152         
153         
154         /**
155          * Load a <code>Motor</code> from a RASP file specified by the <code>Reader</code>.
156          * The <code>Reader</code> is responsible for using the correct charset.
157          * <p>
158          * The CG is assumed to be located at the center of the motor casing and the mass
159          * is calculated from the thrust curve by assuming a constant exhaust velocity.
160          * 
161          * @param reader  the source of the file.
162          * @return                a list of the {@link Motor} objects defined in the file.
163          * @throws IOException  if an I/O error occurs or if the file format is illegal.
164          */
165         public static List<Motor> loadRASP(Reader reader) throws IOException {
166                 List<Motor> motors = new ArrayList<Motor>();
167                 BufferedReader in = new BufferedReader(reader);
168
169                 String manufacturer = "";
170                 String designation = "";
171                 String comment = "";
172                 
173                 double length = 0;
174                 double diameter = 0;
175                 ArrayList<Double> delays = null;
176                 
177                 List<Double> time = new ArrayList<Double>();
178                 List<Double> thrust = new ArrayList<Double>();
179                 
180                 double propW = 0;
181                 double totalW = 0;
182                 
183                 try {
184                         String line;
185                         String[] pieces, buf;
186
187                         line = in.readLine();
188                         main: while (line != null) {   // Until EOF
189
190                                 manufacturer = "";
191                                 designation = "";
192                                 comment = "";
193                                 length = 0;
194                                 diameter = 0;
195                                 delays = new ArrayList<Double>();
196                                 propW = 0;
197                                 totalW = 0;
198                                 time.clear();
199                                 thrust .clear();
200                         
201                                 // Read comment
202                                 while (line.length()==0 || line.charAt(0)==';') {
203                                         if (line.length() > 0) {
204                                                 comment += line.substring(1).trim() + "\n";
205                                         }
206                                         line = in.readLine();
207                                         if (line == null)
208                                                 break main;
209                                 }
210                                 comment = comment.trim();
211                                 
212                                 // Parse header line, example:
213                                 // F32 24 124 5-10-15-P .0377 .0695 RV
214                                 // desig diam len delays prop.w tot.w manufacturer
215                                 pieces = split(line);
216                                 if (pieces.length != 7) {
217                                         throw new IOException("Illegal file format.");
218                                 }
219                                 
220                                 designation = pieces[0];
221                                 diameter = Double.parseDouble(pieces[1]) / 1000.0;
222                                 length = Double.parseDouble(pieces[2]) / 1000.0;
223                                 
224                                 if (pieces[3].equalsIgnoreCase("None")) {
225
226                                 } else {
227                                         buf = split(pieces[3],"[-,]+");
228                                         for (int i=0; i < buf.length; i++) {
229                                                 if (buf[i].equalsIgnoreCase("P")) {
230                                                         delays.add(Motor.PLUGGED);
231                                                 } else {
232                                                         // Many RASP files have "100" as an only delay
233                                                         double d = Double.parseDouble(buf[i]);
234                                                         if (d < 99)
235                                                                 delays.add(d);
236                                                 }
237                                         }
238                                         Collections.sort(delays);
239                                 }
240                                 
241                                 propW = Double.parseDouble(pieces[4]);
242                                 totalW = Double.parseDouble(pieces[5]);
243                                 if (manufacturerCodes.containsKey(pieces[6].toUpperCase())) {
244                                         manufacturer = manufacturerCodes.get(pieces[6].toUpperCase());
245                                 } else {
246                                         manufacturer = pieces[6].replace('_', ' ');
247                                 }
248                                 
249                                 // Read the data
250                                 for (line = in.readLine(); 
251                                          (line != null) && (line.length()==0 || line.charAt(0) != ';');
252                                          line = in.readLine()) {
253                                         
254                                         buf = split(line);
255                                         if (buf.length == 0) {
256                                                 continue;
257                                         } else if (buf.length == 2) {
258                                                 
259                                                 time.add(Double.parseDouble(buf[0]));
260                                                 thrust .add(Double.parseDouble(buf[1]));
261                                                 
262                                         } else {
263                                                 throw new IOException("Illegal file format.");
264                                         }
265                                 }
266                                 
267                                 // Comment of EOF encountered, marks the start of the next motor
268                                 if (time.size() < 2) {
269                                         throw new IOException("Illegal file format, too short thrust-curve.");
270                                 }
271                                 double[] delayArray = new double[delays.size()];
272                                 for (int i=0; i<delays.size(); i++) {
273                                         delayArray[i] = delays.get(i);
274                                 }
275                                 motors.add(createRASPMotor(manufacturer, designation, comment,
276                                                 length, diameter, delayArray, propW, totalW, time, thrust));
277                         }
278                         
279                 } catch (NumberFormatException e) {
280                         
281                         throw new IOException("Illegal file format.");
282                         
283                 } finally {
284                         
285                         in.close();
286                         
287                 }
288                 
289                 return motors;
290         }
291         
292         
293         /**
294          * Create a motor from RASP file data.
295          * @throws IOException  if the data is illegal for a thrust curve
296          */
297         private static Motor createRASPMotor(String manufacturer, String designation,
298                         String comment, double length, double diameter, double[] delays,
299                         double propW, double totalW, List<Double> time, List<Double> thrust) 
300                         throws IOException {
301                 
302                 // Add zero time/thrust if necessary
303                 if (time.get(0) > 0) {
304                         time.add(0, 0.0);
305                         thrust.add(0, 0.0);
306                 }
307                 
308                 List<Double> mass = calculateMass(time,thrust,totalW,propW);
309                 
310                 double[] timeArray = new double[time.size()];
311                 double[] thrustArray = new double[time.size()];
312                 Coordinate[] cgArray = new Coordinate[time.size()];
313                 for (int i=0; i < time.size(); i++) {
314                         timeArray[i] = time.get(i);
315                         thrustArray[i] = thrust.get(i);
316                         cgArray[i] = new Coordinate(length/2,0,0,mass.get(i));
317                 }
318                 
319                 try {
320                         
321                         return new ThrustCurveMotor(manufacturer, designation, comment, Motor.Type.UNKNOWN,
322                                         delays, diameter, length, timeArray, thrustArray, cgArray);
323                         
324                 } catch (IllegalArgumentException e) {
325                         
326                         // Bad data read from file.
327                         throw new IOException("Illegal file format.", e);
328                         
329                 }
330         }
331         
332         
333         
334         /**
335          * Calculate the mass of a motor at distinct points in time based on the
336          * initial total mass, propellant weight and thrust.
337          * <p>
338          * This calculation assumes that the velocity of the exhaust remains constant
339          * during the burning.  This derives from the mass-flow and thrust relation
340          * <pre>F = m' * v</pre>
341          *  
342          * @param time    list of time points
343          * @param thrust  thrust at the discrete times
344          * @param total   total weight of the motor
345          * @param prop    propellant amount consumed during burning
346          * @return                a list of the mass at the specified time points
347          */
348         private static List<Double> calculateMass(List<Double> time, List<Double> thrust,
349                         double total, double prop) {
350                 List<Double> mass = new ArrayList<Double>();
351                 List<Double> deltam = new ArrayList<Double>();
352
353                 double t0, f0;
354                 double totalMassChange = 0;
355                 double scale;
356
357                 // First calculate mass change between points
358                 t0 = time.get(0);
359                 f0 = thrust.get(0);
360                 for (int i=1; i < time.size(); i++) {
361                         double t1 = time.get(i);
362                         double f1 = thrust.get(i);
363                         
364                         double dm = 0.5*(f0+f1)*(t1-t0);
365                         deltam.add(dm);
366                         totalMassChange += dm;
367                 }
368                 
369                 // Scale mass change and calculate mass
370                 mass.add(total);
371                 scale = prop / totalMassChange;
372                 for (double dm: deltam) {
373                         total -= dm*scale;
374                         mass.add(total);
375                 }
376                 
377                 return mass;
378         }
379         
380         
381         /**
382          * Tokenizes a string using whitespace as the delimiter.
383          */
384         private static String[] split(String str) {
385                 return split(str,"\\s+");
386         }
387         
388         /**
389          * Tokenizes a string using the given delimiter.
390          */
391         private static String[] split(String str, String delim) {
392                 String[] pieces = str.split(delim);
393                 if (pieces.length==0 || !pieces[0].equals(""))
394                         return pieces;
395                 return Arrays.copyOfRange(pieces, 1, pieces.length);
396         }
397         
398         
399         
400         
401         
402         /**
403          * For testing purposes.
404          */
405         public static void main(String[] args) throws IOException {
406                 List<Motor> motors;
407                 
408                 if (args.length != 1) {
409                         System.out.println("Run with one argument, the RAPS file.");
410                         System.exit(1);
411                 }
412                 
413                 motors = loadRASP(new FileInputStream(new File(args[0])));
414                 
415                 for (Motor motor: motors) {
416                         double time = motor.getTotalTime();
417
418                         System.out.println("Motor " + motor);
419                         System.out.println("Manufacturer:    "+motor.getManufacturer());
420                         System.out.println("Designation:     "+motor.getDesignation());
421                         System.out.println("Type:            "+motor.getMotorType().getName());
422                         System.out.printf( "Length:          %.1f mm\n",motor.getLength()*1000);
423                         System.out.printf( "Diameter:        %.1f mm\n",motor.getDiameter()*1000);
424                         System.out.println("Comment:\n" + motor.getDescription());
425
426                         System.out.printf( "Total burn time: %.2f s\n", time);
427                         System.out.printf( "Avg. burn time:  %.2f s\n", motor.getAverageTime());
428                         System.out.printf( "Avg. thrust:     %.2f N\n", motor.getAverageThrust());
429                         System.out.printf( "Max. thrust:     %.2f N\n", motor.getMaxThrust());
430                         System.out.printf( "Total impulse:   %.2f Ns\n", motor.getTotalImpulse());
431                         System.out.println("Delay times:     " + 
432                                         Arrays.toString(motor.getStandardDelays()));
433                         System.out.println("");
434                         
435                         final double COUNT = 20;
436                         for (int i=0; i <= COUNT; i++) {
437                                 double t = time * i/COUNT;
438                                 System.out.printf("t=%.2fs F=%.2fN m=%.4fkg\n",
439                                                 t, motor.getThrust(t), motor.getMass(t));
440                         }
441                         System.out.println("");
442                 }
443                 
444         }
445 }