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 longed than this will be interpreted as a plugged motor. */
37 private static final int DELAY_LIMIT = 90;
40 // FIXME: Obtain default motor type from manufacturer info
44 protected Charset getDefaultCharset() {
51 * Load a <code>Motor</code> from a RockSim motor definition file specified by the
52 * <code>Reader</code>. The <code>Reader</code> is responsible for using the correct
55 * If automatic CG/mass calculation is used, then the CG is assumed to be located at
56 * the center of the motor casing and the mass is calculated from the thrust curve
57 * by assuming a constant exhaust velocity.
59 * @param reader the source of the file.
60 * @return a list of the {@link Motor} objects defined in the file.
61 * @throws IOException if an I/O error occurs or if the file format is invalid.
64 public List<Motor> load(Reader reader, String filename) throws IOException {
65 InputSource source = new InputSource(reader);
66 RSEHandler handler = new RSEHandler();
67 WarningSet warnings = new WarningSet();
70 SimpleSAX.readXML(source, handler, warnings);
71 return handler.getMotors();
72 } catch (SAXException e) {
73 throw new IOException(e.getMessage(), e);
80 * Initial handler for the RockSim engine files.
82 private static class RSEHandler extends ElementHandler {
83 private final List<Motor> motors = new ArrayList<Motor>();
85 private RSEMotorHandler motorHandler;
87 public List<Motor> getMotors() {
92 public ElementHandler openElement(String element,
93 HashMap<String, String> attributes, WarningSet warnings) throws SAXException {
95 if (element.equals("engine-database") ||
96 element.equals("engine-list")) {
97 // Ignore <engine-database> and <engine-list> elements
101 if (element.equals("version")) {
102 // Ignore <version> elements completely
106 if (element.equals("engine")) {
107 motorHandler = new RSEMotorHandler(attributes);
115 public void closeElement(String element, HashMap<String, String> attributes,
116 String content, WarningSet warnings) throws SAXException {
118 if (element.equals("engine")) {
119 Motor motor = motorHandler.getMotor();
127 * Handler for a RockSim engine file <motor> element.
129 private static class RSEMotorHandler extends ElementHandler {
131 private final String manufacturer;
132 private final String designation;
133 private final double[] delays;
134 private final double diameter;
135 private final double length;
136 private final double initMass;
137 private final double propMass;
138 private final Motor.Type type;
139 private boolean calculateMass;
140 private boolean calculateCG;
142 private String description = "";
144 private List<Double> time;
145 private List<Double> force;
146 private List<Double> mass;
147 private List<Double> cg;
149 private RSEMotorDataHandler dataHandler = null;
152 public RSEMotorHandler(HashMap<String, String> attributes) throws SAXException {
156 str = attributes.get("mfg");
158 throw new SAXException("Manufacturer missing");
162 str = attributes.get("code");
164 throw new SAXException("Designation missing");
165 designation = removeDelay(str);
168 ArrayList<Double> delayList = new ArrayList<Double>();
169 str = attributes.get("delays");
171 String[] split = str.split(",");
172 for (String delay : split) {
175 double d = Double.parseDouble(delay);
176 if (d >= DELAY_LIMIT)
180 } catch (NumberFormatException e) {
181 if (str.equalsIgnoreCase("P") || str.equalsIgnoreCase("plugged")) {
182 delayList.add(Motor.PLUGGED);
187 delays = new double[delayList.size()];
188 for (int i = 0; i < delayList.size(); i++) {
189 delays[i] = delayList.get(i);
193 str = attributes.get("dia");
195 throw new SAXException("Diameter missing");
197 diameter = Double.parseDouble(str) / 1000.0;
198 } catch (NumberFormatException e) {
199 throw new SAXException("Invalid diameter " + str);
203 str = attributes.get("len");
205 throw new SAXException("Length missing");
207 length = Double.parseDouble(str) / 1000.0;
208 } catch (NumberFormatException e) {
209 throw new SAXException("Invalid length " + str);
213 str = attributes.get("initWt");
215 throw new SAXException("Initial mass missing");
217 initMass = Double.parseDouble(str) / 1000.0;
218 } catch (NumberFormatException e) {
219 throw new SAXException("Invalid initial mass " + str);
223 str = attributes.get("propWt");
225 throw new SAXException("Propellant mass missing");
227 propMass = Double.parseDouble(str) / 1000.0;
228 } catch (NumberFormatException e) {
229 throw new SAXException("Invalid propellant mass " + str);
232 if (propMass > initMass) {
233 throw new SAXException("Propellant weight exceeds total weight in " +
234 "RockSim engine format");
238 str = attributes.get("Type");
239 if (str != null && str.equalsIgnoreCase("single-use")) {
240 type = Motor.Type.SINGLE;
241 } else if (str != null && str.equalsIgnoreCase("hybrid")) {
242 type = Motor.Type.HYBRID;
243 } else if (str != null && str.equalsIgnoreCase("reloadable")) {
244 type = Motor.Type.RELOAD;
246 type = Motor.Type.UNKNOWN;
250 str = attributes.get("auto-calc-mass");
251 if ("0".equals(str) || "false".equalsIgnoreCase(str)) {
252 calculateMass = false;
254 calculateMass = true;
258 str = attributes.get("auto-calc-cg");
259 if ("0".equals(str) || "false".equalsIgnoreCase(str)) {
267 public ElementHandler openElement(String element,
268 HashMap<String, String> attributes, WarningSet warnings) throws SAXException {
270 if (element.equals("comments")) {
271 return PlainTextHandler.INSTANCE;
274 if (element.equals("data")) {
275 if (dataHandler != null) {
276 throw new SAXException("Multiple data elements encountered in motor " +
279 dataHandler = new RSEMotorDataHandler();
283 warnings.add("Unknown element '" + element + "' encountered, ignoring.");
288 public void closeElement(String element, HashMap<String, String> attributes,
289 String content, WarningSet warnings) {
291 if (element.equals("comments")) {
292 if (description.length() > 0) {
293 description = description + "\n\n" + content.trim();
295 description = content.trim();
300 if (element.equals("data")) {
301 time = dataHandler.getTime();
302 force = dataHandler.getForce();
303 mass = dataHandler.getMass();
304 cg = dataHandler.getCG();
306 sortLists(time, force, mass, cg);
308 for (double d : mass) {
309 if (Double.isNaN(d)) {
310 calculateMass = true;
314 for (double d : cg) {
315 if (Double.isNaN(d)) {
324 public Motor getMotor() throws SAXException {
325 if (time == null || time.size() == 0)
326 throw new SAXException("Illegal motor data");
329 finalizeThrustCurve(time, force, mass, cg);
330 final int n = time.size();
332 if (hasIllegalValue(mass))
333 calculateMass = true;
334 if (hasIllegalValue(cg))
338 mass = calculateMass(time, force, initMass, propMass);
341 for (int i = 0; i < n; i++) {
342 cg.set(i, length / 2);
346 double[] timeArray = toArray(time);
347 double[] thrustArray = toArray(force);
348 Coordinate[] cgArray = new Coordinate[n];
349 for (int i = 0; i < n; i++) {
350 cgArray[i] = new Coordinate(cg.get(i), 0, 0, mass.get(i));
354 // Create the motor digest from all data available in the file
355 MotorDigest motorDigest = new MotorDigest();
356 motorDigest.update(DataType.TIME_ARRAY, timeArray);
357 if (!calculateMass) {
358 motorDigest.update(DataType.MASS_PER_TIME, toArray(mass));
360 motorDigest.update(DataType.MASS_SPECIFIC, initMass, initMass - propMass);
363 motorDigest.update(DataType.CG_PER_TIME, toArray(cg));
365 motorDigest.update(DataType.FORCE_PER_TIME, thrustArray);
366 // TODO: HIGH: Motor digest?
367 // final String digest = motorDigest.getDigest();
371 Manufacturer m = Manufacturer.getManufacturer(manufacturer);
373 if (t == Motor.Type.UNKNOWN) {
374 t = m.getMotorType();
376 if (m.getMotorType() != Motor.Type.UNKNOWN && m.getMotorType() != t) {
377 log.warn("Loaded motor type inconsistent with manufacturer," +
378 " loaded type=" + t + " manufacturer=" + m +
379 " manufacturer type=" + m.getMotorType() +
380 " designation=" + designation);
384 return new ThrustCurveMotor(m, designation, description, t,
385 delays, diameter, length, timeArray, thrustArray, cgArray);
386 } catch (IllegalArgumentException e) {
387 throw new SAXException("Illegal motor data", e);
394 * Handler for the <data> element in a RockSim engine file motor definition.
396 private static class RSEMotorDataHandler extends ElementHandler {
398 private final List<Double> time = new ArrayList<Double>();
399 private final List<Double> force = new ArrayList<Double>();
400 private final List<Double> mass = new ArrayList<Double>();
401 private final List<Double> cg = new ArrayList<Double>();
404 public List<Double> getTime() {
408 public List<Double> getForce() {
412 public List<Double> getMass() {
416 public List<Double> getCG() {
422 public ElementHandler openElement(String element,
423 HashMap<String, String> attributes, WarningSet warnings) {
425 if (element.equals("eng-data")) {
426 return NullElementHandler.INSTANCE;
429 warnings.add("Unknown element '" + element + "' encountered, ignoring.");
434 public void closeElement(String element, HashMap<String, String> attributes,
435 String content, WarningSet warnings) throws SAXException {
437 double t = parseDouble(attributes.get("t"));
438 double f = parseDouble(attributes.get("f"));
439 double m = parseDouble(attributes.get("m")) / 1000.0;
440 double g = parseDouble(attributes.get("cg")) / 1000.0;
442 if (Double.isNaN(t) || Double.isNaN(f)) {
443 throw new SAXException("Illegal motor data point encountered");
453 private double parseDouble(String str) {
457 return Double.parseDouble(str);
458 } catch (NumberFormatException e) {
466 private static boolean hasIllegalValue(List<Double> list) {
467 for (Double d : list) {
468 if (d == null || d.isNaN() || d.isInfinite()) {
475 private static double[] toArray(List<Double> list) {
476 final int n = list.size();
477 double[] array = new double[n];
478 for (int i = 0; i < n; i++) {
479 array[i] = list.get(i);