Skip to content

Commit 0c42b21

Browse files
authored
add wind level exports (#29)
* add wind level exports * remove unneeded check for data * bump gradle for release
1 parent 26d8687 commit 0c42b21

File tree

5 files changed

+192
-41
lines changed

5 files changed

+192
-41
lines changed

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ plugins {
66
}
77

88
group 'com.waterloorocketry'
9-
version '1.4.0'
9+
version '1.5.0'
1010

1111
ext {
1212
openRocketVersion = '24.12'
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.waterloorocketry.openrocket_monte_carlo;
2+
3+
public interface Listener {
4+
void update();
5+
}

or-monte-carlo/src/com/waterloorocketry/openrocket_monte_carlo/SimulationData.java

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,11 @@
1010
import info.openrocket.core.unit.Unit;
1111
import info.openrocket.core.unit.UnitGroup;
1212
import info.openrocket.core.util.Chars;
13+
import info.openrocket.core.util.ListenerList;
1314
import org.slf4j.Logger;
1415
import org.slf4j.LoggerFactory;
1516

16-
import java.util.ArrayList;
17-
import java.util.Collections;
18-
import java.util.Comparator;
19-
import java.util.List;
20-
import java.util.Optional;
17+
import java.util.*;
2118
import java.util.stream.Collectors;
2219

2320
/**
@@ -39,6 +36,8 @@ public class SimulationData {
3936

4037
private final double temperature;
4138
private final double pressure;
39+
private final MultiLevelPinkNoiseWindModel windModel;
40+
private final List<Listener> listeners = new ArrayList<>();
4241
private Simulation simulation;
4342
private double apogee;
4443
private double maxVelocity;
@@ -51,8 +50,11 @@ public SimulationData(Simulation simulation) {
5150
this.simulation = simulation;
5251
this.name = simulation.getName();
5352

54-
Optional<MultiLevelPinkNoiseWindModel.LevelWindModel> maxWindSpdLevel = simulation.getOptions().getMultiLevelWindModel().getLevels().stream()
55-
.max(Comparator.comparingDouble(MultiLevelPinkNoiseWindModel.LevelWindModel::getSpeed));
53+
this.windModel = simulation.getOptions().getMultiLevelWindModel();
54+
55+
Optional<MultiLevelPinkNoiseWindModel.LevelWindModel> maxWindSpdLevel =
56+
simulation.getOptions().getMultiLevelWindModel().getLevels().stream()
57+
.max(Comparator.comparingDouble(MultiLevelPinkNoiseWindModel.LevelWindModel::getSpeed));
5658
maxWindSpeed = 0;
5759
maxWindDirection = 0;
5860
if (maxWindSpdLevel.isPresent()) {
@@ -158,6 +160,24 @@ public void processData(boolean keepSimulationObject) throws SimulationException
158160
this.hasData = true;
159161
if (!keepSimulationObject)
160162
this.simulation = null; // remove the simulation object to save memory
163+
164+
notifyListeners();
165+
}
166+
167+
public String exportWindLevels() {
168+
StringBuilder sb = new StringBuilder();
169+
sb.append("altitude,speed,direction,stddev,windDirStdDev").append("\n");
170+
List<MultiLevelPinkNoiseWindModel.LevelWindModel> levels = windModel.getLevels();
171+
for (int i = 0; i < levels.size(); i++) {
172+
MultiLevelPinkNoiseWindModel.LevelWindModel level = levels.get(i);
173+
sb.append(UnitGroup.UNITS_LENGTH.getUnit("ft").toUnit(level.getAltitude())).append(",")
174+
.append(UnitGroup.UNITS_VELOCITY.getUnit("mph").toUnit(level.getSpeed())).append(",")
175+
.append(UnitGroup.UNITS_ANGLE.getUnit("" + Chars.DEGREE).toUnit(level.getDirection())).append(",")
176+
.append(UnitGroup.UNITS_VELOCITY.getUnit("mph").toUnit(level.getStandardDeviation())).append(",")
177+
.append(UnitGroup.UNITS_ANGLE.getUnit("" + Chars.DEGREE).toUnit(level.getWindDirStdDev()))
178+
.append("\n");
179+
}
180+
return sb.toString();
161181
}
162182

163183
/**
@@ -284,6 +304,20 @@ public double getMaxWindDirectionInDegrees() {
284304
.toUnit(this.getMaxWindDirection());
285305
}
286306

307+
public MultiLevelPinkNoiseWindModel getWindModel() {
308+
return windModel;
309+
}
310+
311+
public void notifyListeners() {
312+
for (Listener listener : listeners) {
313+
listener.update();
314+
}
315+
}
316+
317+
public void addListener(Listener listener) {
318+
listeners.add(listener);
319+
}
320+
287321
@Override
288322
public String toString() {
289323
return "Simulation " + name + ": " +

or-monte-carlo/src/com/waterloorocketry/openrocket_monte_carlo/SimulationOptionsFrame.java

Lines changed: 65 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,15 @@
3838
import java.beans.PropertyChangeListener;
3939
import java.beans.PropertyChangeSupport;
4040
import java.io.File;
41+
import java.io.FileNotFoundException;
42+
import java.io.FileOutputStream;
43+
import java.io.IOException;
4144
import java.lang.reflect.Field;
45+
import java.nio.charset.StandardCharsets;
4246
import java.util.Collections;
4347
import java.util.List;
48+
import java.util.zip.ZipEntry;
49+
import java.util.zip.ZipOutputStream;
4450
import javax.swing.*;
4551
import javax.swing.table.DefaultTableModel;
4652

@@ -200,51 +206,24 @@ private JPanel addBottomPanel() {
200206
}
201207

202208
private @NotNull JPanel addSimulationListPanel() {
203-
JPanel simulationListPanel = new JPanel(new MigLayout("fill"));
209+
JPanel simulationListPanel = new JPanel(new MigLayout("fill, wrap 1"));
204210
simulationListPanel.setBorder(BorderFactory.createTitledBorder("Simulations"));
205211

206212
// Create table model and table
207213
String[] columnNames =
208214
{"Simulation Name", "Wind Speed(mph)", "Wind Direction(°)", "Temperature(°C)", "Pressure(mbar)",
209215
"Apogee(ft)", "Max Velocity(m/s)", "Min Stability"};
210-
DefaultTableModel tableModel = new DefaultTableModel(columnNames, 0) {
211-
@Override
212-
public boolean isCellEditable(int row, int column) {
213-
return false; // Disable editing for all cells
214-
}
215-
};
216+
SimulationTableModel tableModel = new SimulationTableModel();
216217
JTable simulationTable = new JTable(tableModel);
217218

218219
simulationListPanel.add(new JScrollPane(simulationTable), "grow, push");
219220

220221
PropertyChangeListener tableChangeHandler = evt -> {
222+
tableModel.clearSimulations();
221223
if (simulationEngine == null) {
222-
tableModel.setRowCount(0);
223224
return;
224225
}
225-
226-
tableModel.setRowCount(0); // Clear existing rows
227-
for (SimulationData data : simulationEngine.getData()) {
228-
String name = data.getName();
229-
230-
double temp = data.getTemperatureInCelsius();
231-
double pressure = data.getPressureInMBar();
232-
233-
double windSpeed = data.getMaxWindSpeedInMPH();
234-
double windDirection = data.getMaxWindDirectionInDegrees();
235-
236-
double apogee = 0;
237-
double maxVelocity = 0;
238-
double minStability = 0;
239-
if (data.hasData()) {
240-
apogee = data.getApogeeInFeet();
241-
maxVelocity = data.getMaxVelocity();
242-
minStability = data.getMinStability().get(0);
243-
}
244-
245-
tableModel.addRow(new Object[]{name, windSpeed, windDirection, temp, pressure,
246-
apogee, maxVelocity, minStability});
247-
}
226+
tableModel.addSimulations(simulationEngine.getData());
248227
};
249228

250229
simulationTable.addMouseListener(new MouseAdapter() {
@@ -270,10 +249,63 @@ public void mouseClicked(java.awt.event.MouseEvent e) {
270249
}
271250
});
272251

273-
// Add listener to update table when simulations are configured
252+
// Add listener to add rows when simulations are configured
274253
pcs.addPropertyChangeListener(SIMULATIONS_CONFIGURED_EVENT, tableChangeHandler);
275254

276-
pcs.addPropertyChangeListener(SIMULATIONS_PROCESSED_EVENT, tableChangeHandler);
255+
JButton exportButton = new JButton("Export Wind Levels", Icons.EXPORT);
256+
exportButton.addActionListener(e -> {
257+
int[] selectedIdx = simulationTable.getSelectedRows();
258+
if (selectedIdx.length == 0) {
259+
log.warn("No simulations selected for export");
260+
JOptionPane.showMessageDialog(SimulationOptionsFrame.this,
261+
"Select a simulation to export wind levels.",
262+
"Export Failed",
263+
JOptionPane.ERROR_MESSAGE);
264+
return;
265+
}
266+
267+
// select directory to save to
268+
JFileChooser chooser = new JFileChooser();
269+
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
270+
chooser.setMultiSelectionEnabled(false);
271+
chooser.setCurrentDirectory(((SwingPreferences) Application.getPreferences()).getDefaultDirectory());
272+
int option = chooser.showOpenDialog(this);
273+
if (option != JFileChooser.APPROVE_OPTION) {
274+
log.info(Markers.USER_MARKER, "Decided not to export wind levels, option={}", option);
275+
return;
276+
}
277+
278+
File file = new File(chooser.getSelectedFile().getAbsolutePath() + "/wind_level_export.zip");
279+
if (file.exists()) {
280+
int response = JOptionPane.showConfirmDialog(SimulationOptionsFrame.this,
281+
"File " + file.getName() + " already exists. Overwrite?",
282+
"Confirm Overwrite",
283+
JOptionPane.YES_NO_OPTION,
284+
JOptionPane.WARNING_MESSAGE);
285+
if (response != JOptionPane.YES_OPTION) {
286+
log.info("Decided not to overwrite existing export file {}", file.getAbsolutePath());
287+
return;
288+
}
289+
file.delete();
290+
}
291+
log.info("Export to file {}", file);
292+
try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(file))) {
293+
for (int idx : selectedIdx) {
294+
SimulationData data = tableModel.getDataAt(idx);
295+
296+
log.debug("Exporting simulation data for {}", data.getName());
297+
298+
ZipEntry entry = new ZipEntry(data.getName() + ".csv");
299+
out.putNextEntry(entry);
300+
out.write(data.exportWindLevels().getBytes(StandardCharsets.UTF_8));
301+
out.closeEntry();
302+
}
303+
} catch (IOException ex) {
304+
log.error(ex.toString());
305+
}
306+
});
307+
308+
simulationListPanel.add(exportButton, "left");
277309

278310
return simulationListPanel;
279311
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package com.waterloorocketry.openrocket_monte_carlo;
2+
3+
import javax.swing.table.AbstractTableModel;
4+
import java.util.ArrayList;
5+
import java.util.List;
6+
7+
public class SimulationTableModel extends AbstractTableModel implements Listener {
8+
private final List<SimulationData> data = new ArrayList<>();
9+
private final String[] columnNames =
10+
{"Simulation Name", "Wind Speed(mph)", "Wind Direction(°)", "Temperature(°C)", "Pressure(mbar)",
11+
"Apogee(ft)", "Max Velocity(m/s)", "Min Stability"};
12+
13+
public void addSimulation(SimulationData simulation) {
14+
simulation.addListener(this);
15+
data.add(simulation);
16+
fireTableDataChanged();
17+
}
18+
19+
public void addSimulations(List<SimulationData> simulations) {
20+
for (SimulationData simulation : simulations) {
21+
addSimulation(simulation);
22+
}
23+
}
24+
25+
public void clearSimulations() {
26+
data.clear();
27+
fireTableDataChanged();
28+
}
29+
30+
@Override
31+
public String getColumnName(int column) {
32+
return columnNames[column];
33+
}
34+
35+
@Override
36+
public int getRowCount() {
37+
return data.size();
38+
}
39+
40+
@Override
41+
public int getColumnCount() {
42+
return columnNames.length;
43+
}
44+
45+
@Override
46+
public Object getValueAt(int rowIndex, int columnIndex) {
47+
switch (columnIndex) {
48+
case 0:
49+
return data.get(rowIndex).getName();
50+
case 1:
51+
return data.get(rowIndex).getMaxWindSpeedInMPH();
52+
case 2:
53+
return data.get(rowIndex).getMaxWindDirectionInDegrees();
54+
case 3:
55+
return data.get(rowIndex).getTemperatureInCelsius();
56+
case 4:
57+
return data.get(rowIndex).getPressureInMBar();
58+
case 5:
59+
return data.get(rowIndex).getApogeeInFeet();
60+
case 6:
61+
return data.get(rowIndex).getMaxVelocity();
62+
case 7:
63+
if (data.get(rowIndex).getMinStability().isEmpty()) {
64+
return null;
65+
}
66+
return data.get(rowIndex).getMinStability().get(0);
67+
default:
68+
return null;
69+
}
70+
}
71+
72+
public SimulationData getDataAt(int rowIndex) {
73+
return data.get(rowIndex);
74+
}
75+
76+
@Override
77+
public void update() {
78+
fireTableDataChanged();
79+
}
80+
}

0 commit comments

Comments
 (0)