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.but.Selectall = Select all
SimExpPan.but.Selectnone = Select none
SimExpPan.border.Fieldsep = Field separator
SimExpPan.border.FormatSettings = Format settings
SimExpPan.lbl.Fieldsepstr = Field separator string:
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.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.ttip.Includesimudesc = Include a comment at the beginning of the file describing the simulation.
SimExpPan.border.Comments = Comments

View File

@ -29,6 +29,8 @@ public class CSVExport {
* @param fields the fields to export (in appropriate order).
* @param units the units of the fields.
* @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 simulationComments whether to output general simulation comments.
* @param fieldComments whether to output field comments.
@ -37,8 +39,9 @@ public class CSVExport {
*/
public static void exportCSV(OutputStream stream, Simulation simulation,
FlightDataBranch branch, FlightDataType[] fields, Unit[] units,
String fieldSeparator, String commentStarter, boolean simulationComments,
boolean fieldComments, boolean eventComments) throws IOException {
String fieldSeparator, int decimalPlaces, boolean isExponentialNotation,
String commentStarter, boolean simulationComments, boolean fieldComments,
boolean eventComments) throws IOException {
if (fields.length != units.length) {
throw new IllegalArgumentException("fields and units lengths must be equal " +
@ -71,7 +74,7 @@ public class CSVExport {
writer.println();
}
writeData(writer, branch, fields, units, fieldSeparator,
writeData(writer, branch, fields, units, fieldSeparator, decimalPlaces, isExponentialNotation,
eventComments, commentStarter);
@ -87,8 +90,8 @@ public class CSVExport {
}
private static void writeData(PrintWriter writer, FlightDataBranch branch,
FlightDataType[] fields, Unit[] units, String fieldSeparator, boolean eventComments,
String commentStarter) {
FlightDataType[] fields, Unit[] units, String fieldSeparator, int decimalPlaces, boolean isExponentialNotation,
boolean eventComments, String commentStarter) {
// Number of data points
int n = branch.getLength();
@ -132,7 +135,8 @@ public class CSVExport {
// Store CSV line
for (int i = 0; i < fields.length; i++) {
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) {
writer.print(fieldSeparator);
}

View File

@ -46,6 +46,8 @@ public abstract class Preferences implements ChangeSource {
public static final String DEFAULT_MACH_NUMBER = "DefaultMachNumber";
// Preferences related to data export
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_FIELD_NAME_COMMENT = "ExportFieldDescriptionComment";
public static final String EXPORT_EVENT_COMMENTS = "ExportEventComments";

View File

@ -40,50 +40,81 @@ public class TextUtil {
}
return sb.toString();
}
/**
* 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.
*
*
* @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.
*/
public static final String doubleToString(double d) {
public static String doubleToString(double d, int decimalPlaces, boolean isExponentialNotation) {
// Check for special cases
if (MathUtil.equals(d, 0))
return "0";
if (Double.isNaN(d))
return "NaN";
if (Double.isInfinite(d)) {
if (d < 0)
return "-Inf";
else
return "Inf";
}
final String sign = (d < 0) ? "-" : "";
double abs = Math.abs(d);
// 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);
}
// Check whether decimal or exponential notation is shorter
String exp = exponentialFormat(abs);
String dec = decimalFormat(abs);
if (dec.length() <= exp.length())
String dec;
if (decimalPlaces < 0) {
dec = decimalFormat(abs);
} else {
dec = decimalFormat(abs, decimalPlaces);
}
if (dec.length() <= exp.length() || !isExponentialNotation)
return sign + dec;
else
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);
}
/*
* 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.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.SpinnerModel;
import javax.swing.SpinnerNumberModel;
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.gui.util.SaveCSVWorker;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.startup.Preferences;
@ -27,6 +31,8 @@ public class CsvOptionPanel extends JPanel {
private final String baseClassName;
private final JComboBox<String> fieldSeparator;
private final JSpinner decimalPlacesSpinner;
private final JCheckBox exponentialNotationCheckbox;
private final JCheckBox[] options;
private final JComboBox<String> commentCharacter;
@ -48,10 +54,11 @@ public class CsvOptionPanel extends JPanel {
// TODO: HIGH: Rename the translation keys
// Field separator panel
// Format settings panel
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"));
tip = trans.get("SimExpPan.lbl.longA1") +
trans.get("SimExpPan.lbl.longA2");
@ -62,8 +69,25 @@ public class CsvOptionPanel extends JPanel {
fieldSeparator.setEditable(true);
fieldSeparator.setSelectedItem(Application.getPreferences().getString(Preferences.EXPORT_FIELD_SEPARATOR, ","));
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");
@ -104,6 +128,14 @@ public class CsvOptionPanel extends JPanel {
public String getFieldSeparator() {
return fieldSeparator.getSelectedItem().toString();
}
public int getDecimalPlaces() {
return (Integer) decimalPlacesSpinner.getValue();
}
public boolean isExponentialNotation() {
return exponentialNotationCheckbox.isSelected();
}
public String getCommentCharacter() {
return commentCharacter.getSelectedItem().toString();
@ -118,6 +150,8 @@ public class CsvOptionPanel extends JPanel {
*/
public void storePreferences() {
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());
for (int i = 0; i < options.length; i++) {
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 fieldSep = csvOptions.getFieldSeparator();
int decimalPlaces = csvOptions.getDecimalPlaces();
boolean isExponentialNotation = csvOptions.isExponentialNotation();
boolean simulationComment = csvOptions.getSelectionOption(OPTION_SIMULATION_COMMENTS);
boolean fieldComment = csvOptions.getSelectionOption(OPTION_FIELD_DESCRIPTIONS);
boolean eventComment = csvOptions.getSelectionOption(OPTION_FLIGHT_EVENTS);
@ -245,8 +247,8 @@ public class SimulationExportPanel extends JPanel {
}
SaveCSVWorker.export(file, simulation, branch, fieldTypes, fieldUnits, fieldSep,
commentChar, simulationComment, fieldComment, eventComment,
SaveCSVWorker.export(file, simulation, branch, fieldTypes, fieldUnits, fieldSep, decimalPlaces,
isExponentialNotation, commentChar, simulationComment, fieldComment, eventComment,
SwingUtilities.getWindowAncestor(this));
}

View File

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

View File

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