1 package net.sf.openrocket.file;
3 import java.io.BufferedReader;
5 import java.io.FileInputStream;
6 import java.io.IOException;
7 import java.io.InputStream;
8 import java.io.InputStreamReader;
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;
17 import net.sf.openrocket.rocketcomponent.Motor;
18 import net.sf.openrocket.rocketcomponent.ThrustCurveMotor;
19 import net.sf.openrocket.util.Coordinate;
22 public class MotorLoader implements Loader<Motor> {
24 /** The charset used when reading RASP files. */
25 public static final String RASP_CHARSET = "ISO-8859-1";
29 public List<Motor> load(InputStream stream, String filename) throws IOException {
30 return loadMotor(stream, filename);
35 * Load <code>Motor</code> objects from the specified <code>InputStream</code>.
36 * The file type is detected based on the filename extension.
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
44 public static List<Motor> loadMotor(InputStream stream, String filename) throws IOException {
45 if (filename == null) {
46 throw new IOException("Unknown file type.");
50 int point = filename.lastIndexOf('.');
53 ext = filename.substring(point+1);
55 if (ext.equalsIgnoreCase("eng")) {
56 return loadRASP(stream);
59 throw new IOException("Unknown file type.");
65 ////////////// RASP file format //////////////
68 /** Manufacturer codes to expand in RASP files */
69 private static final Map<String,String> manufacturerCodes =
70 new HashMap<String,String>();
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");
133 manufacturerCodes.put("SF", "WECO Feuerwerk"); // Previously Sachsen Feuerwerks
134 manufacturerCodes.put("WECO", "WECO Feuerwerk");
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}.
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)
148 public static List<Motor> loadRASP(InputStream stream) throws IOException {
149 return loadRASP(new InputStreamReader(stream, RASP_CHARSET));
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.
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.
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.
165 public static List<Motor> loadRASP(Reader reader) throws IOException {
166 List<Motor> motors = new ArrayList<Motor>();
167 BufferedReader in = new BufferedReader(reader);
169 String manufacturer = "";
170 String designation = "";
175 ArrayList<Double> delays = null;
177 List<Double> time = new ArrayList<Double>();
178 List<Double> thrust = new ArrayList<Double>();
185 String[] pieces, buf;
187 line = in.readLine();
188 main: while (line != null) { // Until EOF
195 delays = new ArrayList<Double>();
202 while (line.length()==0 || line.charAt(0)==';') {
203 if (line.length() > 0) {
204 comment += line.substring(1).trim() + "\n";
206 line = in.readLine();
210 comment = comment.trim();
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.");
220 designation = pieces[0];
221 diameter = Double.parseDouble(pieces[1]) / 1000.0;
222 length = Double.parseDouble(pieces[2]) / 1000.0;
224 if (pieces[3].equalsIgnoreCase("None")) {
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);
232 // Many RASP files have "100" as an only delay
233 double d = Double.parseDouble(buf[i]);
238 Collections.sort(delays);
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());
246 manufacturer = pieces[6].replace('_', ' ');
250 for (line = in.readLine();
251 (line != null) && (line.length()==0 || line.charAt(0) != ';');
252 line = in.readLine()) {
255 if (buf.length == 0) {
257 } else if (buf.length == 2) {
259 time.add(Double.parseDouble(buf[0]));
260 thrust .add(Double.parseDouble(buf[1]));
263 throw new IOException("Illegal file format.");
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.");
271 double[] delayArray = new double[delays.size()];
272 for (int i=0; i<delays.size(); i++) {
273 delayArray[i] = delays.get(i);
275 motors.add(createRASPMotor(manufacturer, designation, comment,
276 length, diameter, delayArray, propW, totalW, time, thrust));
279 } catch (NumberFormatException e) {
281 throw new IOException("Illegal file format.");
294 * Create a motor from RASP file data.
295 * @throws IOException if the data is illegal for a thrust curve
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)
302 // Add zero time/thrust if necessary
303 if (time.get(0) > 0) {
308 List<Double> mass = calculateMass(time,thrust,totalW,propW);
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));
321 return new ThrustCurveMotor(manufacturer, designation, comment, Motor.Type.UNKNOWN,
322 delays, diameter, length, timeArray, thrustArray, cgArray);
324 } catch (IllegalArgumentException e) {
326 // Bad data read from file.
327 throw new IOException("Illegal file format.", e);
335 * Calculate the mass of a motor at distinct points in time based on the
336 * initial total mass, propellant weight and thrust.
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>
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
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>();
354 double totalMassChange = 0;
357 // First calculate mass change between points
360 for (int i=1; i < time.size(); i++) {
361 double t1 = time.get(i);
362 double f1 = thrust.get(i);
364 double dm = 0.5*(f0+f1)*(t1-t0);
366 totalMassChange += dm;
369 // Scale mass change and calculate mass
371 scale = prop / totalMassChange;
372 for (double dm: deltam) {
382 * Tokenizes a string using whitespace as the delimiter.
384 private static String[] split(String str) {
385 return split(str,"\\s+");
389 * Tokenizes a string using the given delimiter.
391 private static String[] split(String str, String delim) {
392 String[] pieces = str.split(delim);
393 if (pieces.length==0 || !pieces[0].equals(""))
395 return Arrays.copyOfRange(pieces, 1, pieces.length);
403 * For testing purposes.
405 public static void main(String[] args) throws IOException {
408 if (args.length != 1) {
409 System.out.println("Run with one argument, the RAPS file.");
413 motors = loadRASP(new FileInputStream(new File(args[0])));
415 for (Motor motor: motors) {
416 double time = motor.getTotalTime();
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());
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("");
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));
441 System.out.println("");