Merge pull request #1427 from SiboVG/issue-1354

[fixes #1354 & #1037] Add support for custom decimal places and exponential notation in simulation exports
This commit is contained in:
Joe Pfeiffer 2022-06-11 09:22:15 -06:00 committed by GitHub
commit c3a3df9db2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 144 additions and 43 deletions

View File

@ -552,10 +552,14 @@ SimExpPan.border.Vartoexport = Variables to export
SimExpPan.border.Stage = Stage to export SimExpPan.border.Stage = Stage to export
SimExpPan.but.Selectall = Select all SimExpPan.but.Selectall = Select all
SimExpPan.but.Selectnone = Select none SimExpPan.but.Selectnone = Select none
SimExpPan.border.Fieldsep = Field separator SimExpPan.border.FormatSettings = Format settings
SimExpPan.lbl.Fieldsepstr = Field separator string: SimExpPan.lbl.Fieldsepstr = Field separator string:
SimExpPan.lbl.longA1 = <html>The string used to separate the fields in the exported file.<br> SimExpPan.lbl.longA1 = <html>The string used to separate the fields in the exported file.<br>
SimExpPan.lbl.longA2 = Use ',' for a Comma Separated Values (CSV) file. SimExpPan.lbl.longA2 = Use ',' for a Comma Separated Values (CSV) file.
SimExpPan.lbl.DecimalPlaces = Decimal places:
SimExpPan.lbl.DecimalPlaces.ttip = The number of decimal places to use for the variables in the exported file.
SimExpPan.lbl.ExponentialNotation = Use exponential notation
SimExpPan.lbl.ExponentialNotation.ttip = Whether to use exponential notation for the variables (e.g. 1.5e-4) or not (e.g. 0.00015).
SimExpPan.checkbox.Includesimudesc = Include simulation description SimExpPan.checkbox.Includesimudesc = Include simulation description
SimExpPan.checkbox.ttip.Includesimudesc = Include a comment at the beginning of the file describing the simulation. SimExpPan.checkbox.ttip.Includesimudesc = Include a comment at the beginning of the file describing the simulation.
SimExpPan.border.Comments = Comments SimExpPan.border.Comments = Comments

View File

@ -29,6 +29,8 @@ public class CSVExport {
* @param fields the fields to export (in appropriate order). * @param fields the fields to export (in appropriate order).
* @param units the units of the fields. * @param units the units of the fields.
* @param fieldSeparator the field separator string. * @param fieldSeparator the field separator string.
* @param decimalPlaces the number of decimal places to use.
* @param isExponentialNotation whether to use exponential notation.
* @param commentStarter the comment starting character(s). * @param commentStarter the comment starting character(s).
* @param simulationComments whether to output general simulation comments. * @param simulationComments whether to output general simulation comments.
* @param fieldComments whether to output field comments. * @param fieldComments whether to output field comments.
@ -37,8 +39,9 @@ public class CSVExport {
*/ */
public static void exportCSV(OutputStream stream, Simulation simulation, public static void exportCSV(OutputStream stream, Simulation simulation,
FlightDataBranch branch, FlightDataType[] fields, Unit[] units, FlightDataBranch branch, FlightDataType[] fields, Unit[] units,
String fieldSeparator, String commentStarter, boolean simulationComments, String fieldSeparator, int decimalPlaces, boolean isExponentialNotation,
boolean fieldComments, boolean eventComments) throws IOException { String commentStarter, boolean simulationComments, boolean fieldComments,
boolean eventComments) throws IOException {
if (fields.length != units.length) { if (fields.length != units.length) {
throw new IllegalArgumentException("fields and units lengths must be equal " + throw new IllegalArgumentException("fields and units lengths must be equal " +
@ -71,7 +74,7 @@ public class CSVExport {
writer.println(); writer.println();
} }
writeData(writer, branch, fields, units, fieldSeparator, writeData(writer, branch, fields, units, fieldSeparator, decimalPlaces, isExponentialNotation,
eventComments, commentStarter); eventComments, commentStarter);
@ -87,8 +90,8 @@ public class CSVExport {
} }
private static void writeData(PrintWriter writer, FlightDataBranch branch, private static void writeData(PrintWriter writer, FlightDataBranch branch,
FlightDataType[] fields, Unit[] units, String fieldSeparator, boolean eventComments, FlightDataType[] fields, Unit[] units, String fieldSeparator, int decimalPlaces, boolean isExponentialNotation,
String commentStarter) { boolean eventComments, String commentStarter) {
// Number of data points // Number of data points
int n = branch.getLength(); int n = branch.getLength();
@ -132,7 +135,8 @@ public class CSVExport {
// Store CSV line // Store CSV line
for (int i = 0; i < fields.length; i++) { for (int i = 0; i < fields.length; i++) {
double value = fieldValues.get(i).get(pos); double value = fieldValues.get(i).get(pos);
writer.print(TextUtil.doubleToString(units[i].toUnit(value))); writer.print(TextUtil.doubleToString(units[i].toUnit(value), decimalPlaces, isExponentialNotation));
if (i < fields.length - 1) { if (i < fields.length - 1) {
writer.print(fieldSeparator); writer.print(fieldSeparator);
} }

View File

@ -46,6 +46,8 @@ public abstract class Preferences implements ChangeSource {
public static final String DEFAULT_MACH_NUMBER = "DefaultMachNumber"; public static final String DEFAULT_MACH_NUMBER = "DefaultMachNumber";
// Preferences related to data export // Preferences related to data export
public static final String EXPORT_FIELD_SEPARATOR = "ExportFieldSeparator"; public static final String EXPORT_FIELD_SEPARATOR = "ExportFieldSeparator";
public static final String EXPORT_DECIMAL_PLACES = "ExportDecimalPlaces";
public static final String EXPORT_EXPONENTIAL_NOTATION = "ExportExponentialNotation";
public static final String EXPORT_SIMULATION_COMMENT = "ExportSimulationComment"; public static final String EXPORT_SIMULATION_COMMENT = "ExportSimulationComment";
public static final String EXPORT_FIELD_NAME_COMMENT = "ExportFieldDescriptionComment"; public static final String EXPORT_FIELD_NAME_COMMENT = "ExportFieldDescriptionComment";
public static final String EXPORT_EVENT_COMMENTS = "ExportEventComments"; public static final String EXPORT_EVENT_COMMENTS = "ExportEventComments";

View File

@ -40,50 +40,81 @@ public class TextUtil {
} }
return sb.toString(); return sb.toString();
} }
/** /**
* Return a string of the double value with suitable precision for storage. * Return a string of the double value with suitable precision for storage.
* The string is the shortest representation of the value including at least * The string is the shortest representation of the value including at least
* 5 digits of precision. * 5 digits of precision.
* *
* @param d the value to present. * @param d the value to present.
* @param decimalPlaces the number of decimal places to save the value with.
* @param isExponentialNotation if true, the value is presented in exponential notation.
* @return a representation with suitable precision. * @return a representation with suitable precision.
*/ */
public static final String doubleToString(double d) { public static String doubleToString(double d, int decimalPlaces, boolean isExponentialNotation) {
// Check for special cases // Check for special cases
if (MathUtil.equals(d, 0)) if (MathUtil.equals(d, 0))
return "0"; return "0";
if (Double.isNaN(d)) if (Double.isNaN(d))
return "NaN"; return "NaN";
if (Double.isInfinite(d)) { if (Double.isInfinite(d)) {
if (d < 0) if (d < 0)
return "-Inf"; return "-Inf";
else else
return "Inf"; return "Inf";
} }
final String sign = (d < 0) ? "-" : ""; final String sign = (d < 0) ? "-" : "";
double abs = Math.abs(d); double abs = Math.abs(d);
// Small and large values always in exponential notation // Small and large values always in exponential notation
if (abs < 0.001 || abs >= 100000000) { if (isExponentialNotation && (abs < 0.001 || abs >= 100000000)) {
return sign + exponentialFormat(abs); return sign + exponentialFormat(abs);
} }
// Check whether decimal or exponential notation is shorter // Check whether decimal or exponential notation is shorter
String exp = exponentialFormat(abs); String exp = exponentialFormat(abs);
String dec = decimalFormat(abs); String dec;
if (decimalPlaces < 0) {
if (dec.length() <= exp.length()) dec = decimalFormat(abs);
} else {
dec = decimalFormat(abs, decimalPlaces);
}
if (dec.length() <= exp.length() || !isExponentialNotation)
return sign + dec; return sign + dec;
else else
return sign + exp; return sign + exp;
} }
/**
* Return a string of the double value with suitable precision for storage.
* The string is the shortest representation of the value including at least
* 5 digits of precision.
* Saves with exponential notation by default.
*
* @param d the value to present.
* @param decimalPlaces the number of decimal places to save the value with.
* @return a representation with suitable precision.
*/
public static String doubleToString(double d, int decimalPlaces) {
return doubleToString(d, decimalPlaces, true);
}
/**
* Return a string of the double value with suitable precision for storage.
* The string is the shortest representation of the value including at least
* 5 digits of precision.
* Saves with exponential notation by default & decimal places based on the value's size.
*
* @param d the value to present.
* @return a representation with suitable precision.
*/
public static String doubleToString(double d) {
return doubleToString(d, -1, true);
}
/* /*
@ -122,6 +153,16 @@ public class TextUtil {
return shortDecimal(value, decimals); return shortDecimal(value, decimals);
} }
/*
* value must be positive and not zero!
*/
private static String decimalFormat(double value, int decimals) {
if (value >= 10000)
return "" + (int) (value + 0.5);
return shortDecimal(value, decimals);
}

View File

@ -5,8 +5,12 @@ import javax.swing.JCheckBox;
import javax.swing.JComboBox; import javax.swing.JComboBox;
import javax.swing.JLabel; import javax.swing.JLabel;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.SpinnerModel;
import javax.swing.SpinnerNumberModel;
import net.miginfocom.swing.MigLayout; import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.gui.util.SaveCSVWorker;
import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.startup.Application; import net.sf.openrocket.startup.Application;
import net.sf.openrocket.startup.Preferences; import net.sf.openrocket.startup.Preferences;
@ -27,6 +31,8 @@ public class CsvOptionPanel extends JPanel {
private final String baseClassName; private final String baseClassName;
private final JComboBox<String> fieldSeparator; private final JComboBox<String> fieldSeparator;
private final JSpinner decimalPlacesSpinner;
private final JCheckBox exponentialNotationCheckbox;
private final JCheckBox[] options; private final JCheckBox[] options;
private final JComboBox<String> commentCharacter; private final JComboBox<String> commentCharacter;
@ -48,10 +54,11 @@ public class CsvOptionPanel extends JPanel {
// TODO: HIGH: Rename the translation keys // TODO: HIGH: Rename the translation keys
// Field separator panel // Format settings panel
panel = new JPanel(new MigLayout("fill")); panel = new JPanel(new MigLayout("fill"));
panel.setBorder(BorderFactory.createTitledBorder(trans.get("SimExpPan.border.Fieldsep"))); panel.setBorder(BorderFactory.createTitledBorder(trans.get("SimExpPan.border.FormatSettings")));
//// Field separation
label = new JLabel(trans.get("SimExpPan.lbl.Fieldsepstr")); label = new JLabel(trans.get("SimExpPan.lbl.Fieldsepstr"));
tip = trans.get("SimExpPan.lbl.longA1") + tip = trans.get("SimExpPan.lbl.longA1") +
trans.get("SimExpPan.lbl.longA2"); trans.get("SimExpPan.lbl.longA2");
@ -62,8 +69,25 @@ public class CsvOptionPanel extends JPanel {
fieldSeparator.setEditable(true); fieldSeparator.setEditable(true);
fieldSeparator.setSelectedItem(Application.getPreferences().getString(Preferences.EXPORT_FIELD_SEPARATOR, ",")); fieldSeparator.setSelectedItem(Application.getPreferences().getString(Preferences.EXPORT_FIELD_SEPARATOR, ","));
fieldSeparator.setToolTipText(tip); fieldSeparator.setToolTipText(tip);
panel.add(fieldSeparator, "growx"); panel.add(fieldSeparator, "growx, wrap");
//// Decimal places
label = new JLabel(trans.get("SimExpPan.lbl.DecimalPlaces"));
label.setToolTipText(trans.get("SimExpPan.lbl.DecimalPlaces.ttip"));
panel.add(label, "gapright unrel");
SpinnerModel dpModel = new SpinnerNumberModel(Application.getPreferences().getInt(Preferences.EXPORT_DECIMAL_PLACES, SaveCSVWorker.DEFAULT_DECIMAL_PLACES),
0, 15, 1);
decimalPlacesSpinner = new JSpinner(dpModel);
decimalPlacesSpinner.setToolTipText(trans.get("SimExpPan.lbl.DecimalPlaces.ttip"));
panel.add(decimalPlacesSpinner, "growx, wrap");
//// Exponential notation
exponentialNotationCheckbox = new JCheckBox(trans.get("SimExpPan.lbl.ExponentialNotation"));
exponentialNotationCheckbox.setToolTipText(trans.get("SimExpPan.lbl.ExponentialNotation.ttip"));
exponentialNotationCheckbox.setSelected(Application.getPreferences().getBoolean(Preferences.EXPORT_EXPONENTIAL_NOTATION, true));
panel.add(exponentialNotationCheckbox);
this.add(panel, "growx, wrap unrel"); this.add(panel, "growx, wrap unrel");
@ -104,6 +128,14 @@ public class CsvOptionPanel extends JPanel {
public String getFieldSeparator() { public String getFieldSeparator() {
return fieldSeparator.getSelectedItem().toString(); return fieldSeparator.getSelectedItem().toString();
} }
public int getDecimalPlaces() {
return (Integer) decimalPlacesSpinner.getValue();
}
public boolean isExponentialNotation() {
return exponentialNotationCheckbox.isSelected();
}
public String getCommentCharacter() { public String getCommentCharacter() {
return commentCharacter.getSelectedItem().toString(); return commentCharacter.getSelectedItem().toString();
@ -118,6 +150,8 @@ public class CsvOptionPanel extends JPanel {
*/ */
public void storePreferences() { public void storePreferences() {
Application.getPreferences().putString(Preferences.EXPORT_FIELD_SEPARATOR, getFieldSeparator()); Application.getPreferences().putString(Preferences.EXPORT_FIELD_SEPARATOR, getFieldSeparator());
Application.getPreferences().putInt(Preferences.EXPORT_DECIMAL_PLACES, getDecimalPlaces());
Application.getPreferences().putBoolean(Preferences.EXPORT_EXPONENTIAL_NOTATION, isExponentialNotation());
Application.getPreferences().putString(Preferences.EXPORT_COMMENT_CHARACTER, getCommentCharacter()); Application.getPreferences().putString(Preferences.EXPORT_COMMENT_CHARACTER, getCommentCharacter());
for (int i = 0; i < options.length; i++) { for (int i = 0; i < options.length; i++) {
Application.getPreferences().putBoolean("csvOptions." + baseClassName + "." + i, options[i].isSelected()); Application.getPreferences().putBoolean("csvOptions." + baseClassName + "." + i, options[i].isSelected());

View File

@ -212,6 +212,8 @@ public class SimulationExportPanel extends JPanel {
String commentChar = csvOptions.getCommentCharacter(); String commentChar = csvOptions.getCommentCharacter();
String fieldSep = csvOptions.getFieldSeparator(); String fieldSep = csvOptions.getFieldSeparator();
int decimalPlaces = csvOptions.getDecimalPlaces();
boolean isExponentialNotation = csvOptions.isExponentialNotation();
boolean simulationComment = csvOptions.getSelectionOption(OPTION_SIMULATION_COMMENTS); boolean simulationComment = csvOptions.getSelectionOption(OPTION_SIMULATION_COMMENTS);
boolean fieldComment = csvOptions.getSelectionOption(OPTION_FIELD_DESCRIPTIONS); boolean fieldComment = csvOptions.getSelectionOption(OPTION_FIELD_DESCRIPTIONS);
boolean eventComment = csvOptions.getSelectionOption(OPTION_FLIGHT_EVENTS); boolean eventComment = csvOptions.getSelectionOption(OPTION_FLIGHT_EVENTS);
@ -245,8 +247,8 @@ public class SimulationExportPanel extends JPanel {
} }
SaveCSVWorker.export(file, simulation, branch, fieldTypes, fieldUnits, fieldSep, SaveCSVWorker.export(file, simulation, branch, fieldTypes, fieldUnits, fieldSep, decimalPlaces,
commentChar, simulationComment, fieldComment, eventComment, isExponentialNotation, commentChar, simulationComment, fieldComment, eventComment,
SwingUtilities.getWindowAncestor(this)); SwingUtilities.getWindowAncestor(this));
} }

View File

@ -238,6 +238,8 @@ public class SimulationExportPanel extends JPanel {
String commentChar = csvOptions.getCommentCharacter(); String commentChar = csvOptions.getCommentCharacter();
String fieldSep = csvOptions.getFieldSeparator(); String fieldSep = csvOptions.getFieldSeparator();
int decimalPlaces = csvOptions.getDecimalPlaces();
boolean isExponentialNotation = csvOptions.isExponentialNotation();
boolean simulationComment = csvOptions.getSelectionOption(OPTION_SIMULATION_COMMENTS); boolean simulationComment = csvOptions.getSelectionOption(OPTION_SIMULATION_COMMENTS);
boolean fieldComment = csvOptions.getSelectionOption(OPTION_FIELD_DESCRIPTIONS); boolean fieldComment = csvOptions.getSelectionOption(OPTION_FIELD_DESCRIPTIONS);
boolean eventComment = csvOptions.getSelectionOption(OPTION_FLIGHT_EVENTS); boolean eventComment = csvOptions.getSelectionOption(OPTION_FLIGHT_EVENTS);
@ -269,10 +271,10 @@ public class SimulationExportPanel extends JPanel {
} else if (fieldSep.equals(TAB)) { } else if (fieldSep.equals(TAB)) {
fieldSep = "\t"; fieldSep = "\t";
} }
SaveCSVWorker.export(file, simulation, branch, fieldTypes, fieldUnits, fieldSep, SaveCSVWorker.export(file, simulation, branch, fieldTypes, fieldUnits, fieldSep, decimalPlaces,
commentChar, simulationComment, fieldComment, eventComment, isExponentialNotation, commentChar, simulationComment, fieldComment, eventComment,
SwingUtilities.getWindowAncestor(this)); SwingUtilities.getWindowAncestor(this));
return true; return true;

View File

@ -23,6 +23,7 @@ import net.sf.openrocket.util.BugException;
public class SaveCSVWorker extends SwingWorker<Void, Void> { public class SaveCSVWorker extends SwingWorker<Void, Void> {
private static final int BYTES_PER_FIELD_PER_POINT = 7; private static final int BYTES_PER_FIELD_PER_POINT = 7;
public static final int DEFAULT_DECIMAL_PLACES = 3;
private final File file; private final File file;
private final Simulation simulation; private final Simulation simulation;
@ -30,6 +31,8 @@ public class SaveCSVWorker extends SwingWorker<Void, Void> {
private final FlightDataType[] fields; private final FlightDataType[] fields;
private final Unit[] units; private final Unit[] units;
private final String fieldSeparator; private final String fieldSeparator;
private final int decimalPlaces;
private final boolean isExponentialNotation;
private final String commentStarter; private final String commentStarter;
private final boolean simulationComments; private final boolean simulationComments;
private final boolean fieldComments; private final boolean fieldComments;
@ -37,14 +40,17 @@ public class SaveCSVWorker extends SwingWorker<Void, Void> {
public SaveCSVWorker(File file, Simulation simulation, FlightDataBranch branch, public SaveCSVWorker(File file, Simulation simulation, FlightDataBranch branch,
FlightDataType[] fields, Unit[] units, String fieldSeparator, String commentStarter, FlightDataType[] fields, Unit[] units, String fieldSeparator, int decimalPlaces,
boolean simulationComments, boolean fieldComments, boolean eventComments) { boolean isExponentialNotation, String commentStarter, boolean simulationComments,
boolean fieldComments, boolean eventComments) {
this.file = file; this.file = file;
this.simulation = simulation; this.simulation = simulation;
this.branch = branch; this.branch = branch;
this.fields = fields; this.fields = fields;
this.units = units; this.units = units;
this.fieldSeparator = fieldSeparator; this.fieldSeparator = fieldSeparator;
this.decimalPlaces = decimalPlaces;
this.isExponentialNotation = isExponentialNotation;
this.commentStarter = commentStarter; this.commentStarter = commentStarter;
this.simulationComments = simulationComments; this.simulationComments = simulationComments;
this.fieldComments = fieldComments; this.fieldComments = fieldComments;
@ -72,7 +78,7 @@ public class SaveCSVWorker extends SwingWorker<Void, Void> {
}; };
try { try {
CSVExport.exportCSV(os, simulation, branch, fields, units, fieldSeparator, CSVExport.exportCSV(os, simulation, branch, fields, units, fieldSeparator, decimalPlaces, isExponentialNotation,
commentStarter, simulationComments, fieldComments, eventComments); commentStarter, simulationComments, fieldComments, eventComments);
} finally { } finally {
try { try {
@ -83,8 +89,14 @@ public class SaveCSVWorker extends SwingWorker<Void, Void> {
} }
return null; return null;
} }
public static boolean export(File file, Simulation simulation, FlightDataBranch branch,
FlightDataType[] fields, Unit[] units, String fieldSeparator,
String commentStarter, boolean simulationComments,
boolean fieldComments, boolean eventComments, Window parent) {
return export(file, simulation, branch, fields, units, fieldSeparator, DEFAULT_DECIMAL_PLACES, true,
commentStarter, simulationComments, fieldComments, eventComments, parent);
}
/** /**
* Exports a CSV file using a progress dialog if necessary. * Exports a CSV file using a progress dialog if necessary.
@ -92,14 +104,14 @@ public class SaveCSVWorker extends SwingWorker<Void, Void> {
* @return <code>true</code> if the save was successful, <code>false</code> otherwise. * @return <code>true</code> if the save was successful, <code>false</code> otherwise.
*/ */
public static boolean export(File file, Simulation simulation, FlightDataBranch branch, public static boolean export(File file, Simulation simulation, FlightDataBranch branch,
FlightDataType[] fields, Unit[] units, String fieldSeparator, String commentStarter, FlightDataType[] fields, Unit[] units, String fieldSeparator, int decimalPlaces,
boolean simulationComments, boolean fieldComments, boolean eventComments, boolean isExponentialNotation, String commentStarter, boolean simulationComments,
Window parent) { boolean fieldComments, boolean eventComments, Window parent) {
SaveCSVWorker worker = new SaveCSVWorker(file, simulation, branch, fields, units, SaveCSVWorker worker = new SaveCSVWorker(file, simulation, branch, fields, units,
fieldSeparator, commentStarter, simulationComments, fieldComments, fieldSeparator, decimalPlaces, isExponentialNotation, commentStarter, simulationComments,
eventComments); fieldComments, eventComments);
if (!SwingWorkerDialog.runWorker(parent, "Exporting flight data", if (!SwingWorkerDialog.runWorker(parent, "Exporting flight data",
"Writing " + file.getName() + "...", worker)) { "Writing " + file.getName() + "...", worker)) {