1 package net.sf.openrocket.file.motor;
3 import java.io.IOException;
5 import java.nio.charset.Charset;
6 import java.util.ArrayList;
7 import java.util.HashMap;
10 import net.sf.openrocket.aerodynamics.WarningSet;
11 import net.sf.openrocket.file.simplesax.ElementHandler;
12 import net.sf.openrocket.file.simplesax.NullElementHandler;
13 import net.sf.openrocket.file.simplesax.PlainTextHandler;
14 import net.sf.openrocket.file.simplesax.SimpleSAX;
15 import net.sf.openrocket.logging.LogHelper;
16 import net.sf.openrocket.motor.Manufacturer;
17 import net.sf.openrocket.motor.Motor;
18 import net.sf.openrocket.motor.MotorDigest;
19 import net.sf.openrocket.motor.MotorDigest.DataType;
20 import net.sf.openrocket.motor.ThrustCurveMotor;
21 import net.sf.openrocket.startup.Application;
22 import net.sf.openrocket.util.Coordinate;
24 import org.xml.sax.InputSource;
25 import org.xml.sax.SAXException;
27 public class RockSimMotorLoader extends AbstractMotorLoader {
29 private static final LogHelper log = Application.getLogger();
31 public static final String CHARSET_NAME = "UTF-8";
33 public static final Charset CHARSET = Charset.forName(CHARSET_NAME);
36 /** Any delay longer than this will be interpreted as a plugged motor. */
37 private static final int DELAY_LIMIT = 90;
42 protected Charset getDefaultCharset() {
49 * Load a <code>Motor</code> from a RockSim motor definition file specified by the
50 * <code>Reader</code>. The <code>Reader</code> is responsible for using the correct
53 * If automatic CG/mass calculation is used, then the CG is assumed to be located at
54 * the center of the motor casing and the mass is calculated from the thrust curve
55 * by assuming a constant exhaust velocity.
57 * @param reader the source of the file.
58 * @return a list of the {@link Motor} objects defined in the file.
59 * @throws IOException if an I/O error occurs or if the file format is invalid.
62 public List<Motor> load(Reader reader, String filename) throws IOException {
63 InputSource source = new InputSource(reader);
64 RSEHandler handler = new RSEHandler();
65 WarningSet warnings = new WarningSet();
68 SimpleSAX.readXML(source, handler, warnings);
69 return handler.getMotors();
70 } catch (SAXException e) {
71 throw new IOException(e.getMessage(), e);
78 * Initial handler for the RockSim engine files.
80 private static class RSEHandler extends ElementHandler {
81 private final List<Motor> motors = new ArrayList<Motor>();
83 private RSEMotorHandler motorHandler;
85 public List<Motor> getMotors() {
90 public ElementHandler openElement(String element,
91 HashMap<String, String> attributes, WarningSet warnings) throws SAXException {
93 if (element.equals("engine-database") ||
94 element.equals("engine-list")) {
95 // Ignore <engine-database> and <engine-list> elements
99 if (element.equals("version")) {
100 // Ignore <version> elements completely
104 if (element.equals("engine")) {
105 motorHandler = new RSEMotorHandler(attributes);
113 public void closeElement(String element, HashMap<String, String> attributes,
114 String content, WarningSet warnings) throws SAXException {
116 if (element.equals("engine")) {
117 Motor motor = motorHandler.getMotor();
125 * Handler for a RockSim engine file <motor> element.
127 private static class RSEMotorHandler extends ElementHandler {
129 private final String manufacturer;
130 private final String designation;
131 private final double[] delays;
132 private final double diameter;
133 private final double length;
134 private final double initMass;
135 private final double propMass;
136 private final Motor.Type type;
137 private boolean calculateMass;
138 private boolean calculateCG;
140 private String description = "";
142 private List<Double> time;
143 private List<Double> force;
144 private List<Double> mass;
145 private List<Double> cg;
147 private RSEMotorDataHandler dataHandler = null;
150 public RSEMotorHandler(HashMap<String, String> attributes) throws SAXException {
154 str = attributes.get("mfg");
156 throw new SAXException("Manufacturer missing");
160 str = attributes.get("code");
162 throw new SAXException("Designation missing");
163 designation = removeDelay(str);
166 ArrayList<Double> delayList = new ArrayList<Double>();
167 str = attributes.get("delays");
169 String[] split = str.split(",");
170 for (String delay : split) {
173 double d = Double.parseDouble(delay);
174 if (d >= DELAY_LIMIT)
178 } catch (NumberFormatException e) {
179 if (str.equalsIgnoreCase("P") || str.equalsIgnoreCase("plugged")) {
180 delayList.add(Motor.PLUGGED);
185 delays = new double[delayList.size()];
186 for (int i = 0; i < delayList.size(); i++) {
187 delays[i] = delayList.get(i);
191 str = attributes.get("dia");
193 throw new SAXException("Diameter missing");
195 diameter = Double.parseDouble(str) / 1000.0;
196 } catch (NumberFormatException e) {
197 throw new SAXException("Invalid diameter " + str);
201 str = attributes.get("len");
203 throw new SAXException("Length missing");
205 length = Double.parseDouble(str) / 1000.0;
206 } catch (NumberFormatException e) {
207 throw new SAXException("Invalid length " + str);
211 str = attributes.get("initWt");
213 throw new SAXException("Initial mass missing");
215 initMass = Double.parseDouble(str) / 1000.0;
216 } catch (NumberFormatException e) {
217 throw new SAXException("Invalid initial mass " + str);
221 str = attributes.get("propWt");
223 throw new SAXException("Propellant mass missing");
225 propMass = Double.parseDouble(str) / 1000.0;
226 } catch (NumberFormatException e) {
227 throw new SAXException("Invalid propellant mass " + str);
230 if (propMass > initMass) {
231 throw new SAXException("Propellant weight exceeds total weight in " +
232 "RockSim engine format");
236 str = attributes.get("Type");
237 if (str != null && str.equalsIgnoreCase("single-use")) {
238 type = Motor.Type.SINGLE;
239 } else if (str != null && str.equalsIgnoreCase("hybrid")) {
240 type = Motor.Type.HYBRID;
241 } else if (str != null && str.equalsIgnoreCase("reloadable")) {
242 type = Motor.Type.RELOAD;
244 type = Motor.Type.UNKNOWN;
248 str = attributes.get("auto-calc-mass");
249 if ("0".equals(str) || "false".equalsIgnoreCase(str)) {
250 calculateMass = false;
252 calculateMass = true;
256 str = attributes.get("auto-calc-cg");
257 if ("0".equals(str) || "false".equalsIgnoreCase(str)) {
265 public ElementHandler openElement(String element,
266 HashMap<String, String> attributes, WarningSet warnings) throws SAXException {
268 if (element.equals("comments")) {
269 return PlainTextHandler.INSTANCE;
272 if (element.equals("data")) {
273 if (dataHandler != null) {
274 throw new SAXException("Multiple data elements encountered in motor " +
277 dataHandler = new RSEMotorDataHandler();
281 warnings.add("Unknown element '" + element + "' encountered, ignoring.");
286 public void closeElement(String element, HashMap<String, String> attributes,
287 String content, WarningSet warnings) {
289 if (element.equals("comments")) {
290 if (description.length() > 0) {
291 description = description + "\n\n" + content.trim();
293 description = content.trim();
298 if (element.equals("data")) {
299 time = dataHandler.getTime();
300 force = dataHandler.getForce();
301 mass = dataHandler.getMass();
302 cg = dataHandler.getCG();
304 sortLists(time, force, mass, cg);
306 for (double d : mass) {
307 if (Double.isNaN(d)) {
308 calculateMass = true;
312 for (double d : cg) {
313 if (Double.isNaN(d)) {
322 public Motor getMotor() throws SAXException {
323 if (time == null || time.size() == 0)
324 throw new SAXException("Illegal motor data");
327 finalizeThrustCurve(time, force, mass, cg);
328 final int n = time.size();
330 if (hasIllegalValue(mass))
331 calculateMass = true;
332 if (hasIllegalValue(cg))
336 mass = calculateMass(time, force, initMass, propMass);
339 for (int i = 0; i < n; i++) {
340 cg.set(i, length / 2);
344 double[] timeArray = toArray(time);
345 double[] thrustArray = toArray(force);
346 Coordinate[] cgArray = new Coordinate[n];
347 for (int i = 0; i < n; i++) {
348 cgArray[i] = new Coordinate(cg.get(i), 0, 0, mass.get(i));
352 // Create the motor digest from all data available in the file
353 MotorDigest motorDigest = new MotorDigest();
354 motorDigest.update(DataType.TIME_ARRAY, timeArray);
355 if (!calculateMass) {
356 motorDigest.update(DataType.MASS_PER_TIME, toArray(mass));
358 motorDigest.update(DataType.MASS_SPECIFIC, initMass, initMass - propMass);
361 motorDigest.update(DataType.CG_PER_TIME, toArray(cg));
363 motorDigest.update(DataType.FORCE_PER_TIME, thrustArray);
364 // TODO: HIGH: Motor digest?
365 // final String digest = motorDigest.getDigest();
369 Manufacturer m = Manufacturer.getManufacturer(manufacturer);
371 if (t == Motor.Type.UNKNOWN) {
372 t = m.getMotorType();
374 if (m.getMotorType() != Motor.Type.UNKNOWN && m.getMotorType() != t) {
375 log.warn("Loaded motor type inconsistent with manufacturer," +
376 " loaded type=" + t + " manufacturer=" + m +
377 " manufacturer type=" + m.getMotorType() +
378 " designation=" + designation);
382 return new ThrustCurveMotor(m, designation, description, t,
383 delays, diameter, length, timeArray, thrustArray, cgArray);
384 } catch (IllegalArgumentException e) {
385 throw new SAXException("Illegal motor data", e);
392 * Handler for the <data> element in a RockSim engine file motor definition.
394 private static class RSEMotorDataHandler extends ElementHandler {
396 private final List<Double> time = new ArrayList<Double>();
397 private final List<Double> force = new ArrayList<Double>();
398 private final List<Double> mass = new ArrayList<Double>();
399 private final List<Double> cg = new ArrayList<Double>();
402 public List<Double> getTime() {
406 public List<Double> getForce() {
410 public List<Double> getMass() {
414 public List<Double> getCG() {
420 public ElementHandler openElement(String element,
421 HashMap<String, String> attributes, WarningSet warnings) {
423 if (element.equals("eng-data")) {
424 return NullElementHandler.INSTANCE;
427 warnings.add("Unknown element '" + element + "' encountered, ignoring.");
432 public void closeElement(String element, HashMap<String, String> attributes,
433 String content, WarningSet warnings) throws SAXException {
435 double t = parseDouble(attributes.get("t"));
436 double f = parseDouble(attributes.get("f"));
437 double m = parseDouble(attributes.get("m")) / 1000.0;
438 double g = parseDouble(attributes.get("cg")) / 1000.0;
440 if (Double.isNaN(t) || Double.isNaN(f)) {
441 throw new SAXException("Illegal motor data point encountered");
451 private double parseDouble(String str) {
455 return Double.parseDouble(str);
456 } catch (NumberFormatException e) {
464 private static boolean hasIllegalValue(List<Double> list) {
465 for (Double d : list) {
466 if (d == null || d.isNaN() || d.isInfinite()) {
473 private static double[] toArray(List<Double> list) {
474 final int n = list.size();
475 double[] array = new double[n];
476 for (int i = 0; i < n; i++) {
477 array[i] = list.get(i);