├── .github └── workflows │ └── build.yml ├── .gitignore ├── .gitmodules ├── FlightPlot.iml ├── FlightPlot.ipr ├── README.md ├── build.xml ├── flightplot.icns ├── generate_csv.sh ├── lib ├── AppleJavaExtensions.jar ├── annotations.jar ├── asm4-all.jar ├── forms_rt.jar ├── jarbundler-2.4.0.jar ├── javac2.jar ├── jcommon-1.0.17.jar ├── jdom.jar ├── jfreechart-1.0.14.jar ├── universalJavaApplicationStub └── vecmath.jar ├── packaging ├── archlinux │ └── PKGBUILD └── ubuntu │ └── FlightPlot │ ├── DEBIAN │ └── control │ └── usr │ └── share │ ├── applications │ └── flightplot.desktop │ └── icons │ └── 64x64 │ └── apps │ └── flightplot.png └── src ├── me └── drton │ └── flightplot │ ├── AddProcessorDialog.form │ ├── AddProcessorDialog.java │ ├── ColorParamTableCellEditor.java │ ├── ColorSupplier.java │ ├── FieldsListDialog.form │ ├── FieldsListDialog.java │ ├── FlightPlot.form │ ├── FlightPlot.java │ ├── LogInfo.form │ ├── LogInfo.java │ ├── Marker.java │ ├── MarkersList.java │ ├── OSValidator.java │ ├── ParamValueTableCellEditor.java │ ├── ParamValueTableCellRenderer.java │ ├── PlotExportDialog.form │ ├── PlotExportDialog.java │ ├── PlotItem.java │ ├── PreferencesUtil.java │ ├── Preset.java │ ├── ProcessorPreset.java │ ├── Series.java │ ├── TaggedValueMarker.java │ ├── XYPoint.java │ ├── export │ ├── AbstractTrackExporter.java │ ├── AbstractTrackReader.java │ ├── GPXTrackExporter.java │ ├── KMLTrackExporter.java │ ├── PX4TrackReader.java │ ├── TrackExportDialog.form │ ├── TrackExportDialog.java │ ├── TrackExporter.java │ ├── TrackExporterConfiguration.java │ ├── TrackPoint.java │ ├── TrackReader.java │ ├── TrackReaderConfiguration.java │ ├── TrackReaderFactory.java │ └── ULogTrackReader.java │ └── processors │ ├── ATan2.java │ ├── Abs.java │ ├── Battery.java │ ├── Derivative.java │ ├── EulerFromQuaternion.java │ ├── Expression.java │ ├── GlobalPositionProjection.java │ ├── Integral.java │ ├── LandDetector.java │ ├── NEDFromBodyProjection.java │ ├── PlotProcessor.java │ ├── PosPIDControlSimulator.java │ ├── PosRatePIDControlSimulator.java │ ├── PositionEstimator.java │ ├── PositionEstimatorKF.java │ ├── ProcessorsList.java │ ├── Simple.java │ ├── Text.java │ └── tools │ ├── LowPassFilter.java │ └── PID.java ├── net └── objecthunter │ └── exp4j │ ├── Expression.java │ ├── ExpressionBuilder.java │ ├── ValidationResult.java │ ├── function │ ├── Function.java │ └── Functions.java │ ├── operator │ ├── Operator.java │ └── Operators.java │ ├── shuntingyard │ └── ShuntingYard.java │ └── tokenizer │ ├── ArgumentSeparatorToken.java │ ├── CloseParenthesesToken.java │ ├── FunctionToken.java │ ├── NumberToken.java │ ├── OpenParenthesesToken.java │ ├── OperatorToken.java │ ├── Token.java │ ├── Tokenizer.java │ └── VariableToken.java └── org └── json ├── JSONArray.java ├── JSONException.java ├── JSONObject.java ├── JSONString.java └── JSONTokener.java /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ '*' ] 6 | pull_request: 7 | branches: [ master ] 8 | release: 9 | types: [ created ] 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | with: 17 | submodules: 'recursive' 18 | - name: Set up JDK 19 | uses: actions/setup-java@v3 20 | with: 21 | java-version: '8' 22 | distribution: 'temurin' 23 | - name: Build with Ant 24 | run: ant zip_artifacts 25 | - name: Upload artifacts 26 | uses: softprops/action-gh-release@v1 27 | if: ${{ github.event_name == 'release' }} 28 | with: 29 | files: | 30 | out/production/flightplot.jar.zip 31 | out/production/flightplot.app.zip 32 | env: 33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | out 3 | /bin 4 | /.settings 5 | /.classpath 6 | /.project 7 | *.iws 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "jMAVlib"] 2 | path = jMAVlib 3 | url = https://github.com/PX4/jMAVlib.git 4 | -------------------------------------------------------------------------------- /FlightPlot.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FlightPlot 2 | ========== 3 | 4 | [![Build Status](https://travis-ci.org/PX4/FlightPlot.svg?branch=master)](https://travis-ci.org/PX4/FlightPlot) 5 | 6 | Universal flight log plotter 7 | 8 | Releases can be found on [GitHub releases](https://github.com/PX4/FlightPlot/releases). 9 | 10 | Overview 11 | -------- 12 | 13 | ### Supported formats: 14 | - PX4 log (.px4log, .bin) 15 | - APM log (.bin) 16 | - ULog (.ulg) 17 | 18 | ### Features: 19 | - Data processing: low pass filtering, scaling, shifting, derivative, integral, etc. 20 | - Track export in KML and GPS format 21 | - Saving plot as image 22 | 23 | 24 | Building from source 25 | -------------------- 26 | 27 | Requirements: 28 | - Java 6 or newer (JDK, http://www.oracle.com/technetwork/java/javase/downloads/index.html) 29 | - ant 30 | 31 | Clone the repository. The `--recursive` flag is required to pull in the [jMAVlib](https://github.com/PX4/jMAVlib) submodule). 32 | ``` 33 | git clone --recursive https://github.com/PX4/FlightPlot.git 34 | ``` 35 | 36 | Build: 37 | ``` 38 | cd FlightPlot 39 | ant 40 | ``` 41 | 42 | If you want to create deb file for ubuntu, use gen_deb. 43 | ``` 44 | cd FlightPlot 45 | ant gen_deb 46 | sudo dpkg -i out/production/FlightPlot.deb 47 | ``` 48 | 49 | Run: 50 | ``` 51 | java -jar out/production/flightplot.jar 52 | ``` 53 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /flightplot.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PX4/FlightPlot/3955296e32f82e76c4e5bd5b1c724b2162039880/flightplot.icns -------------------------------------------------------------------------------- /generate_csv.sh: -------------------------------------------------------------------------------- 1 | java -classpath out/production/flightplot.jar me/drton/jmavlib/log/ulog/ULogReader 2 | 3 | -------------------------------------------------------------------------------- /lib/AppleJavaExtensions.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PX4/FlightPlot/3955296e32f82e76c4e5bd5b1c724b2162039880/lib/AppleJavaExtensions.jar -------------------------------------------------------------------------------- /lib/annotations.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PX4/FlightPlot/3955296e32f82e76c4e5bd5b1c724b2162039880/lib/annotations.jar -------------------------------------------------------------------------------- /lib/asm4-all.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PX4/FlightPlot/3955296e32f82e76c4e5bd5b1c724b2162039880/lib/asm4-all.jar -------------------------------------------------------------------------------- /lib/forms_rt.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PX4/FlightPlot/3955296e32f82e76c4e5bd5b1c724b2162039880/lib/forms_rt.jar -------------------------------------------------------------------------------- /lib/jarbundler-2.4.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PX4/FlightPlot/3955296e32f82e76c4e5bd5b1c724b2162039880/lib/jarbundler-2.4.0.jar -------------------------------------------------------------------------------- /lib/javac2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PX4/FlightPlot/3955296e32f82e76c4e5bd5b1c724b2162039880/lib/javac2.jar -------------------------------------------------------------------------------- /lib/jcommon-1.0.17.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PX4/FlightPlot/3955296e32f82e76c4e5bd5b1c724b2162039880/lib/jcommon-1.0.17.jar -------------------------------------------------------------------------------- /lib/jdom.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PX4/FlightPlot/3955296e32f82e76c4e5bd5b1c724b2162039880/lib/jdom.jar -------------------------------------------------------------------------------- /lib/jfreechart-1.0.14.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PX4/FlightPlot/3955296e32f82e76c4e5bd5b1c724b2162039880/lib/jfreechart-1.0.14.jar -------------------------------------------------------------------------------- /lib/vecmath.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PX4/FlightPlot/3955296e32f82e76c4e5bd5b1c724b2162039880/lib/vecmath.jar -------------------------------------------------------------------------------- /packaging/archlinux/PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer: Thomas Gubler 2 | _libname=flightplot 3 | pkgname=java-${_libname}-git 4 | pkgver=20150101 5 | pkgrel=1 6 | pkgdesc="PX4/APM flight log plotter" 7 | arch=(any) 8 | url="https://pixhawk.org/dev/flightplot" 9 | license=('unknown') 10 | depends=('java-runtime') 11 | makedepends=('apache-ant') 12 | optdepends=() 13 | provides=('java-flightplot') 14 | conflicts=('java-flightplot') 15 | options=(!emptydirs) 16 | md5sums=('SKIP') 17 | 18 | _gitroot="https://github.com/DrTon/FlightPlot.git" 19 | _gitname="FlightPlot" 20 | source=(git+$_gitroot) 21 | 22 | prepare() { 23 | cd "$srcdir/$_gitname" 24 | git submodule init 25 | git submodule update 26 | } 27 | 28 | build() { 29 | cd "$srcdir/$_gitname" 30 | ant flightplot 31 | 32 | printf "#!/bin/sh\nexec /usr/bin/java -jar '/usr/share/java/${_libname}/flightplot.jar' '$@'" > archlinux_start_script 33 | } 34 | 35 | package() { 36 | cd "$srcdir/$_gitname" 37 | install -Dm755 out/production/flightplot.jar ${pkgdir}/usr/share/java/${_libname}/${_libname}.jar 38 | install -Dm755 archlinux_start_script ${pkgdir}/usr/bin/${_libname} 39 | } 40 | 41 | pkgver() { 42 | cd "$pkgname" 43 | printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)" 44 | } 45 | 46 | # vim:set ts=2 sw=2 et: 47 | -------------------------------------------------------------------------------- /packaging/ubuntu/FlightPlot/DEBIAN/control: -------------------------------------------------------------------------------- 1 | Package: FlightPlot 2 | Version: 0.2.20 3 | Section: devel 4 | Priority: optional 5 | Architecture: all 6 | Depends: build-essential, 7 | cmake 8 | Maintainer: Anton Babushkin , SungTae Moon 9 | Homepage: https://github.com/DrTon/FlightPlot 10 | Description: Universal flight log plotter 11 | 12 | -------------------------------------------------------------------------------- /packaging/ubuntu/FlightPlot/usr/share/applications/flightplot.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Encoding=UTF-8 3 | Name=FlightPlot 4 | Type=Application 5 | Exec=java -jar /usr/local/bin/flightplot.jar 6 | Terminal=false 7 | Icon=/usr/share/icons/64x64/apps/flightplot.png 8 | Comment=FlightPlot 9 | Categories=Utility;Application; 10 | Name=FlightPlot 11 | -------------------------------------------------------------------------------- /packaging/ubuntu/FlightPlot/usr/share/icons/64x64/apps/flightplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PX4/FlightPlot/3955296e32f82e76c4e5bd5b1c724b2162039880/packaging/ubuntu/FlightPlot/usr/share/icons/64x64/apps/flightplot.png -------------------------------------------------------------------------------- /src/me/drton/flightplot/AddProcessorDialog.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 |
102 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/AddProcessorDialog.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot; 2 | 3 | import me.drton.flightplot.processors.PlotProcessor; 4 | 5 | import javax.swing.*; 6 | import java.awt.event.*; 7 | 8 | public class AddProcessorDialog extends JDialog { 9 | private JPanel contentPane; 10 | private JButton buttonOK; 11 | private JButton buttonCancel; 12 | private JTextField titleField; 13 | private JList processorTypesList; 14 | private DefaultListModel processorTypesListModel; 15 | private String[] processorTypes; 16 | 17 | private ProcessorPreset origProcessorPreset = null; 18 | private Runnable callback; 19 | 20 | public AddProcessorDialog(String[] processorTypes) { 21 | this.processorTypes = processorTypes; 22 | //this.processorTypes = processorTypes; 23 | setContentPane(contentPane); 24 | setModal(true); 25 | setTitle("Add Processor"); 26 | getRootPane().setDefaultButton(buttonOK); 27 | buttonOK.addActionListener(new ActionListener() { 28 | public void actionPerformed(ActionEvent e) { 29 | onOK(); 30 | } 31 | }); 32 | buttonCancel.addActionListener(new ActionListener() { 33 | public void actionPerformed(ActionEvent e) { 34 | onCancel(); 35 | } 36 | }); 37 | // call onCancel() when cross is clicked 38 | setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); 39 | addWindowListener(new WindowAdapter() { 40 | public void windowClosing(WindowEvent e) { 41 | onCancel(); 42 | } 43 | }); 44 | // call onCancel() on ESCAPE 45 | contentPane.registerKeyboardAction(new ActionListener() { 46 | public void actionPerformed(ActionEvent e) { 47 | onCancel(); 48 | } 49 | }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 50 | } 51 | 52 | public String getProcessorTitle() { 53 | return titleField.getText(); 54 | } 55 | 56 | public ProcessorPreset getOrigProcessorPreset() { 57 | return origProcessorPreset; 58 | } 59 | 60 | public String getProcessorType() { 61 | return (String) processorTypesList.getSelectedValue(); 62 | } 63 | 64 | public void display(Runnable callback, ProcessorPreset processorPreset) { 65 | if (processorTypesListModel.size() == 0) { 66 | for (String processorType : processorTypes) { 67 | processorTypesListModel.addElement(processorType); 68 | } 69 | processorTypesList.setSelectedValue("Simple", true); 70 | } 71 | this.callback = callback; 72 | if (processorPreset != null) { 73 | origProcessorPreset = processorPreset; 74 | titleField.setText(processorPreset.getTitle()); 75 | processorTypesList.setSelectedValue(processorPreset.getProcessorType(), true); 76 | } else { 77 | origProcessorPreset = null; 78 | titleField.setText(""); 79 | } 80 | titleField.requestFocus(); 81 | this.setVisible(true); 82 | } 83 | 84 | private void onOK() { 85 | setVisible(false); 86 | callback.run(); 87 | } 88 | 89 | private void onCancel() { 90 | setVisible(false); 91 | } 92 | 93 | private void createUIComponents() { 94 | processorTypesListModel = new DefaultListModel(); 95 | processorTypesList = new JList(processorTypesListModel); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/ColorParamTableCellEditor.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot; 2 | 3 | import javax.swing.*; 4 | import javax.swing.table.TableCellEditor; 5 | import java.awt.*; 6 | 7 | /** 8 | * Created by ton on 13.03.15. 9 | */ 10 | class ColorParamTableCellEditor extends AbstractCellEditor implements TableCellEditor { 11 | private ColorSupplier colorSupplier; 12 | private Color color; 13 | private JComboBox select; 14 | 15 | public ColorParamTableCellEditor(ColorSupplier colorSupplier) { 16 | this.colorSupplier = colorSupplier; 17 | select = new JComboBox(); 18 | select.setRenderer(new ColorCellRenderer()); 19 | for (Paint paint : colorSupplier.getPaintSequence()) { 20 | select.addItem(paint); 21 | } 22 | } 23 | 24 | public JComboBox getComponent() { 25 | return select; 26 | } 27 | 28 | @Override 29 | public Object getCellEditorValue() { 30 | return colorSupplier.getPaint(select.getSelectedIndex()); 31 | } 32 | 33 | @Override 34 | public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { 35 | color = (Color) value; 36 | select.setSelectedItem(color); 37 | return select; 38 | } 39 | 40 | private class ColorCellRenderer extends JLabel implements ListCellRenderer { 41 | boolean setBg = false; 42 | 43 | public ColorCellRenderer() { 44 | setOpaque(true); 45 | setPreferredSize(new Dimension(0, 15)); 46 | } 47 | 48 | @Override 49 | public void setBackground(Color bg) { 50 | if (!setBg) { 51 | return; 52 | } 53 | super.setBackground(bg); 54 | } 55 | 56 | public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { 57 | setBg = true; 58 | setText(""); 59 | setBackground((Color) value); 60 | setBorder(BorderFactory.createEmptyBorder()); 61 | setBg = false; 62 | 63 | if (isSelected) { 64 | setBorder(BorderFactory.createLineBorder(Color.white, 2)); 65 | } 66 | 67 | return this; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/ColorSupplier.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot; 2 | 3 | import java.awt.*; 4 | 5 | /** 6 | * Created by ada on 22.12.14. 7 | */ 8 | public class ColorSupplier { 9 | private Color[] paintSequence; 10 | private int[] paintUsage; 11 | 12 | { 13 | paintSequence = new Color[]{ 14 | Color.RED, 15 | Color.GREEN, 16 | Color.BLUE, 17 | Color.CYAN, 18 | Color.MAGENTA, 19 | Color.BLACK, 20 | Color.LIGHT_GRAY, 21 | Color.ORANGE, 22 | Color.RED.darker(), 23 | Color.GREEN.darker(), 24 | Color.BLUE.darker(), 25 | Color.CYAN.darker(), 26 | Color.MAGENTA.darker(), 27 | Color.ORANGE.darker(), 28 | }; 29 | 30 | paintUsage = new int[paintSequence.length]; 31 | } 32 | 33 | public Color[] getPaintSequence() { 34 | return paintSequence; 35 | } 36 | 37 | public Color getPaint(int idx) { 38 | return paintSequence[idx]; 39 | } 40 | 41 | public void resetColorsUsed() { 42 | for (int i = 0; i < paintSequence.length; i++) { 43 | paintUsage[i] = 0; 44 | } 45 | } 46 | 47 | public void markColorUsed(Color color) { 48 | for (int i = 0; i < paintSequence.length; i++) { 49 | if (color.equals(paintSequence[i])) { 50 | markColorUsed(i); 51 | } 52 | } 53 | } 54 | 55 | public void markColorUsed(int color_idx) { 56 | paintUsage[color_idx]++; 57 | } 58 | 59 | /** 60 | * Select color with minimal usage 61 | * 62 | * @param field 63 | * @return 64 | */ 65 | public Color getNextColor(String field) { 66 | int minUsage = -1; 67 | int color_idx = 0; 68 | for (int i = 0; i < paintSequence.length; i++) { 69 | if (paintUsage[i] < minUsage || minUsage < 0) { 70 | minUsage = paintUsage[i]; 71 | color_idx = i; 72 | } 73 | if (minUsage == 0) { 74 | markColorUsed(color_idx); 75 | break; 76 | } 77 | } 78 | return paintSequence[color_idx]; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/FieldsListDialog.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 |
98 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/FieldsListDialog.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot; 2 | 3 | import java.awt.event.ActionEvent; 4 | import java.awt.event.ActionListener; 5 | import java.awt.event.KeyEvent; 6 | import java.awt.event.MouseAdapter; 7 | import java.awt.event.MouseEvent; 8 | import java.awt.event.WindowAdapter; 9 | import java.awt.event.WindowEvent; 10 | import java.util.ArrayList; 11 | import java.util.Collections; 12 | import java.util.List; 13 | import java.util.Map; 14 | import java.util.regex.Pattern; 15 | 16 | import javax.swing.JButton; 17 | import javax.swing.JComponent; 18 | import javax.swing.JDialog; 19 | import javax.swing.JPanel; 20 | import javax.swing.JTable; 21 | import javax.swing.JTextField; 22 | import javax.swing.KeyStroke; 23 | import javax.swing.RowFilter; 24 | import javax.swing.table.DefaultTableModel; 25 | import javax.swing.table.TableRowSorter; 26 | import javax.swing.event.DocumentListener; 27 | import javax.swing.event.DocumentEvent; 28 | 29 | public class FieldsListDialog extends JDialog { 30 | private JPanel contentPane; 31 | private JButton buttonAdd; 32 | private JTable fieldsTable; 33 | private JButton buttonClose; 34 | private JTextField textSearch; 35 | private DefaultTableModel fieldsTableModel; 36 | private TableRowSorter sorter; 37 | 38 | public FieldsListDialog(final Runnable callbackAdd) { 39 | setContentPane(contentPane); 40 | setModal(false); 41 | setTitle("Fields List"); 42 | getRootPane().setDefaultButton(buttonAdd); 43 | buttonAdd.addActionListener(new ActionListener() { 44 | @Override 45 | public void actionPerformed(ActionEvent e) { 46 | callbackAdd.run(); 47 | } 48 | }); 49 | buttonClose.addActionListener(new ActionListener() { 50 | public void actionPerformed(ActionEvent e) { 51 | onClose(); 52 | } 53 | }); 54 | // call onClose() when cross is clicked 55 | setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); 56 | addWindowListener(new WindowAdapter() { 57 | public void windowClosing(WindowEvent e) { 58 | onClose(); 59 | } 60 | }); 61 | // call onClose() on ESCAPE 62 | contentPane.registerKeyboardAction(new ActionListener() { 63 | public void actionPerformed(ActionEvent e) { 64 | onClose(); 65 | } 66 | }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 67 | 68 | fieldsTable.addMouseListener(new MouseAdapter() { 69 | public void mousePressed(MouseEvent me) { 70 | if (me.getClickCount() == 2) { 71 | callbackAdd.run(); 72 | } 73 | } 74 | }); 75 | textSearch.getDocument().addDocumentListener(new DocumentListener() { 76 | public void changedUpdate(DocumentEvent e) { 77 | filterFields(textSearch.getText()); 78 | } 79 | public void removeUpdate(DocumentEvent e) { 80 | filterFields(textSearch.getText()); 81 | } 82 | public void insertUpdate(DocumentEvent e) { 83 | filterFields(textSearch.getText()); 84 | } 85 | }); 86 | } 87 | 88 | private void onClose() { 89 | setVisible(false); 90 | } 91 | 92 | @Override 93 | public void setVisible(boolean value) { 94 | super.setVisible(value); 95 | // Focus search input on showing 96 | textSearch.requestFocus(); 97 | } 98 | 99 | public void setFieldsList(Map fields) { 100 | while (fieldsTableModel.getRowCount() > 0) { 101 | fieldsTableModel.removeRow(0); 102 | } 103 | List fieldsList = new ArrayList(fields.keySet()); 104 | Collections.sort(fieldsList); 105 | for (String field : fieldsList) { 106 | fieldsTableModel.addRow(new Object[]{field, fields.get(field)}); 107 | } 108 | } 109 | 110 | private void filterFields(String str) { 111 | RowFilter rf = RowFilter.regexFilter("(?i)" + Pattern.quote(str), 0); 112 | sorter.setRowFilter(rf); 113 | } 114 | 115 | public List getSelectedFields() { 116 | List selectedFields = new ArrayList(); 117 | for (int i : fieldsTable.getSelectedRows()) { 118 | selectedFields.add((String) fieldsTable.getValueAt(i, 0)); 119 | } 120 | return selectedFields; 121 | } 122 | 123 | private void createUIComponents() { 124 | // Fields table 125 | fieldsTableModel = new DefaultTableModel() { 126 | @Override 127 | public boolean isCellEditable(int row, int col) { 128 | return false; 129 | } 130 | }; 131 | fieldsTableModel.addColumn("Field"); 132 | fieldsTableModel.addColumn("Type"); 133 | fieldsTable = new JTable(fieldsTableModel); 134 | sorter = new TableRowSorter(fieldsTableModel); 135 | fieldsTable.setRowSorter(sorter); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/LogInfo.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 |
73 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/LogInfo.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot; 2 | 3 | import me.drton.jmavlib.log.LogReader; 4 | 5 | import javax.swing.*; 6 | import javax.swing.table.DefaultTableModel; 7 | import java.text.DateFormat; 8 | import java.text.SimpleDateFormat; 9 | import java.util.*; 10 | 11 | /** 12 | * User: ton Date: 27.10.13 Time: 17:45 13 | */ 14 | public class LogInfo { 15 | private JFrame mainFrame; 16 | private JPanel mainPanel; 17 | private JTable infoTable; 18 | private DefaultTableModel infoTableModel; 19 | private JTable parametersTable; 20 | private DefaultTableModel parametersTableModel; 21 | private DateFormat dateFormat; 22 | 23 | public LogInfo() { 24 | mainFrame = new JFrame("Log Info"); 25 | mainFrame.setContentPane(mainPanel); 26 | mainFrame.pack(); 27 | dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); 28 | dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); 29 | } 30 | 31 | public JFrame getFrame() { 32 | return mainFrame; 33 | } 34 | 35 | public void setVisible(boolean visible) { 36 | mainFrame.setVisible(visible); 37 | } 38 | 39 | public void updateInfo(LogReader logReader) { 40 | while (infoTableModel.getRowCount() > 0) { 41 | infoTableModel.removeRow(0); 42 | } 43 | while (parametersTableModel.getRowCount() > 0) { 44 | parametersTableModel.removeRow(0); 45 | } 46 | if (logReader != null) { 47 | infoTableModel.addRow(new Object[]{"Format", logReader.getFormat()}); 48 | infoTableModel.addRow(new Object[]{"System", logReader.getSystemName()}); 49 | infoTableModel.addRow(new Object[]{ 50 | "Length, s", String.format(Locale.ROOT, "%.3f", logReader.getSizeMicroseconds() * 1e-6)}); 51 | String startTimeStr = ""; 52 | if (logReader.getUTCTimeReferenceMicroseconds() > 0) { 53 | startTimeStr = dateFormat.format( 54 | new Date((logReader.getStartMicroseconds() + logReader.getUTCTimeReferenceMicroseconds()) / 1000)) + " UTC"; 55 | } 56 | infoTableModel.addRow(new Object[]{ 57 | "Start Time", startTimeStr}); 58 | infoTableModel.addRow(new Object[]{"Updates count", logReader.getSizeUpdates()}); 59 | infoTableModel.addRow(new Object[]{"Errors", logReader.getErrors().size()}); 60 | Map ver = logReader.getVersion(); 61 | infoTableModel.addRow(new Object[]{"Hardware Version", ver.get("HW")}); 62 | infoTableModel.addRow(new Object[]{"Firmware Version", ver.get("FW")}); 63 | Map parameters = logReader.getParameters(); 64 | List keys = new ArrayList(parameters.keySet()); 65 | Collections.sort(keys); 66 | for (String key : keys) { 67 | parametersTableModel.addRow(new Object[]{key, parameters.get(key).toString()}); 68 | } 69 | } 70 | } 71 | 72 | private void createUIComponents() { 73 | // Info table 74 | infoTableModel = new DefaultTableModel() { 75 | @Override 76 | public boolean isCellEditable(int row, int col) { 77 | return false; 78 | } 79 | }; 80 | infoTableModel.addColumn("Property"); 81 | infoTableModel.addColumn("Value"); 82 | infoTable = new JTable(infoTableModel); 83 | // Parameters table 84 | parametersTableModel = new DefaultTableModel() { 85 | @Override 86 | public boolean isCellEditable(int row, int col) { 87 | return false; 88 | } 89 | }; 90 | parametersTableModel.addColumn("Parameter"); 91 | parametersTableModel.addColumn("Value"); 92 | parametersTable = new JTable(parametersTableModel); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/Marker.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot; 2 | 3 | /** 4 | * Created by ton on 29.09.15. 5 | */ 6 | public class Marker { 7 | public final double x; 8 | public final String label; 9 | 10 | public Marker(double x, String label) { 11 | this.x = x; 12 | this.label = label; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/MarkersList.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot; 2 | 3 | import me.drton.flightplot.Marker; 4 | 5 | import java.util.ArrayList; 6 | 7 | /** 8 | * Created by ton on 29.09.15. 9 | */ 10 | public class MarkersList extends ArrayList implements PlotItem { 11 | private final String title; 12 | 13 | public MarkersList(String title) { 14 | this.title = title; 15 | } 16 | 17 | @Override 18 | public String getTitle() { 19 | return title; 20 | } 21 | 22 | public String getFullTitle(String processorTitle) { 23 | return processorTitle + (title.isEmpty() ? "" : (":" + title)); 24 | } 25 | 26 | public void addMarker(double time, String label) { 27 | add(new Marker(time, label)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/OSValidator.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot; 2 | 3 | /** 4 | * User: ton Date: 22.06.13 Time: 11:40 5 | */ 6 | public class OSValidator { 7 | 8 | private static String OS = System.getProperty("os.name").toLowerCase(); 9 | 10 | public static void main(String[] args) { 11 | System.out.println(OS); 12 | if (isWindows()) { 13 | System.out.println("This is Windows"); 14 | } else if (isMac()) { 15 | System.out.println("This is Mac"); 16 | } else if (isUnix()) { 17 | System.out.println("This is Unix or Linux"); 18 | } else if (isSolaris()) { 19 | System.out.println("This is Solaris"); 20 | } else { 21 | System.out.println("Your OS is not support!!"); 22 | } 23 | } 24 | 25 | public static boolean isWindows() { 26 | return (OS.contains("win")); 27 | } 28 | 29 | public static boolean isMac() { 30 | return (OS.contains("mac")); 31 | } 32 | 33 | public static boolean isUnix() { 34 | return (OS.contains("nix") || OS.contains("nux") || OS.contains("aix")); 35 | } 36 | 37 | public static boolean isSolaris() { 38 | return (OS.contains("sunos")); 39 | } 40 | } -------------------------------------------------------------------------------- /src/me/drton/flightplot/ParamValueTableCellEditor.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot; 2 | 3 | import javax.swing.*; 4 | import javax.swing.table.TableCellEditor; 5 | import java.awt.*; 6 | import java.awt.event.ActionEvent; 7 | import java.awt.event.ActionListener; 8 | import java.awt.event.MouseEvent; 9 | import java.util.EventObject; 10 | 11 | /** 12 | * Created by ton on 13.03.15. 13 | */ 14 | class ParamValueTableCellEditor extends AbstractCellEditor implements TableCellEditor { 15 | private FlightPlot app; 16 | private TableCellEditor editor; 17 | 18 | public ParamValueTableCellEditor(FlightPlot app) { 19 | this.app = app; 20 | } 21 | 22 | public boolean isCellEditable(EventObject anEvent) { 23 | if (anEvent instanceof MouseEvent) { 24 | return ((MouseEvent)anEvent).getClickCount() >= 2; 25 | } 26 | return true; 27 | } 28 | 29 | @Override 30 | public Object getCellEditorValue() { 31 | return editor != null ? editor.getCellEditorValue() : null; 32 | } 33 | 34 | @Override 35 | public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { 36 | app.setEditingProcessor(); 37 | if (value instanceof Color) { 38 | editor = new ColorParamTableCellEditor(app.getColorSupplier()); 39 | ((ColorParamTableCellEditor) editor).getComponent().addActionListener(new ActionDelegate()); 40 | } else if (value instanceof String) { 41 | JTextField textField = new JTextField(); 42 | textField.setFont(table.getFont()); 43 | textField.setBorder(BorderFactory.createLineBorder(Color.BLACK)); 44 | editor = new DefaultCellEditor(textField); 45 | ((JTextField) ((DefaultCellEditor) editor).getComponent()).addActionListener(new ActionDelegate()); 46 | } 47 | 48 | return editor.getTableCellEditorComponent(table, value, isSelected, row, column); 49 | } 50 | 51 | private class ActionDelegate implements ActionListener { 52 | @Override 53 | public void actionPerformed(ActionEvent actionEvent) { 54 | ParamValueTableCellEditor.this.stopCellEditing(); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/ParamValueTableCellRenderer.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot; 2 | 3 | import javax.swing.*; 4 | import javax.swing.table.DefaultTableCellRenderer; 5 | import javax.swing.table.TableCellRenderer; 6 | import java.awt.*; 7 | 8 | /** 9 | * Created by ton on 13.03.15. 10 | */ 11 | class ParamValueTableCellRenderer extends JLabel implements TableCellRenderer { 12 | private DefaultTableCellRenderer defaultTableCellRenderer = new DefaultTableCellRenderer(); 13 | 14 | public ParamValueTableCellRenderer() { 15 | setOpaque(true); 16 | } 17 | 18 | public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected 19 | , boolean hasFocus, int row, int column) { 20 | if (value instanceof Color) { 21 | setBackground((Color) value); 22 | } else { 23 | return defaultTableCellRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); 24 | } 25 | return this; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/PlotExportDialog.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 |
140 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/PlotExportDialog.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot; 2 | 3 | import javax.imageio.IIOImage; 4 | import javax.imageio.ImageIO; 5 | import javax.imageio.ImageWriteParam; 6 | import javax.imageio.ImageWriter; 7 | import javax.imageio.stream.FileImageOutputStream; 8 | import javax.imageio.stream.ImageOutputStream; 9 | import javax.swing.*; 10 | import javax.swing.filechooser.FileNameExtensionFilter; 11 | import java.awt.*; 12 | import java.awt.event.*; 13 | import java.awt.geom.AffineTransform; 14 | import java.awt.geom.Rectangle2D; 15 | import java.awt.image.BufferedImage; 16 | import java.io.File; 17 | import java.util.prefs.Preferences; 18 | 19 | public class PlotExportDialog extends JDialog { 20 | private static final String DIALOG_SETTING = "PlotExportDialog"; 21 | private static final String LAST_EXPORT_DIRECTORY_SETTING = "LastExportDirectory"; 22 | 23 | private JPanel contentPane; 24 | private JButton buttonExport; 25 | private JButton buttonClose; 26 | private JTextField widthField; 27 | private JTextField heightField; 28 | private JComboBox formatComboBox; 29 | private JTextField scaleField; 30 | private FlightPlot app; 31 | private File lastExportDirectory; 32 | 33 | public PlotExportDialog(FlightPlot app) { 34 | this.app = app; 35 | setContentPane(contentPane); 36 | setModal(true); 37 | getRootPane().setDefaultButton(buttonExport); 38 | 39 | buttonExport.addActionListener(new ActionListener() { 40 | public void actionPerformed(ActionEvent e) { 41 | onOK(); 42 | } 43 | }); 44 | 45 | buttonClose.addActionListener(new ActionListener() { 46 | public void actionPerformed(ActionEvent e) { 47 | onClose(); 48 | } 49 | }); 50 | 51 | // call onClose() when cross is clicked 52 | setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); 53 | addWindowListener(new WindowAdapter() { 54 | public void windowClosing(WindowEvent e) { 55 | onClose(); 56 | } 57 | }); 58 | 59 | // call onClose() on ESCAPE 60 | contentPane.registerKeyboardAction(new ActionListener() { 61 | public void actionPerformed(ActionEvent e) { 62 | onClose(); 63 | } 64 | }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 65 | pack(); 66 | } 67 | 68 | private void onOK() { 69 | String format = ((String) formatComboBox.getSelectedItem()).toLowerCase(); 70 | JFileChooser fc = new JFileChooser(); 71 | if (lastExportDirectory != null) { 72 | fc.setCurrentDirectory(lastExportDirectory); 73 | } 74 | FileNameExtensionFilter extensionFilter = new FileNameExtensionFilter(format.toUpperCase() + " Image (*." + format + ")", format); 75 | fc.setFileFilter(extensionFilter); 76 | fc.setDialogTitle("Export Plot"); 77 | int returnVal = fc.showDialog(null, "Export Plot"); 78 | if (returnVal == JFileChooser.APPROVE_OPTION) { 79 | lastExportDirectory = fc.getCurrentDirectory(); 80 | String fileName = fc.getSelectedFile().toString(); 81 | if (extensionFilter == fc.getFileFilter() && !fileName.toLowerCase().endsWith("." + format)) { 82 | fileName += "." + format; 83 | } 84 | try { 85 | int width = Integer.parseInt(widthField.getText()); 86 | int height = Integer.parseInt(heightField.getText()); 87 | 88 | BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); 89 | Graphics2D g2 = img.createGraphics(); 90 | double scale = Double.parseDouble(scaleField.getText()); 91 | AffineTransform st = AffineTransform.getScaleInstance(scale, scale); 92 | g2.transform(st); 93 | app.getChart().draw(g2, new Rectangle2D.Double(0.0D, 0.0D, width / scale, height / scale), null, null); 94 | g2.dispose(); 95 | 96 | ImageWriter imgWriter = ImageIO.getImageWritersByFormatName(format).next(); 97 | ImageWriteParam imgWriteParam = imgWriter.getDefaultWriteParam(); 98 | if ("jpg".equals(format)) { 99 | imgWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); 100 | imgWriteParam.setCompressionQuality(1.0f); 101 | } 102 | ImageOutputStream outputStream = new FileImageOutputStream(new File(fileName)); 103 | imgWriter.setOutput(outputStream); 104 | IIOImage outputImage = new IIOImage(img, null, null); 105 | imgWriter.write(null, outputImage, imgWriteParam); 106 | imgWriter.dispose(); 107 | 108 | app.setStatus(String.format("Exported to \"%s\"", fileName)); 109 | 110 | } catch (Exception e) { 111 | app.setStatus("Error: " + e); 112 | e.printStackTrace(); 113 | } 114 | } 115 | dispose(); 116 | } 117 | 118 | private void onClose() { 119 | dispose(); 120 | } 121 | 122 | public void savePreferences(Preferences preferences) { 123 | PreferencesUtil.saveWindowPreferences(this, preferences.node(DIALOG_SETTING)); 124 | if (lastExportDirectory != null) { 125 | preferences.put(LAST_EXPORT_DIRECTORY_SETTING, lastExportDirectory.getAbsolutePath()); 126 | } 127 | } 128 | 129 | public void loadPreferences(Preferences preferences) { 130 | PreferencesUtil.loadWindowPreferences(this, preferences.node(DIALOG_SETTING), -1, -1); 131 | String lastExportDirectoryPath = preferences.get(LAST_EXPORT_DIRECTORY_SETTING, null); 132 | if (null != lastExportDirectoryPath) { 133 | lastExportDirectory = new File(lastExportDirectoryPath); 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/PlotItem.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot; 2 | 3 | /** 4 | * Created by ton on 29.09.15. 5 | */ 6 | public interface PlotItem { 7 | String getTitle(); 8 | } 9 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/PreferencesUtil.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot; 2 | 3 | import java.awt.*; 4 | import java.util.prefs.Preferences; 5 | 6 | /** 7 | * Created by ada on 25.01.14. 8 | */ 9 | public class PreferencesUtil { 10 | 11 | public static void saveWindowPreferences(Component window, Preferences windowPreferences) { 12 | Dimension size = window.getSize(); 13 | windowPreferences.putInt("Width", size.width); 14 | windowPreferences.putInt("Height", size.height); 15 | Point location = window.getLocation(); 16 | windowPreferences.putInt("X", location.x); 17 | windowPreferences.putInt("Y", location.y); 18 | } 19 | 20 | public static void loadWindowPreferences(Component window, Preferences windowPreferences, int defaultWidth, 21 | int defaultHeight) { 22 | if (defaultWidth > 0) { 23 | window.setSize(windowPreferences.getInt("Width", defaultWidth), 24 | windowPreferences.getInt("Height", defaultHeight)); 25 | } 26 | window.setLocation(windowPreferences.getInt("X", 0), windowPreferences.getInt("Y", 0)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/Preset.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot; 2 | 3 | import org.json.JSONArray; 4 | import org.json.JSONObject; 5 | 6 | import java.io.IOException; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.prefs.BackingStoreException; 10 | import java.util.prefs.Preferences; 11 | 12 | /** 13 | * User: ton Date: 22.06.13 Time: 15:05 14 | */ 15 | public class Preset { 16 | private String title; 17 | private List processorPresets; 18 | 19 | public Preset() { 20 | this.title = ""; 21 | this.processorPresets = new ArrayList(); 22 | } 23 | 24 | public Preset(String title, List processorPresets) { 25 | this.title = title; 26 | this.processorPresets = processorPresets; 27 | } 28 | 29 | public String getTitle() { 30 | return title; 31 | } 32 | 33 | public void setTitle(String title) { 34 | this.title = title; 35 | } 36 | 37 | public List getProcessorPresets() { 38 | return processorPresets; 39 | } 40 | 41 | public JSONObject packJSONObject() throws IOException { 42 | JSONObject json = new JSONObject(); 43 | json.put("Title", title); 44 | JSONArray jsonProcessorPresets = new JSONArray(); 45 | for (ProcessorPreset pp : processorPresets) { 46 | jsonProcessorPresets.put(pp.packJSONObject()); 47 | } 48 | json.put("ProcessorPresets", jsonProcessorPresets); 49 | return json; 50 | } 51 | 52 | public static Preset unpackJSONObject(JSONObject json) throws IOException { 53 | JSONArray jsonProcessorPresets = json.getJSONArray("ProcessorPresets"); 54 | List processorPresets = new ArrayList(); 55 | for (int i = 0; i < jsonProcessorPresets.length(); i++) { 56 | processorPresets.add(ProcessorPreset.unpackJSONObject(jsonProcessorPresets.getJSONObject(i))); 57 | } 58 | return new Preset(json.getString("Title"), processorPresets); 59 | } 60 | 61 | @Override 62 | public String toString() { 63 | return title; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/ProcessorPreset.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot; 2 | 3 | import org.json.JSONObject; 4 | 5 | import java.awt.*; 6 | import java.io.IOException; 7 | import java.util.*; 8 | 9 | /** 10 | * User: ton Date: 22.06.13 Time: 15:08 11 | */ 12 | public class ProcessorPreset { 13 | private String title; 14 | private String processorType; 15 | private Map parameters; 16 | private Map colors; 17 | private boolean visible; 18 | 19 | public ProcessorPreset(String title, String processorType, Map parameters, Map colors, boolean visible) { 20 | this.title = title; 21 | this.processorType = processorType; 22 | this.parameters = parameters; 23 | this.colors = colors; 24 | this.visible = visible; 25 | } 26 | 27 | public static ProcessorPreset unpackJSONObject(JSONObject json) throws IOException { 28 | JSONObject jsonParameters = json.getJSONObject("Parameters"); 29 | Map parametersNew = new HashMap(); 30 | for (Object key : jsonParameters.keySet()) { 31 | String keyStr = (String) key; 32 | parametersNew.put(keyStr, jsonParameters.get(keyStr).toString()); 33 | } 34 | JSONObject jsonColors = json.getJSONObject("Colors"); 35 | Map colorsNew = new HashMap(); 36 | for (Object key : jsonColors.keySet()) { 37 | String keyStr = (String) key; 38 | colorsNew.put(keyStr, new Color(Integer.parseInt(jsonColors.get(keyStr).toString(), 16))); 39 | } 40 | return new ProcessorPreset(json.getString("Title"), json.getString("ProcessorType"), parametersNew, colorsNew, json.optBoolean("visible", true)); 41 | } 42 | 43 | public String getTitle() { 44 | return title; 45 | } 46 | 47 | public void setTitle(String title) { 48 | this.title = title; 49 | } 50 | 51 | public boolean isVisible() { 52 | return visible; 53 | } 54 | 55 | public void setVisible(boolean visible) { 56 | this.visible = visible; 57 | } 58 | 59 | public String getProcessorType() { 60 | return processorType; 61 | } 62 | 63 | public void setProcessorType(String processorType) { 64 | this.processorType = processorType; 65 | } 66 | 67 | public Map getParameters() { 68 | return parameters; 69 | } 70 | 71 | public void setParameters(Map parameters) { 72 | this.parameters = parameters; 73 | } 74 | 75 | public Map getColors() { 76 | return colors; 77 | } 78 | 79 | public void setColors(Map colors) { 80 | this.colors = colors; 81 | } 82 | 83 | public JSONObject packJSONObject() throws IOException { 84 | JSONObject json = new JSONObject(); 85 | json.put("Title", title); 86 | json.put("ProcessorType", processorType); 87 | json.put("Parameters", new JSONObject(parameters)); 88 | Map jsonColors = new HashMap(); 89 | for (Map.Entry entry : colors.entrySet()) { 90 | jsonColors.put(entry.getKey(), Integer.toHexString(entry.getValue().getRGB()).substring(2, 8)); 91 | } 92 | json.put("Colors", new JSONObject(jsonColors)); 93 | json.put("visible", visible); 94 | return json; 95 | } 96 | 97 | public Map.Entry getParameter(int i) { 98 | java.util.List paramEntries = new ArrayList(parameters.entrySet()); 99 | Collections.sort(paramEntries, new Comparator() { 100 | @Override 101 | public int compare(Map.Entry o1, Map.Entry o2) { 102 | return ((String) o1.getKey()).compareTo((String) o2.getKey()); 103 | } 104 | }); 105 | java.util.List colorsEntries = new ArrayList(colors.entrySet()); 106 | Collections.sort(paramEntries, new Comparator() { 107 | @Override 108 | public int compare(Map.Entry o1, Map.Entry o2) { 109 | return ((String) o1.getKey()).compareTo((String) o2.getKey()); 110 | } 111 | }); 112 | paramEntries.addAll(colorsEntries); 113 | return paramEntries.get(i); 114 | } 115 | 116 | public ProcessorPreset clone() { 117 | return new ProcessorPreset(title, processorType, new HashMap(parameters), new HashMap(colors), visible); 118 | } 119 | 120 | @Override 121 | public String toString() { 122 | return title + " [" + processorType + "]"; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/Series.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot; 2 | 3 | import java.util.ArrayList; 4 | 5 | /** 6 | * Created by ton on 09.03.15. 7 | */ 8 | public class Series extends ArrayList implements PlotItem { 9 | private final String title; 10 | private final double skipOut; 11 | private Double lastTime = null; 12 | private Double lastValue = null; 13 | 14 | public Series(String title, double skipOut) { 15 | this.title = title; 16 | this.skipOut = skipOut; 17 | } 18 | 19 | @Override 20 | public String getTitle() { 21 | return title; 22 | } 23 | 24 | public String getFullTitle(String processorTitle) { 25 | return processorTitle + (title.isEmpty() ? "" : (":" + title)); 26 | } 27 | 28 | public void addPoint(double time, double value) { 29 | if (lastTime != null && time - lastTime < skipOut) { 30 | lastValue = value; 31 | return; 32 | } 33 | if (lastValue != null && lastTime != null && time - lastTime > skipOut * 2) { 34 | add(new XYPoint(lastTime, lastValue)); 35 | } 36 | lastTime = time; 37 | lastValue = null; 38 | add(new XYPoint(time, value)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/TaggedValueMarker.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot; 2 | 3 | import org.jfree.chart.plot.ValueMarker; 4 | 5 | /** 6 | * Created by ton on 29.09.15. 7 | */ 8 | public class TaggedValueMarker extends ValueMarker { 9 | public final int tag; 10 | 11 | public TaggedValueMarker(int tag, double value) { 12 | super(value); 13 | this.tag = tag; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/XYPoint.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot; 2 | 3 | /** 4 | * Created by ton on 09.03.15. 5 | */ 6 | public class XYPoint { 7 | public final double x; 8 | public final double y; 9 | 10 | public XYPoint(double x, double y) { 11 | this.x = x; 12 | this.y = y; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/export/AbstractTrackExporter.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot.export; 2 | 3 | import java.io.*; 4 | import java.lang.reflect.Method; 5 | 6 | /** 7 | * Created by ada on 14.01.14. 8 | */ 9 | public abstract class AbstractTrackExporter implements TrackExporter { 10 | protected TrackReader trackReader; 11 | protected TrackExporterConfiguration config; 12 | protected String title; 13 | protected Writer writer; 14 | protected int trackPart = 0; 15 | protected String flightMode = null; 16 | 17 | @Override 18 | public void export(TrackReader trackReader, TrackExporterConfiguration config, File file, String title) throws IOException { 19 | this.trackReader = trackReader; 20 | this.config = config; 21 | this.writer = new BufferedWriter(new FileWriter(file)); 22 | this.title = title; 23 | boolean trackStarted = false; 24 | try { 25 | writeStart(); 26 | while (true) { 27 | TrackPoint point = trackReader.readNextPoint(); 28 | if (point == null) { 29 | break; 30 | } 31 | if (!trackStarted || (point.flightMode != null && !point.flightMode.equals(flightMode))) { 32 | if (trackStarted) { 33 | writePoint(point); // Write this point at the end of previous track to avoid interruption of track 34 | writeTrackPartEnd(); 35 | } 36 | flightMode = point.flightMode; 37 | String trackPartName; 38 | if (point.flightMode != null) { 39 | trackPartName = String.format("%s: %s", trackPart, point.flightMode); 40 | trackPart++; 41 | } else { 42 | trackPartName = "Track"; 43 | } 44 | writeTrackPartStart(trackPartName); 45 | trackStarted = true; 46 | } 47 | writePoint(point); 48 | } 49 | if (trackStarted) { 50 | writeTrackPartEnd(); 51 | } 52 | writeEnd(); 53 | } catch (Exception e) { 54 | e.printStackTrace(); 55 | } finally { 56 | this.writer.close(); 57 | } 58 | } 59 | 60 | protected abstract void writeStart() throws IOException; 61 | 62 | protected abstract void writeTrackPartStart(String trackPartName) throws IOException; 63 | 64 | protected abstract void writePoint(TrackPoint point) throws IOException; 65 | 66 | protected abstract void writeTrackPartEnd() throws IOException; 67 | 68 | protected abstract void writeEnd() throws IOException; 69 | } 70 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/export/AbstractTrackReader.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot.export; 2 | 3 | import me.drton.jmavlib.log.FormatErrorException; 4 | import me.drton.jmavlib.log.LogReader; 5 | 6 | import java.io.EOFException; 7 | import java.io.IOException; 8 | import java.util.Map; 9 | 10 | /** 11 | * Created by ada on 23.12.13. 12 | */ 13 | public abstract class AbstractTrackReader implements TrackReader { 14 | protected final LogReader reader; 15 | private long timeNext = 0; 16 | protected final TrackReaderConfiguration config; 17 | 18 | public AbstractTrackReader(LogReader reader, TrackReaderConfiguration config) throws IOException, FormatErrorException { 19 | this.reader = reader; 20 | this.config = config; 21 | this.reader.seek(this.config.getTimeStart()); 22 | } 23 | 24 | protected long readUpdate(Map data) throws IOException, FormatErrorException { 25 | long t; 26 | while (true) { 27 | t = reader.readUpdate(data); 28 | if (t > config.getTimeEnd()) { 29 | throw new EOFException("Reached configured export limit."); 30 | } 31 | if (t >= timeNext) { 32 | if (timeNext == 0) { 33 | timeNext = t; 34 | } 35 | timeNext += config.getTimeInterval(); 36 | return t; 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/export/GPXTrackExporter.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot.export; 2 | 3 | import java.io.IOException; 4 | import java.text.SimpleDateFormat; 5 | import java.util.Locale; 6 | 7 | /** 8 | * Created by ada on 16.02.14. 9 | */ 10 | public class GPXTrackExporter extends AbstractTrackExporter { 11 | 12 | private final SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); 13 | 14 | protected void writeStart() throws IOException { 15 | writer.write("\n"); 16 | writer.write("\n"); 19 | writer.write("\n"); 20 | writer.write(String.format("%s\n", this.title)); 21 | writer.write("\n"); 22 | writer.write("\n"); 23 | } 24 | 25 | @Override 26 | protected void writeTrackPartStart(String trackPartName) throws IOException { 27 | writer.write("\n"); 28 | } 29 | 30 | @Override 31 | protected void writePoint(TrackPoint point) throws IOException { 32 | writer.write(String.format(Locale.ROOT, "\n", point.lat, point.lon)); 33 | writer.write(String.format(Locale.ROOT, "%.2f\n", point.alt)); 34 | writer.write(String.format("\n", dateFormatter.format(point.time / 1000))); 35 | writer.write("\n"); 36 | } 37 | 38 | protected void writeTrackPartEnd() throws IOException { 39 | writer.write("\n"); 40 | } 41 | 42 | protected void writeEnd() throws IOException { 43 | writer.write("\n"); 44 | writer.write("\n"); 45 | } 46 | 47 | @Override 48 | public String getName() { 49 | return "GPX"; 50 | } 51 | 52 | @Override 53 | public String getDescription() { 54 | return "GPS Exchange Format (GPX)"; 55 | } 56 | 57 | @Override 58 | public String getFileExtension() { 59 | return "gpx"; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/export/KMLTrackExporter.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot.export; 2 | 3 | import java.io.IOException; 4 | import java.text.SimpleDateFormat; 5 | import java.util.Locale; 6 | 7 | /** 8 | * Created by ada on 23.12.13. 9 | */ 10 | public class KMLTrackExporter extends AbstractTrackExporter { 11 | private static final String LINE_STYLE_RED = "red"; 12 | private static final String LINE_STYLE_GREEN = "green"; 13 | private static final String LINE_STYLE_BLUE = "blue"; 14 | private static final String LINE_STYLE_CYAN = "cyan"; 15 | private static final String LINE_STYLE_MAGENTA = "magenta"; 16 | private static final String LINE_STYLE_YELLOW = "yellow"; 17 | private final SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); 18 | 19 | protected String getStyleForFlightMode(String flightMode) { 20 | if (flightMode == null) { 21 | return LINE_STYLE_YELLOW; 22 | } 23 | if ("MANUAL".equals(flightMode)) { 24 | return LINE_STYLE_RED; 25 | } else if ("ALTCTL".equals(flightMode)) { 26 | return LINE_STYLE_YELLOW; 27 | } else if ("POSCTL".equals(flightMode)) { 28 | return LINE_STYLE_GREEN; 29 | } else if ("AUTO_MISSION".equals(flightMode)) { 30 | return LINE_STYLE_BLUE; 31 | } else if ("AUTO_LOITER".equals(flightMode)) { 32 | return LINE_STYLE_CYAN; 33 | } else if ("AUTO_RTL".equals(flightMode)) { 34 | return LINE_STYLE_MAGENTA; 35 | } else if ("AUTO_ACRO".equals(flightMode)) { 36 | return LINE_STYLE_RED; 37 | } else if ("AUTO_OFFBOARD".equals(flightMode)) { 38 | return LINE_STYLE_BLUE; 39 | } else { 40 | return LINE_STYLE_YELLOW; 41 | } 42 | } 43 | 44 | protected void writeStart() throws IOException { 45 | // TODO: maybe make some settings configurable 46 | writer.write("\n"); 47 | writer.write("\n"); 48 | writer.write("\n"); 49 | writer.write("" + this.title + "\n"); 50 | writer.write("\n"); 51 | writer.write("\n"); 57 | writer.write("\n"); 63 | writer.write("\n"); 69 | } 70 | 71 | @Override 72 | protected void writeTrackPartStart(String trackPartName) throws IOException { 73 | String styleId = getStyleForFlightMode(flightMode); 74 | writer.write("\n"); 75 | writer.write("" + trackPartName + "\n"); 76 | writer.write("\n"); 77 | writer.write("#" + styleId + "\n"); 78 | writer.write("\n"); 79 | writer.write("absolute\n"); 80 | writer.write("0\n"); 81 | } 82 | 83 | protected void writePoint(TrackPoint point) throws IOException { 84 | writer.write(String.format("%s\n", dateFormatter.format(point.time / 1000))); 85 | writer.write(String.format(Locale.ROOT, "%.10f %.10f %.2f\n", point.lon, point.lat, point.alt)); 86 | } 87 | 88 | protected void writeTrackPartEnd() throws IOException { 89 | writer.write("\n"); 90 | writer.write("\n"); 91 | } 92 | 93 | protected void writeEnd() throws IOException { 94 | writer.write("\n"); 95 | writer.write(""); 96 | } 97 | 98 | @Override 99 | public String getName() { 100 | return "KML"; 101 | } 102 | 103 | @Override 104 | public String getDescription() { 105 | return "Google Earth Track (KML)"; 106 | } 107 | 108 | @Override 109 | public String getFileExtension() { 110 | return "kml"; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/export/PX4TrackReader.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot.export; 2 | 3 | import me.drton.jmavlib.log.FormatErrorException; 4 | import me.drton.jmavlib.log.px4.PX4LogReader; 5 | 6 | import java.io.EOFException; 7 | import java.io.IOException; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | /** 12 | * Created by ada on 23.12.13. 13 | */ 14 | public class PX4TrackReader extends AbstractTrackReader { 15 | private static final String GPOS_LAT = "GPOS.Lat"; 16 | private static final String GPOS_LON = "GPOS.Lon"; 17 | private static final String GPOS_ALT = "GPOS.Alt"; 18 | private static final String STAT_MAINSTATE = "STAT.MainState"; 19 | 20 | private String flightMode = null; 21 | 22 | public PX4TrackReader(PX4LogReader reader, TrackReaderConfiguration config) throws IOException, FormatErrorException { 23 | super(reader, config); 24 | } 25 | 26 | @Override 27 | public TrackPoint readNextPoint() throws IOException, FormatErrorException { 28 | Map data = new HashMap(); 29 | while (true) { 30 | data.clear(); 31 | long t; 32 | try { 33 | t = readUpdate(data); 34 | } catch (EOFException e) { 35 | break; // End of file 36 | } 37 | String currentFlightMode = getFlightMode(data); 38 | if (currentFlightMode != null) { 39 | flightMode = currentFlightMode; 40 | } 41 | Number lat = (Number) data.get(GPOS_LAT); 42 | Number lon = (Number) data.get(GPOS_LON); 43 | Number alt = (Number) data.get(GPOS_ALT); 44 | if (lat != null && lon != null && alt != null) { 45 | return new TrackPoint(lat.doubleValue(), lon.doubleValue(), alt.doubleValue() + config.getAltitudeOffset(), 46 | t + reader.getUTCTimeReferenceMicroseconds(), flightMode); 47 | } 48 | } 49 | return null; 50 | } 51 | 52 | private String getFlightMode(Map data) { 53 | Number flightMode = (Number) data.get(STAT_MAINSTATE); 54 | if (flightMode != null) { 55 | switch (flightMode.intValue()) { 56 | case 0: 57 | return "MANUAL"; 58 | case 1: 59 | return "ALTCTL"; 60 | case 2: 61 | return "POSCTL"; 62 | case 3: 63 | return "AUTO_MISSION"; 64 | case 4: 65 | return "AUTO_LOITER"; 66 | case 5: 67 | return "AUTO_RTL"; 68 | case 6: 69 | return "AUTO_ACRO"; 70 | case 7: 71 | return "AUTO_OFFBOARD"; 72 | default: 73 | return String.format("UNKNOWN(%s)", flightMode.intValue()); 74 | } 75 | } 76 | return null; // Not supported 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/export/TrackExporter.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot.export; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | 6 | /** 7 | * Created by ada on 19.01.14. 8 | */ 9 | public interface TrackExporter { 10 | String getName(); 11 | 12 | String getDescription(); 13 | 14 | String getFileExtension(); 15 | 16 | /** 17 | * Exports track to specified file, uses title if possible for current export format. 18 | * 19 | * @param file output file 20 | * @param title track title 21 | * @throws IOException 22 | */ 23 | void export(TrackReader trackReader, TrackExporterConfiguration config, File file, String title) throws IOException; 24 | } 25 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/export/TrackExporterConfiguration.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot.export; 2 | 3 | import java.util.prefs.Preferences; 4 | 5 | /** 6 | * Created by ada on 19.01.14. 7 | */ 8 | public class TrackExporterConfiguration { 9 | private boolean splitTracksByFlightMode; 10 | private final static String SPLIT_TRACK_BY_FLIGHT_MODE_SETTING = "splitTracksByFlightMode"; 11 | private String exportFormat; 12 | private final static String EXPORT_FORMAT_TYPE_SETTING = "exportFormat"; 13 | 14 | public void saveConfiguration(Preferences preferences) { 15 | preferences.putBoolean(SPLIT_TRACK_BY_FLIGHT_MODE_SETTING, this.splitTracksByFlightMode); 16 | if (exportFormat != null) { 17 | preferences.put(EXPORT_FORMAT_TYPE_SETTING, exportFormat); 18 | } 19 | } 20 | 21 | public void loadConfiguration(Preferences preferences) { 22 | splitTracksByFlightMode = preferences.getBoolean(SPLIT_TRACK_BY_FLIGHT_MODE_SETTING, false); 23 | exportFormat = preferences.get(EXPORT_FORMAT_TYPE_SETTING, null); 24 | } 25 | 26 | public boolean isSplitTracksByFlightMode() { 27 | return splitTracksByFlightMode; 28 | } 29 | 30 | public void setSplitTracksByFlightMode(boolean splitTracksByFlightMode) { 31 | splitTracksByFlightMode = splitTracksByFlightMode; 32 | } 33 | 34 | public String getExportFormat() { 35 | return exportFormat; 36 | } 37 | 38 | public void setExportFormat(String exportFormat) { 39 | this.exportFormat = exportFormat; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/export/TrackPoint.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot.export; 2 | 3 | /** 4 | * Created by ada on 23.12.13. 5 | */ 6 | public class TrackPoint { 7 | public final double lat; /// latitude 8 | public final double lon; /// longitude 9 | public final double alt; /// altitude AMLS 10 | public final long time; /// unix time in milliseconds 11 | public final String flightMode; /// flight mode 12 | 13 | public TrackPoint(double lat, double lon, double alt, long time, String flightMode) { 14 | this.lat = lat; 15 | this.lon = lon; 16 | this.alt = alt; 17 | this.time = time; 18 | this.flightMode = flightMode; 19 | } 20 | 21 | public TrackPoint(double lat, double lon, double alt, long time) { 22 | this.lat = lat; 23 | this.lon = lon; 24 | this.alt = alt; 25 | this.time = time; 26 | this.flightMode = null; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/export/TrackReader.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot.export; 2 | 3 | import me.drton.jmavlib.log.FormatErrorException; 4 | 5 | import java.io.IOException; 6 | 7 | /** 8 | * Created by ada on 24.12.13. 9 | */ 10 | public interface TrackReader { 11 | /** 12 | * Reads next track point from LogReader. 13 | * 14 | * @return returns TrackPoint or null if no more points can be read. 15 | * @throws IOException 16 | * @throws FormatErrorException 17 | */ 18 | TrackPoint readNextPoint() throws IOException, FormatErrorException; 19 | } 20 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/export/TrackReaderConfiguration.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot.export; 2 | 3 | import java.util.prefs.Preferences; 4 | 5 | /** 6 | * Created by ada on 25.01.14. 7 | */ 8 | public class TrackReaderConfiguration { 9 | private long timeInterval; /// Min time interval between generated points, [us] 10 | private final static String TIME_INTERVAL_SETTING = "timeInterval"; 11 | private long timeStart; /// Start time, [us] 12 | private long timeEnd; /// End time, [us] 13 | private double altitudeOffset; /// Altitude offset, [m] 14 | private final static String ALTITUDE_OFFSET_SETTING = "altitudeOffset"; 15 | 16 | public void saveConfiguration(Preferences preferences) { 17 | preferences.putLong(TIME_INTERVAL_SETTING, timeInterval); 18 | preferences.putDouble(ALTITUDE_OFFSET_SETTING, altitudeOffset); 19 | } 20 | 21 | public void loadConfiguration(Preferences preferences) { 22 | timeInterval = preferences.getLong(TIME_INTERVAL_SETTING, 0); 23 | altitudeOffset = preferences.getDouble(ALTITUDE_OFFSET_SETTING, 0.0); 24 | } 25 | 26 | public long getTimeInterval() { 27 | return timeInterval; 28 | } 29 | 30 | public void setTimeInterval(long timeInterval) { 31 | this.timeInterval = timeInterval; 32 | } 33 | 34 | public long getTimeStart() { 35 | return timeStart; 36 | } 37 | 38 | public void setTimeStart(long timeStart) { 39 | this.timeStart = timeStart; 40 | } 41 | 42 | public long getTimeEnd() { 43 | return timeEnd; 44 | } 45 | 46 | public void setTimeEnd(long timeEnd) { 47 | this.timeEnd = timeEnd; 48 | } 49 | 50 | public double getAltitudeOffset() { 51 | return altitudeOffset; 52 | } 53 | 54 | public void setAltitudeOffset(double altitudeOffset) { 55 | this.altitudeOffset = altitudeOffset; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/export/TrackReaderFactory.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot.export; 2 | 3 | import me.drton.jmavlib.log.FormatErrorException; 4 | import me.drton.jmavlib.log.LogReader; 5 | import me.drton.jmavlib.log.px4.PX4LogReader; 6 | import me.drton.jmavlib.log.ulog.ULogReader; 7 | 8 | import java.io.IOException; 9 | 10 | /** 11 | * Created by ada on 24.12.13. 12 | */ 13 | public class TrackReaderFactory { 14 | public static TrackReader getTrackReader(LogReader reader, TrackReaderConfiguration config) throws IOException, FormatErrorException { 15 | if (reader instanceof PX4LogReader) { 16 | return new PX4TrackReader((PX4LogReader) reader, config); 17 | } else if (reader instanceof ULogReader) { 18 | return new ULogTrackReader((ULogReader) reader, config); 19 | } else { 20 | throw new UnsupportedOperationException( 21 | String.format("No track reader for \"%s\" format available.", reader.getFormat())); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/export/ULogTrackReader.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot.export; 2 | 3 | import me.drton.jmavlib.log.FormatErrorException; 4 | import me.drton.jmavlib.log.ulog.ULogReader; 5 | 6 | import java.io.EOFException; 7 | import java.io.IOException; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | /** 12 | * Created by ada on 23.12.13. 13 | */ 14 | public class ULogTrackReader extends AbstractTrackReader { 15 | private static final String POS_VALID = "ATTITUDE_POSITION.valid_pos"; 16 | private static final String POS_LAT = "ATTITUDE_POSITION.lat"; 17 | private static final String POS_LON = "ATTITUDE_POSITION.lon"; 18 | private static final String POS_ALT = "ATTITUDE_POSITION.alt_msl"; 19 | private static final String MODE = "SYSTEM_STATUS.mode"; 20 | 21 | private String flightMode = null; 22 | 23 | public ULogTrackReader(ULogReader reader, TrackReaderConfiguration config) throws IOException, FormatErrorException { 24 | super(reader, config); 25 | } 26 | 27 | @Override 28 | public TrackPoint readNextPoint() throws IOException, FormatErrorException { 29 | Map data = new HashMap(); 30 | while (true) { 31 | data.clear(); 32 | long t; 33 | try { 34 | t = readUpdate(data); 35 | } catch (EOFException e) { 36 | break; // End of file 37 | } 38 | String currentFlightMode = getFlightMode(data); 39 | if (currentFlightMode != null) { 40 | flightMode = currentFlightMode; 41 | } 42 | Number valid = (Number) data.get(POS_VALID); 43 | Number lat = (Number) data.get(POS_LAT); 44 | Number lon = (Number) data.get(POS_LON); 45 | Number alt = (Number) data.get(POS_ALT); 46 | if (valid != null && lat != null && lon != null && alt != null && valid.intValue() != 0) { 47 | return new TrackPoint(lat.doubleValue(), lon.doubleValue(), alt.doubleValue() + config.getAltitudeOffset(), 48 | t + reader.getUTCTimeReferenceMicroseconds(), flightMode); 49 | } 50 | } 51 | return null; 52 | } 53 | 54 | private String getFlightMode(Map data) { 55 | Number flightMode = (Number) data.get(MODE); 56 | if (flightMode != null) { 57 | switch (flightMode.intValue()) { 58 | case 1: 59 | return "MANUAL"; 60 | case 2: 61 | return "ALTCTL"; 62 | case 3: 63 | return "POSCTL"; 64 | case 4: 65 | return "RTH"; 66 | default: 67 | return String.format("UNKNOWN(%s)", flightMode.intValue()); 68 | } 69 | } 70 | return null; // Not supported 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/processors/ATan2.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot.processors; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * User: ton Date: 16.06.13 Time: 19:55 8 | */ 9 | public class ATan2 extends PlotProcessor { 10 | protected String param_Field_X; 11 | protected String param_Field_Y; 12 | protected double param_Angle_Offset; 13 | 14 | @Override 15 | public Map getDefaultParameters() { 16 | Map params = new HashMap(); 17 | params.put("Field_X", "LPOS.VX"); 18 | params.put("Field_Y", "LPOS.VY"); 19 | params.put("Angle Offset", 0.0); 20 | return params; 21 | } 22 | 23 | @Override 24 | public void init() { 25 | param_Field_X = (String) parameters.get("Field_X"); 26 | param_Field_Y = (String) parameters.get("Field_Y"); 27 | param_Angle_Offset = (Double) parameters.get("Angle Offset"); 28 | addSeries(); 29 | } 30 | 31 | @Override 32 | public void process(double time, Map update) { 33 | Object x = update.get(param_Field_X); 34 | Object y = update.get(param_Field_Y); 35 | if (x != null && y != null && x instanceof Number && y instanceof Number) { 36 | double a = Math.atan2(((Number) y).doubleValue(), ((Number) x).doubleValue()); 37 | a += param_Angle_Offset + Math.PI; 38 | int a_2pi = (int) Math.round(a / 2.0 / Math.PI - 0.5); 39 | a -= (a_2pi * 2.0 + 1.0) * Math.PI; 40 | addPoint(0, time, a); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/processors/Abs.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot.processors; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * User: ton Date: 16.06.13 Time: 12:59 8 | */ 9 | public class Abs extends PlotProcessor { 10 | protected String[] param_Fields; 11 | protected double param_Scale; 12 | 13 | @Override 14 | public Map getDefaultParameters() { 15 | Map params = new HashMap(); 16 | params.put("Fields", "LPOS.VX LPOS.VY"); 17 | params.put("Scale", 1.0); 18 | return params; 19 | } 20 | 21 | @Override 22 | public void init() { 23 | param_Fields = ((String) parameters.get("Fields")).split(WHITESPACE_RE); 24 | param_Scale = (Double) parameters.get("Scale"); 25 | addSeries(); 26 | } 27 | 28 | @Override 29 | public void process(double time, Map update) { 30 | double s = 0.0; 31 | for (String field : param_Fields) { 32 | Object v = update.get(field); 33 | if (v != null && v instanceof Number) { 34 | double d = ((Number) v).doubleValue(); 35 | s += d * d; 36 | } else { 37 | return; 38 | } 39 | } 40 | addPoint(0, time, Math.sqrt(s) * param_Scale); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/processors/Battery.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot.processors; 2 | 3 | import me.drton.flightplot.processors.tools.LowPassFilter; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | /** 9 | * User: ton Date: 10.11.13 Time: 16:50 10 | */ 11 | public class Battery extends PlotProcessor { 12 | private String param_Field_Voltage; 13 | private String param_Field_Current; 14 | private String param_Field_Discharged; 15 | private double param_Capacity; 16 | private double param_Resistance; 17 | private double param_N_Cells; 18 | private double param_V_Empty; 19 | private double param_V_Full; 20 | private boolean showV; 21 | private boolean showRemainingV; 22 | private boolean showRemainingC; 23 | private LowPassFilter lpf; 24 | 25 | @Override 26 | public Map getDefaultParameters() { 27 | Map params = new HashMap(); 28 | params.put("Field Voltage", "BATT.V"); 29 | params.put("Field Current", "BATT.C"); 30 | params.put("Field Discharged", "BATT.Discharged"); 31 | params.put("Capacity", 2200.0); 32 | params.put("Resistance", 0.03); 33 | params.put("N Cells", 3); 34 | params.put("V Empty", 3.7); 35 | params.put("V Full", 4.0); 36 | params.put("LPF", 1.0); 37 | params.put("Show", "VC"); 38 | return params; 39 | } 40 | 41 | @Override 42 | public void init() { 43 | param_Field_Voltage = (String) parameters.get("Field Voltage"); 44 | param_Field_Current = (String) parameters.get("Field Current"); 45 | param_Field_Discharged = (String) parameters.get("Field Discharged"); 46 | param_Capacity = (Double) parameters.get("Capacity"); 47 | param_Resistance = (Double) parameters.get("Resistance"); 48 | param_N_Cells = (Integer) parameters.get("N Cells"); 49 | param_V_Empty = (Double) parameters.get("V Empty"); 50 | param_V_Full = (Double) parameters.get("V Full"); 51 | lpf = new LowPassFilter(); 52 | lpf.setF((Double) parameters.get("LPF")); 53 | String show = ((String) parameters.get("Show")).toUpperCase(); 54 | showRemainingV = show.contains("V"); 55 | showRemainingC = show.contains("C"); 56 | addSeries("RemainingV"); 57 | addSeries("RemainingC"); 58 | } 59 | 60 | @Override 61 | public void process(double time, Map update) { 62 | Number voltageNum = (Number) update.get(param_Field_Voltage); 63 | Number currentNum = (Number) update.get(param_Field_Current); 64 | Number dischargedNum = (Number) update.get(param_Field_Discharged); 65 | if (voltageNum != null) { 66 | double v = voltageNum.doubleValue(); 67 | double vFiltered = v; 68 | if (currentNum != null) { 69 | double current = currentNum.doubleValue(); 70 | if (current > 0.0) { 71 | // current < 0 means not available 72 | vFiltered += current * param_Resistance; 73 | } 74 | } 75 | vFiltered = lpf.getOutput(time, vFiltered); 76 | double remainingV = Math.min(1.0, 77 | Math.max(0.0, (vFiltered / param_N_Cells - param_V_Empty) / (param_V_Full - param_V_Empty))); 78 | if (showRemainingV) 79 | addPoint(0, time, remainingV * 100.0); 80 | if (dischargedNum != null) { 81 | double discharged = dischargedNum.doubleValue(); 82 | if (discharged > 0.0) { 83 | double remainingC = Math.min(1.0, Math.max(0.0, 1.0 - discharged / param_Capacity)); 84 | if (showRemainingC) 85 | addPoint(1, time, remainingC * 100.0); 86 | } 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/processors/Derivative.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot.processors; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * User: ton Date: 24.06.13 Time: 22:46 7 | */ 8 | public class Derivative extends Simple { 9 | private double[] valuesPrev; 10 | private double[] timesPrev; 11 | 12 | @Override 13 | public Map getDefaultParameters() { 14 | Map params = super.getDefaultParameters(); 15 | params.put("Fields", "LPOS.VX LPOS.VY"); 16 | return params; 17 | } 18 | 19 | @Override 20 | public void init() { 21 | super.init(); 22 | valuesPrev = new double[param_Fields.length]; 23 | timesPrev = new double[param_Fields.length]; 24 | for (int i = 0; i < param_Fields.length; i++) { 25 | valuesPrev[i] = Double.NaN; 26 | timesPrev[i] = Double.NaN; 27 | } 28 | } 29 | 30 | @Override 31 | protected double postProcessValue(int idx, double time, double in) { 32 | double out = Double.NaN; 33 | if (!Double.isNaN(timesPrev[idx])) { 34 | double dt = time - timesPrev[idx]; 35 | if (dt > 1.0e-5) { 36 | out = (in - valuesPrev[idx]) / dt; 37 | } 38 | } 39 | valuesPrev[idx] = in; 40 | timesPrev[idx] = time; 41 | return out; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/processors/EulerFromQuaternion.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot.processors; 2 | 3 | import me.drton.jmavlib.conversion.RotationConversion; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | /** 9 | * Created by ton on 05.01.15. 10 | */ 11 | public class EulerFromQuaternion extends PlotProcessor { 12 | private String[] param_Fields; 13 | private double param_Scale; 14 | private boolean[] show; 15 | private double[] q; 16 | 17 | @Override 18 | public Map getDefaultParameters() { 19 | Map params = new HashMap(); 20 | params.put("Fields", "vehicle_attitude_0.q[0] vehicle_attitude_0.q[1] vehicle_attitude_0.q[2] vehicle_attitude_0.q[3]"); 21 | params.put("Show", "RPY"); 22 | params.put("Scale", 1.0); 23 | return params; 24 | } 25 | 26 | @Override 27 | public void init() { 28 | q = new double[4]; 29 | param_Fields = ((String) parameters.get("Fields")).split(WHITESPACE_RE); 30 | param_Scale = (Double) parameters.get("Scale"); 31 | String showStr = ((String) parameters.get("Show")).toUpperCase(); 32 | show = new boolean[]{false, false, false}; 33 | String[] axes = new String[]{"Roll", "Pitch", "Yaw"}; 34 | for (int axis = 0; axis < 3; axis++) { 35 | String axisName = axes[axis]; 36 | show[axis] = showStr.contains(axisName.substring(0, 1)); 37 | if (show[axis]) { 38 | addSeries(axisName); 39 | } 40 | } 41 | } 42 | 43 | @Override 44 | public void process(double time, Map update) { 45 | if (param_Fields.length < 4) { 46 | return; 47 | } 48 | for (int i = 0; i < 4; i++) { 49 | Number v = (Number) update.get(param_Fields[i]); 50 | if (v == null) { 51 | return; 52 | } 53 | q[i] = v.doubleValue(); 54 | } 55 | double[] euler = RotationConversion.eulerAnglesByQuaternion(q); 56 | int plot_idx = 0; 57 | for (int axis = 0; axis < 3; axis++) { 58 | if (show[axis]) { 59 | addPoint(plot_idx++, time, euler[axis] * param_Scale); 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/processors/Expression.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot.processors; 2 | 3 | import me.drton.flightplot.processors.tools.LowPassFilter; 4 | import net.objecthunter.exp4j.ExpressionBuilder; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * Created by markw on 1/22/15. 11 | */ 12 | public class Expression extends PlotProcessor { 13 | protected final Map data = new HashMap(); 14 | protected LowPassFilter lowPassFilter; 15 | private net.objecthunter.exp4j.Expression expr; 16 | 17 | @Override 18 | public Map getDefaultParameters() { 19 | Map params = new HashMap(); 20 | params.put("Expression", "BATT.C * BATT.V"); 21 | params.put("LPF", 0.0); 22 | return params; 23 | } 24 | 25 | @Override 26 | public void init() { 27 | String exprStr = (String) parameters.get("Expression"); 28 | expr = null; 29 | ExpressionBuilder expBuilder = new ExpressionBuilder(exprStr); 30 | if (fieldsList != null) { 31 | expBuilder.variables(fieldsList.keySet()); 32 | try { 33 | expr = expBuilder.build(); 34 | } catch (Exception e) { 35 | e.printStackTrace(); 36 | } 37 | } 38 | lowPassFilter = new LowPassFilter(); 39 | lowPassFilter.setF((Double) parameters.get("LPF")); 40 | addSeries(); 41 | } 42 | 43 | @Override 44 | public void process(double time, Map update) { 45 | if (expr == null) { 46 | return; 47 | } 48 | for (Map.Entry entry : update.entrySet()) { 49 | Object val = entry.getValue(); 50 | if (val != null && val instanceof Number) { 51 | expr.setVariable(entry.getKey(), ((Number) val).doubleValue()); 52 | } 53 | } 54 | double res; 55 | try { 56 | res = expr.evaluate(); 57 | } catch (Exception e) { 58 | return; 59 | } 60 | addPoint(0, time, lowPassFilter.getOutput(time, res)); 61 | 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/processors/GlobalPositionProjection.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot.processors; 2 | 3 | import me.drton.jmavlib.geo.GlobalPositionProjector; 4 | import me.drton.jmavlib.geo.LatLonAlt; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * User: ton Date: 11.07.13 Time: 22:14 11 | */ 12 | public class GlobalPositionProjection extends PlotProcessor { 13 | private GlobalPositionProjector positionProjector = new GlobalPositionProjector(); 14 | private String[] param_Fields; 15 | 16 | @Override 17 | public Map getDefaultParameters() { 18 | Map params = new HashMap(); 19 | params.put("Fields", "GPS.Lat GPS.Lon"); 20 | params.put("Ref", ""); 21 | return params; 22 | } 23 | 24 | @Override 25 | public void init() { 26 | positionProjector.reset(); 27 | param_Fields = ((String) parameters.get("Fields")).split(WHITESPACE_RE); 28 | String[] ref = ((String) parameters.get("Ref")).split(WHITESPACE_RE); 29 | if (ref.length >= 2) { 30 | positionProjector.init(new LatLonAlt(Double.parseDouble(ref[0]), Double.parseDouble(ref[1]), 0.0)); 31 | } 32 | addSeries("X"); 33 | addSeries("Y"); 34 | } 35 | 36 | @Override 37 | public void process(double time, Map update) { 38 | // GPS 39 | Number latNum = (Number) update.get(param_Fields[0]); 40 | Number lonNum = (Number) update.get(param_Fields[1]); 41 | if (latNum != null && lonNum != null) { 42 | LatLonAlt latLonAlt = new LatLonAlt(latNum.doubleValue(), lonNum.doubleValue(), 0.0); 43 | if (!positionProjector.isInited()) { 44 | positionProjector.init(latLonAlt); 45 | } 46 | double[] xyz = positionProjector.project(latLonAlt); 47 | addPoint(0, time, xyz[0]); 48 | addPoint(1, time, xyz[1]); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/processors/Integral.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot.processors; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * User: ton Date: 04.11.13 Time: 23:11 7 | */ 8 | public class Integral extends Simple { 9 | private double param_In_Offset; 10 | private double[] integrals; 11 | private double[] times; 12 | 13 | @Override 14 | public Map getDefaultParameters() { 15 | Map params = super.getDefaultParameters(); 16 | params.put("In Offset", 0.0); 17 | return params; 18 | } 19 | 20 | @Override 21 | public void init() { 22 | super.init(); 23 | param_In_Offset = (Double) parameters.get("In Offset"); 24 | integrals = new double[param_Fields.length]; 25 | times = new double[param_Fields.length]; 26 | for (int i = 0; i < integrals.length; i++) { 27 | integrals[i] = 0.0; 28 | times[i] = Double.NaN; 29 | } 30 | } 31 | 32 | @Override 33 | protected double preProcessValue(int idx, double time, double in) { 34 | if (!Double.isNaN(times[idx])) { 35 | integrals[idx] += (in + param_In_Offset) * (time - times[idx]); 36 | } 37 | times[idx] = time; 38 | return integrals[idx]; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/processors/LandDetector.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot.processors; 2 | 3 | import me.drton.flightplot.processors.tools.LowPassFilter; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | /** 9 | * User: ton Date: 25.07.13 Time: 14:20 10 | */ 11 | public class LandDetector extends PlotProcessor { 12 | private String param_Field_Baro; 13 | private String param_Field_Thrust; 14 | private double param_Filter_Time; 15 | private double param_Threshold_Alt2; 16 | private double param_Threshold_Thrust; 17 | private double timePrev; 18 | private double baro; 19 | private double thrust; 20 | private LowPassFilter baroLPF; 21 | private double landDetectedTime; 22 | private boolean landed; 23 | private boolean initialized; 24 | private double altAvg; 25 | private double altDisp; 26 | 27 | @Override 28 | public Map getDefaultParameters() { 29 | Map params = new HashMap(); 30 | params.put("Field Baro", "SENS.BaroAlt"); 31 | params.put("Field Thrust", "ATTC.Thrust"); 32 | params.put("Baro LPF", 20.0); 33 | params.put("Filter Time", 1.0); 34 | params.put("Threshold Alt", 0.3); 35 | params.put("Threshold Thrust", 0.25); 36 | return params; 37 | } 38 | 39 | @Override 40 | public void init() { 41 | timePrev = Double.NaN; 42 | landed = true; 43 | landDetectedTime = Double.NaN; 44 | initialized = false; 45 | altAvg = 0.0; 46 | altDisp = 0.0; 47 | baro = 0.0; 48 | baroLPF = new LowPassFilter(); 49 | thrust = 0.0; 50 | param_Field_Baro = (String) parameters.get("Field Baro"); 51 | param_Field_Thrust = (String) parameters.get("Field Thrust"); 52 | param_Filter_Time = (Double) parameters.get("Filter Time"); 53 | baroLPF.setF((Double) parameters.get("Baro LPF")); 54 | param_Threshold_Alt2 = (Double) parameters.get("Threshold Alt"); 55 | param_Threshold_Alt2 = param_Threshold_Alt2 * param_Threshold_Alt2; 56 | param_Threshold_Thrust = (Double) parameters.get("Threshold Thrust"); 57 | addSeries("Landed"); 58 | addSeries("AltDisp"); 59 | } 60 | 61 | @Override 62 | public void process(double time, Map update) { 63 | Number baroNum = (Number) update.get(param_Field_Baro); 64 | if (baroNum != null) { 65 | baro = baroLPF.getOutput(time, baroNum.doubleValue()); 66 | if (!initialized) { 67 | initialized = true; 68 | altAvg = baro; 69 | } 70 | } 71 | Number thrustNum = (Number) update.get(param_Field_Thrust); 72 | if (thrustNum != null) { 73 | thrust = thrustNum.doubleValue(); 74 | } 75 | if (initialized && !Double.isNaN(timePrev)) { 76 | double dt = time - timePrev; 77 | altAvg += (baro - altAvg) * dt / param_Filter_Time; 78 | altDisp = baro - altAvg; 79 | altDisp = altDisp * altDisp; 80 | if (landed) { 81 | if (altDisp > param_Threshold_Alt2 && thrust > param_Threshold_Thrust) { 82 | landed = false; 83 | landDetectedTime = Double.NaN; 84 | } 85 | } else { 86 | if (altDisp < param_Threshold_Alt2 && thrust < param_Threshold_Thrust) { 87 | if (Double.isNaN(landDetectedTime)) { 88 | landDetectedTime = time; // land detected first time 89 | } else { 90 | if (time - landDetectedTime > param_Filter_Time) { 91 | landed = true; 92 | landDetectedTime = Double.NaN; 93 | } 94 | } 95 | } else { 96 | landDetectedTime = Double.NaN; 97 | } 98 | } 99 | } 100 | addPoint(0, time, landed ? 1.0 : 0.0); 101 | addPoint(1, time, Math.sqrt(altDisp)); 102 | timePrev = time; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/processors/NEDFromBodyProjection.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot.processors; 2 | 3 | import me.drton.flightplot.processors.tools.LowPassFilter; 4 | import me.drton.jmavlib.conversion.RotationConversion; 5 | 6 | import javax.vecmath.Matrix3d; 7 | import javax.vecmath.Vector3d; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | /** 12 | * User: ton Date: 14.09.13 Time: 23:45 13 | */ 14 | public class NEDFromBodyProjection extends PlotProcessor { 15 | private String[] param_Fields; 16 | private String[] param_Fields_Att; 17 | private double param_Scale; 18 | private double param_Offset; 19 | private boolean param_Backward; 20 | private double[] param_Att_Offsets; 21 | private boolean[] show; 22 | private LowPassFilter[] lowPassFilters; 23 | private Matrix3d r; 24 | private double[] vArr; 25 | private Vector3d v; 26 | private double[] vNEDArr; 27 | private Vector3d vNED; 28 | 29 | @Override 30 | public Map getDefaultParameters() { 31 | Map params = new HashMap(); 32 | params.put("Fields", "IMU.AccX IMU.AccY IMU.AccZ"); 33 | params.put("Fields Att", "ATT.Roll ATT.Pitch ATT.Yaw"); 34 | params.put("Att Offsets", "0.0 0.0 0.0"); 35 | params.put("Show", "XYZ"); 36 | params.put("LPF", 0.0); 37 | params.put("Scale", 1.0); 38 | params.put("Offset", 0.0); 39 | params.put("Backward", false); 40 | return params; 41 | } 42 | 43 | @Override 44 | public void init() { 45 | param_Fields = ((String) parameters.get("Fields")).split(WHITESPACE_RE); 46 | param_Fields_Att = ((String) parameters.get("Fields Att")).split(WHITESPACE_RE); 47 | String[] attOffsStr = ((String) parameters.get("Att Offsets")).split(WHITESPACE_RE); 48 | param_Scale = (Double) parameters.get("Scale"); 49 | param_Offset = (Double) parameters.get("Offset"); 50 | param_Backward = (Boolean) parameters.get("Backward"); 51 | String showStr = ((String) parameters.get("Show")).toUpperCase(); 52 | show = new boolean[]{false, false, false}; 53 | lowPassFilters = new LowPassFilter[3]; 54 | vArr = new double[3]; 55 | v = new Vector3d(); 56 | vNEDArr = new double[3]; 57 | vNED = new Vector3d(); 58 | r = new Matrix3d(); 59 | param_Att_Offsets = new double[3]; 60 | for (int i = 0; i < 3; i++) { 61 | if (attOffsStr.length > i) { 62 | param_Att_Offsets[i] = Double.parseDouble(attOffsStr[i]); 63 | } else { 64 | param_Att_Offsets[i] = 0.0; 65 | } 66 | String axisName = "XYZ".substring(i, i + 1); 67 | show[i] = showStr.contains(axisName); 68 | if (show[i]) { 69 | LowPassFilter lowPassFilter = new LowPassFilter(); 70 | lowPassFilter.setF((Double) parameters.get("LPF")); 71 | lowPassFilters[i] = lowPassFilter; 72 | addSeries(axisName); 73 | } 74 | } 75 | } 76 | 77 | @Override 78 | public void process(double time, Map update) { 79 | boolean act = false; 80 | 81 | Number roll = (Number) update.get(param_Fields_Att[0]); 82 | Number pitch = (Number) update.get(param_Fields_Att[1]); 83 | Number yaw = (Number) update.get(param_Fields_Att[2]); 84 | 85 | if (roll != null && pitch != null && yaw != null) { 86 | // Update rotation matrix 87 | r.set(RotationConversion.rotationMatrixByEulerAngles(roll.doubleValue() + param_Att_Offsets[0], 88 | pitch.doubleValue() + param_Att_Offsets[1], yaw.doubleValue() + param_Att_Offsets[2])); 89 | if (param_Backward) { 90 | r.transpose(); 91 | } 92 | act = true; 93 | } 94 | 95 | for (int i = 0; i < 3; i++) { 96 | Number vNum = (Number) update.get(param_Fields[i]); 97 | if (vNum != null) { 98 | // Update source vector 99 | vArr[i] = vNum.doubleValue(); 100 | act = true; 101 | } 102 | } 103 | v.set(vArr); 104 | 105 | if (act) { 106 | vNED.set(v); 107 | r.transform(vNED); 108 | int seriesIdx = 0; 109 | vNED.get(vNEDArr); 110 | for (int i = 0; i < 3; i++) { 111 | if (show[i]) { 112 | double out = lowPassFilters[i].getOutput(time, vNEDArr[i]); 113 | addPoint(seriesIdx, time, out * param_Scale + param_Offset); 114 | seriesIdx++; 115 | } 116 | } 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/processors/PlotProcessor.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot.processors; 2 | 3 | import me.drton.flightplot.MarkersList; 4 | import me.drton.flightplot.PlotItem; 5 | import me.drton.flightplot.Series; 6 | import me.drton.flightplot.XYPoint; 7 | 8 | import java.util.ArrayList; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | /** 14 | * User: ton Date: 12.06.13 Time: 18:25 15 | */ 16 | public abstract class PlotProcessor { 17 | protected static final String WHITESPACE_RE = "[ \t]+"; 18 | protected Map parameters; 19 | protected Map fieldsList = new HashMap(); 20 | private double skipOut = 0.0; 21 | private List seriesList = new ArrayList(); 22 | private List lastPoints = new ArrayList(); 23 | 24 | protected PlotProcessor() { 25 | this.parameters = getDefaultParameters(); 26 | } 27 | 28 | public abstract void init(); 29 | 30 | public void setSkipOut(double skipOut) { 31 | this.skipOut = skipOut; 32 | } 33 | 34 | public void setFieldsList(Map fieldsList) { 35 | this.fieldsList = fieldsList; 36 | } 37 | 38 | private static Object castValue(Object valueOld, Object valueNewObj) { 39 | String valueNewStr = valueNewObj.toString(); 40 | Object valueNew = valueNewObj; 41 | if (valueOld instanceof String) { 42 | valueNew = valueNewStr; 43 | } else if (valueOld instanceof Double) { 44 | valueNew = Double.parseDouble(valueNewStr); 45 | } else if (valueOld instanceof Float) { 46 | valueNew = Float.parseFloat(valueNewStr); 47 | } else if (valueOld instanceof Integer) { 48 | valueNew = Integer.parseInt(valueNewStr); 49 | } else if (valueOld instanceof Long) { 50 | valueNew = Long.parseLong(valueNewStr); 51 | } else if (valueOld instanceof Boolean) { 52 | char firstChar = valueNewStr.toLowerCase().charAt(0); 53 | if (firstChar == 'f' || firstChar == 'n' || "0".equals(valueNewStr)) 54 | valueNew = false; 55 | else 56 | valueNew = true; 57 | } 58 | return valueNew; 59 | } 60 | 61 | public abstract Map getDefaultParameters(); 62 | 63 | public Map getParameters() { 64 | return parameters; 65 | } 66 | 67 | public void setParameters(Map parametersNew) { 68 | for (Map.Entry entry : parametersNew.entrySet()) { 69 | String key = entry.getKey(); 70 | Object oldValue = parameters.get(key); 71 | Object newValue = parametersNew.get(key); 72 | if (oldValue != null) { 73 | parameters.put(key, castValue(oldValue, newValue)); 74 | } 75 | } 76 | } 77 | 78 | protected int addSeries() { 79 | int idx = seriesList.size(); 80 | seriesList.add(new Series("", skipOut)); 81 | lastPoints.add(null); 82 | return idx; 83 | } 84 | 85 | protected int addSeries(String label) { 86 | int idx = seriesList.size(); 87 | seriesList.add(new Series(label, skipOut)); 88 | lastPoints.add(null); 89 | return idx; 90 | } 91 | 92 | protected int addMarkersList() { 93 | int idx = seriesList.size(); 94 | seriesList.add(new MarkersList("")); 95 | return idx; 96 | } 97 | 98 | protected int addMarkersList(String label) { 99 | int idx = seriesList.size(); 100 | seriesList.add(new MarkersList(label)); 101 | return idx; 102 | } 103 | 104 | public List getSeriesList() { 105 | return seriesList; 106 | } 107 | 108 | protected void addPoint(int seriesIdx, double time, double value) { 109 | ((Series) seriesList.get(seriesIdx)).addPoint(time, value); 110 | } 111 | 112 | protected void addMarker(int seriesIdx, double time, String label) { 113 | ((MarkersList) seriesList.get(seriesIdx)).addMarker(time, label); 114 | } 115 | 116 | public abstract void process(double time, Map update); 117 | 118 | public String getProcessorType() { 119 | return getClass().getSimpleName(); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/processors/PosPIDControlSimulator.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot.processors; 2 | 3 | import me.drton.flightplot.processors.tools.LowPassFilter; 4 | import me.drton.flightplot.processors.tools.PID; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * User: ton Date: 20.06.13 Time: 6:06 11 | */ 12 | public class PosPIDControlSimulator extends PlotProcessor { 13 | private double startTime; 14 | private double startSP; 15 | private double startSPRate; 16 | private double thrustK; 17 | private double accScale; 18 | private double drag; 19 | private double awuRate; 20 | 21 | private LowPassFilter propeller = new LowPassFilter(); 22 | private PID pidPos = new PID(); 23 | private double pos; 24 | private double rate; 25 | private double posSP; 26 | private double timePrev; 27 | 28 | @Override 29 | public Map getDefaultParameters() { 30 | Map params = new HashMap(); 31 | params.put("Start Time", 100.0); 32 | params.put("Start SP", 1.0); 33 | params.put("Start SP Rate", 1.0); 34 | params.put("Thrust T", 0.03); 35 | params.put("Thrust K", 40.0); 36 | params.put("Ctrl P", 0.1); 37 | params.put("Ctrl I", 0.05); 38 | params.put("Ctrl D", 0.1); 39 | params.put("Ctrl Limit", 0.2); 40 | params.put("AWU Rate", 1.0); 41 | params.put("Acc Scale", 1.0); 42 | params.put("Drag", 0.0); 43 | return params; 44 | } 45 | 46 | @Override 47 | public void init() { 48 | propeller.reset(); 49 | pos = 0.0; 50 | rate = 0.0; 51 | posSP = 0.0; 52 | timePrev = Double.NaN; 53 | startTime = (Double) parameters.get("Start Time"); 54 | startSP = (Double) parameters.get("Start SP"); 55 | startSPRate = (Double) parameters.get("Start SP Rate"); 56 | thrustK = (Double) parameters.get("Thrust K"); 57 | accScale = (Double) parameters.get("Acc Scale"); 58 | drag = (Double) parameters.get("Drag"); 59 | awuRate = (Double) parameters.get("AWU Rate"); 60 | propeller.setT((Double) parameters.get("Thrust T")); 61 | pidPos.reset(); 62 | pidPos.setK((Double) parameters.get("Ctrl P"), (Double) parameters.get("Ctrl I"), 63 | (Double) parameters.get("Ctrl D"), (Double) parameters.get("Ctrl Limit"), PID.MODE.DERIVATIVE_CALC); 64 | addSeries("Pos"); 65 | addSeries("Rate"); 66 | addSeries("Acc"); 67 | addSeries("Ctrl"); 68 | } 69 | 70 | @Override 71 | public void process(double time, Map update) { 72 | if (update.containsKey("ATT.Roll")) { // Act only on attitude updates 73 | if (!Double.isNaN(timePrev)) { 74 | double dt = time - timePrev; 75 | double spRate = 0.0; 76 | if (time > startTime && posSP < startSP) { 77 | spRate = startSPRate; 78 | posSP += startSPRate * dt; 79 | } 80 | double force = propeller.getOutput(time, 0.0); 81 | double acc = force * thrustK - drag * rate; 82 | rate += acc * dt; 83 | pos += rate * dt; 84 | double awuW = 85 | awuRate == 0.0 ? 1.0 : Math.exp(-(spRate * spRate + rate * rate) / 2.0 / awuRate / awuRate); 86 | double thrustControl = pidPos.getOutput(posSP, pos, spRate - rate, dt, awuW); 87 | propeller.setInput(thrustControl); 88 | addPoint(0, time, pos); 89 | addPoint(1, time, rate); 90 | if (accScale != 0.0) 91 | addPoint(2, time, acc * accScale); 92 | addPoint(3, time, pidPos.getIntegral() * 10); 93 | } 94 | timePrev = time; 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/processors/PosRatePIDControlSimulator.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot.processors; 2 | 3 | import me.drton.flightplot.processors.tools.LowPassFilter; 4 | import me.drton.flightplot.processors.tools.PID; 5 | import me.drton.jmavlib.conversion.RotationConversion; 6 | import me.drton.jmavlib.processing.Batterworth2pLPF; 7 | import me.drton.jmavlib.processing.DelayLine; 8 | import me.drton.jmavlib.processing.Filter; 9 | 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | /** 14 | * User: ton Date: 20.06.13 Time: 6:06 15 | */ 16 | public class PosRatePIDControlSimulator extends PlotProcessor { 17 | private double timeStep; 18 | private double thrustK; 19 | private double accScale; 20 | private double drag; 21 | private DelayLine delayLine = new DelayLine(); 22 | private Filter rateLPF = new Batterworth2pLPF(); 23 | private LowPassFilter controlLPF = new LowPassFilter(); 24 | private PID pidPos = new PID(); 25 | private PID pidRate = new PID(); 26 | private double pos; 27 | private double rate; 28 | private double posSP; 29 | private boolean useRateSP; 30 | private double rateSP; 31 | private double timePrev; 32 | private String spField; 33 | private String rateSpField; 34 | 35 | @Override 36 | public Map getDefaultParameters() { 37 | Map params = new HashMap(); 38 | params.put("Time Step", 0.004); 39 | params.put("Thrust T", 0.03); 40 | params.put("Thrust Delay", 0.05); 41 | params.put("Thrust K", 200.0); 42 | params.put("Ctrl P", 5.0); 43 | params.put("Ctrl D", 0.0); 44 | params.put("Ctrl Limit", 0.0); 45 | params.put("Ctrl Rate P", 0.1); 46 | params.put("Ctrl Rate I", 0.0); 47 | params.put("Ctrl Rate D", 0.0); 48 | params.put("Ctrl Rate D SP", false); 49 | params.put("Ctrl Rate Limit", 0.0); 50 | params.put("Acc Scale", 1.0); 51 | params.put("Drag", 0.0); 52 | params.put("Use Rate SP", false); 53 | params.put("Rate LPF", 0.0); 54 | params.put("Field SP", ""); 55 | params.put("Field Rate SP", ""); 56 | return params; 57 | } 58 | 59 | @Override 60 | public void init() { 61 | pos = 0.0; 62 | rate = 0.0; 63 | posSP = 0.0; 64 | rateSP = 0.0; 65 | timePrev = -1.0; 66 | timeStep = (Double) parameters.get("Time Step"); 67 | thrustK = (Double) parameters.get("Thrust K"); 68 | accScale = (Double) parameters.get("Acc Scale"); 69 | drag = (Double) parameters.get("Drag"); 70 | useRateSP = (Boolean) parameters.get("Use Rate SP"); 71 | delayLine.reset(); 72 | delayLine.setDelay((Double) parameters.get("Thrust Delay")); 73 | controlLPF.reset(); 74 | controlLPF.setT((Double) parameters.get("Thrust T")); 75 | pidPos.reset(); 76 | pidPos.setK((Double) parameters.get("Ctrl P"), 0.0, (Double) parameters.get("Ctrl D"), 77 | (Double) parameters.get("Ctrl Limit"), PID.MODE.DERIVATIVE_SET); 78 | pidRate.reset(); 79 | PID.MODE pidRateMode = (Boolean) parameters.get( 80 | "Ctrl Rate D SP") ? PID.MODE.DERIVATIVE_CALC : PID.MODE.DERIVATIVE_CALC_NO_SP; 81 | pidRate.setK((Double) parameters.get("Ctrl Rate P"), (Double) parameters.get("Ctrl Rate I"), 82 | (Double) parameters.get("Ctrl Rate D"), (Double) parameters.get("Ctrl Rate Limit"), pidRateMode); 83 | rateLPF.setCutoffFreqFactor(((Double) parameters.get("Rate LPF")) * timeStep); 84 | spField = (String) parameters.get("Field SP"); 85 | rateSpField = (String) parameters.get("Field Rate SP"); 86 | addSeries("Rate"); 87 | addSeries("Ctrl"); 88 | addSeries("Acc"); 89 | if (useRateSP) { 90 | addSeries("Rate SP"); 91 | } else { 92 | addSeries("Pos SP"); 93 | addSeries("Pos"); 94 | } 95 | } 96 | 97 | @Override 98 | public void process(double time, Map update) { 99 | if (timePrev < 0.0) { 100 | timePrev = time; 101 | return; 102 | } 103 | while (time > timePrev + timeStep) { 104 | timePrev += timeStep; 105 | updateSimulation(timePrev, timeStep); 106 | } 107 | updateSP(update); 108 | } 109 | 110 | private void updateSP(Map update) { 111 | if (useRateSP) { 112 | Number v = (Number) update.get(rateSpField); 113 | if (v != null) { 114 | rateSP = v.doubleValue(); 115 | } 116 | } else { 117 | String[] p = spField.split(" "); 118 | if (p.length > 1) { 119 | int axis = "RPY".indexOf(p[0]); 120 | if (axis >= 0 && axis < 3) { 121 | double[] q = new double[4]; 122 | for (int i = 0; i < 4; i++) { 123 | Number v = (Number) update.get(p[i + 1]); 124 | if (v == null) { 125 | return; 126 | } 127 | q[i] = v.doubleValue(); 128 | } 129 | double[] euler = RotationConversion.eulerAnglesByQuaternion(q); 130 | posSP = euler[axis]; 131 | } 132 | } else { 133 | Number v = (Number) update.get(spField); 134 | if (v != null) { 135 | posSP = v.doubleValue(); 136 | } 137 | } 138 | } 139 | } 140 | 141 | private void updateSimulation(double time, double dt) { 142 | Double force = delayLine.getOutput(time, controlLPF.getOutput(time, 0.0)); 143 | if (force == null) { 144 | force = 0.0; 145 | } 146 | double acc = force * thrustK - drag * Math.abs(rate) * rate; 147 | rate += acc * dt; 148 | pos += rate * dt; 149 | double rateFiltered = rateLPF.apply(rate); 150 | if (!useRateSP) { 151 | rateSP = pidPos.getOutput(posSP - pos, - rateFiltered, dt); 152 | } 153 | double control = pidRate.getOutput(rateSP, rateFiltered, 0.0, dt, 1.0); 154 | controlLPF.setInput(control); 155 | 156 | addPoint(0, time, rate); 157 | addPoint(1, time, control); 158 | if (accScale != 0.0) { 159 | addPoint(2, time, acc * accScale); 160 | } 161 | if (useRateSP) { 162 | addPoint(3, time, rateSP); 163 | } else { 164 | addPoint(3, time, posSP); 165 | addPoint(4, time, pos); 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/processors/ProcessorsList.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot.processors; 2 | 3 | import me.drton.flightplot.ColorSupplier; 4 | import me.drton.flightplot.ProcessorPreset; 5 | 6 | import java.lang.reflect.InvocationTargetException; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | import java.util.Set; 10 | 11 | /** 12 | * User: ton Date: 15.06.13 Time: 12:21 13 | */ 14 | public class ProcessorsList { 15 | private Map> processors 16 | = new HashMap>(); 17 | 18 | public ProcessorsList() throws InstantiationException, IllegalAccessException { 19 | addProcessorClass(Simple.class); 20 | addProcessorClass(Derivative.class); 21 | addProcessorClass(Abs.class); 22 | addProcessorClass(ATan2.class); 23 | addProcessorClass(PosPIDControlSimulator.class); 24 | addProcessorClass(PosRatePIDControlSimulator.class); 25 | addProcessorClass(PositionEstimator.class); 26 | addProcessorClass(GlobalPositionProjection.class); 27 | addProcessorClass(LandDetector.class); 28 | addProcessorClass(Expression.class); 29 | addProcessorClass(NEDFromBodyProjection.class); 30 | addProcessorClass(Integral.class); 31 | addProcessorClass(Battery.class); 32 | addProcessorClass(PositionEstimatorKF.class); 33 | addProcessorClass(EulerFromQuaternion.class); 34 | addProcessorClass(Text.class); 35 | } 36 | 37 | private void addProcessorClass(Class processorClass) { 38 | processors.put(processorClass.getSimpleName(), processorClass); 39 | } 40 | 41 | public Set getProcessorsList() { 42 | return processors.keySet(); 43 | } 44 | 45 | public PlotProcessor getProcessorInstance(ProcessorPreset processorPreset, double skipOut, Map fieldsList) 46 | throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { 47 | Class procClass = processors.get(processorPreset.getProcessorType()); 48 | if (procClass != null) { 49 | PlotProcessor processor = procClass.newInstance(); 50 | processor.setSkipOut(skipOut); 51 | processor.setFieldsList(fieldsList); 52 | processor.setParameters(processorPreset.getParameters()); 53 | processor.init(); 54 | return processor; 55 | } else { 56 | return null; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/processors/Simple.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot.processors; 2 | 3 | import me.drton.flightplot.processors.tools.LowPassFilter; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | /** 9 | * User: ton Date: 15.06.13 Time: 12:04 10 | */ 11 | public class Simple extends PlotProcessor { 12 | protected String[] param_Fields; 13 | protected double param_Scale; 14 | protected double param_Offset; 15 | protected double param_Delay; 16 | protected LowPassFilter[] lowPassFilters; 17 | 18 | @Override 19 | public Map getDefaultParameters() { 20 | Map params = new HashMap(); 21 | params.put("Fields", "ATT.Pitch ATT.Roll"); 22 | params.put("Delay", 0.0); 23 | params.put("LPF", 0.0); 24 | params.put("Scale", 1.0); 25 | params.put("Offset", 0.0); 26 | return params; 27 | } 28 | 29 | @Override 30 | public void init() { 31 | param_Fields = ((String) parameters.get("Fields")).split(WHITESPACE_RE); 32 | param_Scale = (Double) parameters.get("Scale"); 33 | param_Offset = (Double) parameters.get("Offset"); 34 | param_Delay = (Double) parameters.get("Delay"); 35 | lowPassFilters = new LowPassFilter[param_Fields.length]; 36 | for (int i = 0; i < param_Fields.length; i++) { 37 | LowPassFilter lowPassFilter = new LowPassFilter(); 38 | lowPassFilter.setF((Double) parameters.get("LPF")); 39 | lowPassFilters[i] = lowPassFilter; 40 | } 41 | for (String field : param_Fields) { 42 | addSeries(field); 43 | } 44 | } 45 | 46 | protected double preProcessValue(int idx, double time, double in) { 47 | return in; 48 | } 49 | 50 | protected double postProcessValue(int idx, double time, double in) { 51 | return in; 52 | } 53 | 54 | @Override 55 | public void process(double time, Map update) { 56 | for (int i = 0; i < param_Fields.length; i++) { 57 | String field = param_Fields[i]; 58 | Object v = update.get(field); 59 | if (v != null && v instanceof Number) { 60 | double out = preProcessValue(i, time, ((Number) v).doubleValue()); 61 | if (Double.isNaN(out)) { 62 | addPoint(i, time, Double.NaN); 63 | } else { 64 | out = lowPassFilters[i].getOutput(time, out); 65 | out = postProcessValue(i, time, out); 66 | addPoint(i, time + param_Delay, out * param_Scale + param_Offset); 67 | } 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/processors/Text.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot.processors; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * Created by ton on 29.09.15. 8 | */ 9 | public class Text extends PlotProcessor { 10 | protected String param_Field; 11 | 12 | @Override 13 | public Map getDefaultParameters() { 14 | Map params = new HashMap(); 15 | params.put("Field", "STATUS_TEXT.text"); 16 | return params; 17 | } 18 | 19 | @Override 20 | public void init() { 21 | param_Field = (String) parameters.get("Field"); 22 | addMarkersList(); 23 | } 24 | 25 | @Override 26 | public void process(double time, Map update) { 27 | Object v = update.get(param_Field); 28 | if (v != null && v instanceof String) { 29 | addMarker(0, time, (String) v); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/processors/tools/LowPassFilter.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot.processors.tools; 2 | 3 | /** 4 | * User: ton Date: 09.03.13 Time: 15:07 5 | */ 6 | public class LowPassFilter { 7 | private double inLast = 0.0; 8 | private double valueFiltered = 0.0; 9 | private double tLast = Double.NaN; 10 | private double f = 1.0; 11 | private double rc_inv = f * 2 * Math.PI; 12 | 13 | public void setF(double f) { 14 | this.f = f; 15 | this.rc_inv = f * 2 * Math.PI; 16 | } 17 | 18 | public void setT(double t) { 19 | if (t == 0.0) { 20 | this.f = 0.0; 21 | this.rc_inv = 0.0; 22 | } else { 23 | this.f = 1 / t; 24 | this.rc_inv = f * 2 * Math.PI; 25 | } 26 | } 27 | 28 | public void reset() { 29 | tLast = Double.NaN; 30 | } 31 | 32 | public double getOutput(double t, double in) { 33 | if (rc_inv == 0.0) { 34 | this.valueFiltered = in; 35 | return in; 36 | } else { 37 | if (Double.isNaN(tLast)) { 38 | this.tLast = t; 39 | this.inLast = in; 40 | this.valueFiltered = in; 41 | return in; 42 | } else { 43 | double dt = t - tLast; 44 | this.valueFiltered += (1.0 - Math.exp(-dt * rc_inv)) * (inLast - valueFiltered); 45 | this.inLast = in; 46 | this.tLast = t; 47 | return valueFiltered; 48 | } 49 | } 50 | } 51 | 52 | public void setInput(double in) { 53 | this.inLast = in; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/me/drton/flightplot/processors/tools/PID.java: -------------------------------------------------------------------------------- 1 | package me.drton.flightplot.processors.tools; 2 | 3 | /** 4 | * User: ton Date: 20.06.13 Time: 7:13 5 | */ 6 | public class PID { 7 | public static enum MODE { 8 | DERIVATIVE_SET, 9 | DERIVATIVE_CALC, 10 | DERIVATIVE_CALC_NO_SP, 11 | } 12 | 13 | private double kP = 0.0; 14 | private double kI = 0.0; 15 | private double kD = 0.0; 16 | private double limit = 0.0; 17 | private double integral = 0.0; 18 | private double errorLast = 0.0; 19 | private MODE mode = MODE.DERIVATIVE_CALC; 20 | 21 | public void setK(double kP, double kI, double kD, double limit, MODE mode) { 22 | this.kP = kP; 23 | this.kI = kI; 24 | this.kD = kD; 25 | this.limit = limit; 26 | this.mode = mode; 27 | } 28 | 29 | public void reset() { 30 | errorLast = 0.0; 31 | integral = 0.0; 32 | } 33 | 34 | public double getIntegral() { 35 | return integral; 36 | } 37 | 38 | public void setIntegral(double integral) { 39 | this.integral = integral; 40 | } 41 | 42 | private double limitValue(double value) { 43 | if (limit == 0.0) 44 | return value; 45 | else 46 | return Math.max(-limit, Math.min(limit, value)); 47 | } 48 | 49 | public double getOutput(double err, double dt) { 50 | if (mode != MODE.DERIVATIVE_CALC) 51 | throw new RuntimeException("Can't use this method in mode " + mode); 52 | double d = (err - errorLast) / dt; 53 | errorLast = err; 54 | double pd = err * kP + d * kD; 55 | integral += pd / kP * kI * dt; 56 | integral = limitValue(integral); 57 | return limitValue(pd + integral); 58 | } 59 | 60 | public double getOutput(double err, double derivative, double dt) { 61 | if (mode != MODE.DERIVATIVE_SET) 62 | throw new RuntimeException("Can't use this method in mode " + mode); 63 | errorLast = err; 64 | double pd = err * kP + derivative * kD; 65 | integral += pd / kP * kI * dt; 66 | integral = limitValue(integral); 67 | return limitValue(pd + integral); 68 | } 69 | 70 | public double getOutput(double sp, double value, double derivative, double dt, double awuW) { 71 | double err = sp - value; 72 | double d; 73 | if (mode == MODE.DERIVATIVE_SET) { 74 | d = derivative; 75 | errorLast = err; 76 | } else if (mode == MODE.DERIVATIVE_CALC) { 77 | d = (err - errorLast) / dt; 78 | errorLast = err; 79 | } else if (mode == MODE.DERIVATIVE_CALC_NO_SP) { 80 | d = (-value - errorLast) / dt; 81 | errorLast = -value; 82 | } else { 83 | d = 0.0; 84 | errorLast = 0.0; 85 | } 86 | double pd = err * kP + d * kD; 87 | double i = integral + err * kI * dt * awuW; 88 | if (limit == 0.0 || (Math.abs(i) < limit && Math.abs(pd + i) < limit)) 89 | integral = limitValue(i); 90 | return limitValue(pd + integral); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/net/objecthunter/exp4j/Expression.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Frank Asseg 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.objecthunter.exp4j; 17 | 18 | import java.util.*; 19 | import java.util.concurrent.*; 20 | 21 | import net.objecthunter.exp4j.function.Function; 22 | import net.objecthunter.exp4j.operator.Operator; 23 | import net.objecthunter.exp4j.tokenizer.*; 24 | 25 | public class Expression { 26 | 27 | private final Token[] tokens; 28 | 29 | private final Map variables; 30 | 31 | private final Set userFunctionNames; 32 | 33 | 34 | Expression(final Token[] tokens) { 35 | this.tokens = tokens; 36 | this.variables = new HashMap(4); 37 | this.userFunctionNames = Collections.emptySet(); 38 | } 39 | 40 | Expression(final Token[] tokens, Set userFunctionNames) { 41 | this.tokens = tokens; 42 | this.variables = new HashMap(4); 43 | this.userFunctionNames = userFunctionNames; 44 | } 45 | 46 | public Expression setVariable(final String name, final double value) { 47 | this.checkVariableName(name); 48 | this.variables.put(name, value); 49 | return this; 50 | } 51 | 52 | private void checkVariableName(String name) { 53 | if (this.userFunctionNames.contains(name)) { 54 | throw new IllegalArgumentException("The setVariable name '" + name + "' is invalid. Since there exists a function with the same name"); 55 | } 56 | } 57 | 58 | public Expression setVariables(Map variables) { 59 | for (Map.Entry v : variables.entrySet()) { 60 | this.setVariable(v.getKey(), v.getValue()); 61 | } 62 | return this; 63 | } 64 | 65 | public ValidationResult validate(boolean checkVariablesSet) { 66 | final List errors = new ArrayList(0); 67 | if (checkVariablesSet) { 68 | /* check that all vars have a value set */ 69 | for (final Token t : this.tokens) { 70 | if (t.getType() == Token.TOKEN_VARIABLE) { 71 | final String var = ((VariableToken) t).getName(); 72 | if (!variables.containsKey(var)) { 73 | errors.add("The setVariable '" + var + "' has not been set"); 74 | } 75 | } 76 | } 77 | } 78 | 79 | /* Check if the number of operands, functions and operators match. 80 | The idea is to increment a counter for operands and decrease it for operators. 81 | When a function occurs the number of available arguments has to be greater 82 | than or equals to the function's expected number of arguments. 83 | The count has to be larger than 1 at all times and exactly 1 after all tokens 84 | have been processed */ 85 | int count = 0; 86 | for (Token tok : this.tokens) { 87 | switch (tok.getType()) { 88 | case Token.TOKEN_NUMBER: 89 | case Token.TOKEN_VARIABLE: 90 | count++; 91 | break; 92 | case Token.TOKEN_FUNCTION: 93 | final Function func = ((FunctionToken) tok).getFunction(); 94 | if (func.getNumArguments() > count) { 95 | errors.add("Not enough arguments for '" + func.getName() + "'"); 96 | } 97 | break; 98 | case Token.TOKEN_OPERATOR: 99 | Operator op = ((OperatorToken) tok).getOperator(); 100 | if (op.getNumOperands() == 2) { 101 | count--; 102 | } 103 | break; 104 | } 105 | if (count < 1) { 106 | errors.add("Too many operators"); 107 | return new ValidationResult(false, errors); 108 | } 109 | } 110 | if (count > 1) { 111 | errors.add("Too many operands"); 112 | } 113 | return errors.size() == 0 ? ValidationResult.SUCCESS : new ValidationResult(false, errors); 114 | 115 | } 116 | 117 | public ValidationResult validate() { 118 | return validate(true); 119 | } 120 | 121 | public Future evaluateAsync(ExecutorService executor) { 122 | return executor.submit(new Callable() { 123 | @Override 124 | public Double call() throws Exception { 125 | return evaluate(); 126 | } 127 | }); 128 | } 129 | 130 | public double evaluate() { 131 | final Stack output = new Stack(); 132 | for (int i = 0; i < tokens.length; i++) { 133 | Token t = tokens[i]; 134 | if (t.getType() == Token.TOKEN_NUMBER) { 135 | output.push(((NumberToken) t).getValue()); 136 | } else if (t.getType() == Token.TOKEN_VARIABLE) { 137 | final String name = ((VariableToken) t).getName(); 138 | final Double value = this.variables.get(name); 139 | if (value == null) { 140 | throw new IllegalArgumentException("No value has been set for the setVariable '" + name + "'."); 141 | } 142 | output.push(value); 143 | } else if (t.getType() == Token.TOKEN_OPERATOR) { 144 | OperatorToken op = (OperatorToken) t; 145 | if (output.size() < op.getOperator().getNumOperands()) { 146 | throw new IllegalArgumentException("Invalid number of operands available"); 147 | } 148 | if (op.getOperator().getNumOperands() == 2) { 149 | /* pop the operands and push the result of the operation */ 150 | double rightArg = output.pop(); 151 | double leftArg = output.pop(); 152 | output.push(op.getOperator().apply(leftArg, rightArg)); 153 | } else if (op.getOperator().getNumOperands() == 1) { 154 | /* pop the operand and push the result of the operation */ 155 | double arg = output.pop(); 156 | output.push(op.getOperator().apply(arg)); 157 | } 158 | } else if (t.getType() == Token.TOKEN_FUNCTION) { 159 | FunctionToken func = (FunctionToken) t; 160 | if (output.size() < func.getFunction().getNumArguments()) { 161 | throw new IllegalArgumentException("Invalid number of arguments available"); 162 | } 163 | /* collect the arguments from the stack */ 164 | double[] args = new double[func.getFunction().getNumArguments()]; 165 | for (int j = 0; j < func.getFunction().getNumArguments(); j++) { 166 | args[j] = output.pop(); 167 | } 168 | output.push(func.getFunction().apply(this.reverseInPlace(args))); 169 | } 170 | } 171 | if (output.size() > 1) { 172 | throw new IllegalArgumentException("Invalid number of items on the output queue. Might be caused by an invalid number of arguments for a function."); 173 | } 174 | return output.pop(); 175 | } 176 | 177 | private double[] reverseInPlace(double[] args) { 178 | int len = args.length; 179 | for (int i = 0; i < len / 2; i++) { 180 | double tmp = args[i]; 181 | args[i] = args[len - i - 1]; 182 | args[len - i - 1] = tmp; 183 | } 184 | return args; 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/net/objecthunter/exp4j/ExpressionBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Frank Asseg 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.objecthunter.exp4j; 18 | 19 | import java.util.*; 20 | 21 | import net.objecthunter.exp4j.function.Function; 22 | import net.objecthunter.exp4j.operator.Operator; 23 | import net.objecthunter.exp4j.shuntingyard.ShuntingYard; 24 | 25 | /** 26 | * Factory class for {@link Expression} instances. This class is the main API entrypoint. Users should create new 27 | * {@link Expression} instances using this factory class. 28 | */ 29 | public class ExpressionBuilder { 30 | 31 | private final String expression; 32 | 33 | private final Map userFunctions; 34 | 35 | private final Map userOperators; 36 | 37 | private final Set variableNames; 38 | 39 | /** 40 | * Create a new ExpressionBuilder instance and initialize it with a given expression string. 41 | * @param expression the expression to be parsed 42 | */ 43 | public ExpressionBuilder(String expression) { 44 | if (expression == null || expression.trim().length() == 0) { 45 | throw new IllegalArgumentException("Expression can not be empty"); 46 | } 47 | this.expression = expression; 48 | this.userOperators = new HashMap(4); 49 | this.userFunctions = new HashMap(4); 50 | this.variableNames = new HashSet(4); 51 | } 52 | 53 | /** 54 | * Add a {@link net.objecthunter.exp4j.function.Function} implementation available for use in the expression 55 | * @param function the custom {@link net.objecthunter.exp4j.function.Function} implementation that should be available for use in the expression. 56 | * @return the ExpressionBuilder instance 57 | */ 58 | public ExpressionBuilder function(Function function) { 59 | this.userFunctions.put(function.getName(), function); 60 | return this; 61 | } 62 | 63 | /** 64 | * Add multiple {@link net.objecthunter.exp4j.function.Function} implementations available for use in the expression 65 | * @param functions the custom {@link net.objecthunter.exp4j.function.Function} implementations 66 | * @return the ExpressionBuilder instance 67 | */ 68 | public ExpressionBuilder functions(Function... functions) { 69 | for (Function f : functions) { 70 | this.userFunctions.put(f.getName(), f); 71 | } 72 | return this; 73 | } 74 | 75 | /** 76 | * Add multiple {@link net.objecthunter.exp4j.function.Function} implementations available for use in the expression 77 | * @param functions A {@link java.util.List} of custom {@link net.objecthunter.exp4j.function.Function} implementations 78 | * @return the ExpressionBuilder instance 79 | */ 80 | public ExpressionBuilder functions(List functions) { 81 | for (Function f : functions) { 82 | this.userFunctions.put(f.getName(), f); 83 | } 84 | return this; 85 | } 86 | 87 | public ExpressionBuilder variables(Set variableNames) { 88 | this.variableNames.addAll(variableNames); 89 | return this; 90 | } 91 | 92 | public ExpressionBuilder variables(String ... variableNames) { 93 | Collections.addAll(this.variableNames, variableNames); 94 | return this; 95 | } 96 | 97 | public ExpressionBuilder variable(String variableName) { 98 | this.variableNames.add(variableName); 99 | return this; 100 | } 101 | 102 | /** 103 | * Add an {@link net.objecthunter.exp4j.operator.Operator} which should be available for use in the expression 104 | * @param operator the custom {@link net.objecthunter.exp4j.operator.Operator} to add 105 | * @return the ExpressionBuilder instance 106 | */ 107 | public ExpressionBuilder operator(Operator operator) { 108 | this.checkOperatorSymbol(operator); 109 | this.userOperators.put(operator.getSymbol(), operator); 110 | return this; 111 | } 112 | 113 | private void checkOperatorSymbol(Operator op) { 114 | String name = op.getSymbol(); 115 | for (char ch : name.toCharArray()) { 116 | if (!Operator.isAllowedOperatorChar(ch)) { 117 | throw new IllegalArgumentException("The operator symbol '" + name + "' is invalid"); 118 | } 119 | } 120 | } 121 | 122 | /** 123 | * Add multiple {@link net.objecthunter.exp4j.operator.Operator} implementations which should be available for use in the expression 124 | * @param operators the set of custom {@link net.objecthunter.exp4j.operator.Operator} implementations to add 125 | * @return the ExpressionBuilder instance 126 | */ 127 | public ExpressionBuilder operator(Operator... operators) { 128 | for (Operator o : operators) { 129 | this.operator(o); 130 | } 131 | return this; 132 | } 133 | 134 | /** 135 | * Add multiple {@link net.objecthunter.exp4j.operator.Operator} implementations which should be available for use in the expression 136 | * @param operators the {@link java.util.List} of custom {@link net.objecthunter.exp4j.operator.Operator} implementations to add 137 | * @return the ExpressionBuilder instance 138 | */ 139 | public ExpressionBuilder operator(List operators) { 140 | for (Operator o : operators) { 141 | this.operator(o); 142 | } 143 | return this; 144 | } 145 | 146 | /** 147 | * Build the {@link Expression} instance using the custom operators and functions set. 148 | * @return an {@link Expression} instance which can be used to evaluate the result of the expression 149 | */ 150 | public Expression build() { 151 | if (expression.length() == 0) { 152 | throw new IllegalArgumentException("The expression can not be empty"); 153 | } 154 | return new Expression(ShuntingYard.convertToRPN(this.expression, this.userFunctions, this.userOperators, this.variableNames), 155 | this.userFunctions.keySet()); 156 | } 157 | 158 | } 159 | -------------------------------------------------------------------------------- /src/net/objecthunter/exp4j/ValidationResult.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Frank Asseg 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.objecthunter.exp4j; 17 | 18 | import java.util.List; 19 | 20 | /** 21 | * Contains the validation result for a given {@link Expression} 22 | */ 23 | public class ValidationResult { 24 | private final boolean valid; 25 | private final List errors; 26 | 27 | /** 28 | * Create a new instance 29 | * @param valid Whether the validation of the expression was successful 30 | * @param errors The list of errors returned if the validation was unsuccessful 31 | */ 32 | public ValidationResult(boolean valid, List errors) { 33 | this.valid = valid; 34 | this.errors = errors; 35 | } 36 | 37 | /** 38 | * Check if an expression has been validated successfully 39 | * @return true if the validation was successful, false otherwise 40 | */ 41 | public boolean isValid() { 42 | return valid; 43 | } 44 | 45 | /** 46 | * Get the list of errors describing the issues while validating the expression 47 | * @return The List of errors 48 | */ 49 | public List getErrors() { 50 | return errors; 51 | } 52 | 53 | /** 54 | * A static class representing a successful validation result 55 | */ 56 | public static final ValidationResult SUCCESS = new ValidationResult(true, null); 57 | } 58 | -------------------------------------------------------------------------------- /src/net/objecthunter/exp4j/function/Function.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Frank Asseg 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.objecthunter.exp4j.function; 17 | 18 | /** 19 | * A class representing a Function which can be used in an expression 20 | */ 21 | public abstract class Function { 22 | 23 | protected final String name; 24 | 25 | protected final int numArguments; 26 | 27 | /** 28 | * Create a new Function with a given name and number of arguments 29 | * @param name the name of the Function 30 | * @param numArguments the number of arguments the function takes 31 | */ 32 | public Function(String name, int numArguments) { 33 | if (numArguments < 0) { 34 | throw new IllegalArgumentException("The number of function arguments can not be less than 0 for '" + 35 | name + "'"); 36 | } 37 | if (!isValidFunctionName(name)) { 38 | throw new IllegalArgumentException("The function name '" + name +"' is invalid"); 39 | } 40 | this.name = name; 41 | this.numArguments = numArguments; 42 | 43 | } 44 | 45 | 46 | /** 47 | * Create a new Function with a given name that takes a single argument 48 | * @param name the name of the Function 49 | */ 50 | public Function(String name) { 51 | this(name, 1); 52 | } 53 | 54 | /** 55 | * Get the name of the Function 56 | * @return the name 57 | */ 58 | public String getName() { 59 | return name; 60 | } 61 | 62 | /** 63 | * Get the number of arguments for this function 64 | * @return the number of arguments 65 | */ 66 | public int getNumArguments() { 67 | return numArguments; 68 | } 69 | 70 | /** 71 | * Method that does the actual calculation of the function value given the arguments 72 | * @param args the set of arguments used for calculating the function 73 | * @return the result of the function evaluation 74 | */ 75 | public abstract double apply(double... args); 76 | 77 | /** 78 | * Get the set of characters which are allowed for use in Function names. 79 | * @return the set of characters allowed 80 | */ 81 | public static char[] getAllowedFunctionCharacters() { 82 | char[] chars = new char[53]; 83 | int count = 0; 84 | for (int i = 65; i < 91; i++) { 85 | chars[count++] = (char) i; 86 | } 87 | for (int i = 97; i < 123; i++) { 88 | chars[count++] = (char) i; 89 | } 90 | chars[count] = '_'; 91 | return chars; 92 | } 93 | 94 | public static boolean isValidFunctionName(final String name) { 95 | if (name == null) { 96 | return false; 97 | } 98 | 99 | final int size = name.length(); 100 | 101 | if (size == 0) { 102 | return false; 103 | } 104 | 105 | for (int i=0;i< size;i++) { 106 | final char c = name.charAt(i); 107 | if (Character.isLetter(c) || c == '_') { 108 | continue; 109 | } else if (Character.isDigit(c) && i > 0) { 110 | continue; 111 | } 112 | return false; 113 | } 114 | return true; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/net/objecthunter/exp4j/function/Functions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Frank Asseg 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.objecthunter.exp4j.function; 17 | 18 | /** 19 | * Class representing the builtin functions available for use in expressions 20 | */ 21 | public class Functions { 22 | private static final int INDEX_SIN = 0; 23 | private static final int INDEX_COS = 1; 24 | private static final int INDEX_TAN = 2; 25 | private static final int INDEX_LOG = 3; 26 | private static final int INDEX_LOG1P = 4; 27 | private static final int INDEX_ABS = 5; 28 | private static final int INDEX_ACOS = 6; 29 | private static final int INDEX_ASIN = 7; 30 | private static final int INDEX_ATAN = 8; 31 | private static final int INDEX_CBRT = 9; 32 | private static final int INDEX_CEIL = 10; 33 | private static final int INDEX_FLOOR = 11; 34 | private static final int INDEX_SINH = 12; 35 | private static final int INDEX_SQRT = 13; 36 | private static final int INDEX_TANH = 14; 37 | private static final int INDEX_COSH = 15; 38 | private static final int INDEX_POW = 16; 39 | private static final int INDEX_EXP = 17; 40 | private static final int INDEX_EXPM1 = 18; 41 | private static final int INDEX_LOG10 = 19; 42 | private static final int INDEX_LOG2 = 20; 43 | 44 | private static final Function[] builtinFunctions = new Function[21]; 45 | 46 | static { 47 | builtinFunctions[INDEX_SIN] = new Function("sin") { 48 | @Override 49 | public double apply(double... args) { 50 | return Math.sin(args[0]); 51 | } 52 | }; 53 | builtinFunctions[INDEX_COS] = new Function("cos") { 54 | @Override 55 | public double apply(double... args) { 56 | return Math.cos(args[0]); 57 | } 58 | }; 59 | builtinFunctions[INDEX_TAN] = new Function("tan") { 60 | @Override 61 | public double apply(double... args) { 62 | return Math.tan(args[0]); 63 | } 64 | }; 65 | builtinFunctions[INDEX_LOG] = new Function("log") { 66 | @Override 67 | public double apply(double... args) { 68 | return Math.log(args[0]); 69 | } 70 | }; 71 | builtinFunctions[INDEX_LOG2] = new Function("log2") { 72 | @Override 73 | public double apply(double... args) { 74 | return Math.log(args[0]) / Math.log(2d); 75 | } 76 | }; 77 | builtinFunctions[INDEX_LOG10] = new Function("log10") { 78 | @Override 79 | public double apply(double... args) { 80 | return Math.log10(args[0]); 81 | } 82 | }; 83 | builtinFunctions[INDEX_LOG1P] = new Function("log1p") { 84 | @Override 85 | public double apply(double... args) { 86 | return Math.log1p(args[0]); 87 | } 88 | }; 89 | builtinFunctions[INDEX_ABS] = new Function("abs") { 90 | @Override 91 | public double apply(double... args) { 92 | return Math.abs(args[0]); 93 | } 94 | }; 95 | builtinFunctions[INDEX_ACOS] = new Function("acos") { 96 | @Override 97 | public double apply(double... args) { 98 | return Math.acos(args[0]); 99 | } 100 | }; 101 | builtinFunctions[INDEX_ASIN] = new Function("asin") { 102 | @Override 103 | public double apply(double... args) { 104 | return Math.asin(args[0]); 105 | } 106 | }; 107 | builtinFunctions[INDEX_ATAN] = new Function("atan") { 108 | @Override 109 | public double apply(double... args) { 110 | return Math.atan(args[0]); 111 | } 112 | }; 113 | builtinFunctions[INDEX_CBRT] = new Function("cbrt") { 114 | @Override 115 | public double apply(double... args) { 116 | return Math.cbrt(args[0]); 117 | } 118 | }; 119 | builtinFunctions[INDEX_FLOOR] = new Function("floor") { 120 | @Override 121 | public double apply(double... args) { 122 | return Math.floor(args[0]); 123 | } 124 | }; 125 | builtinFunctions[INDEX_SINH] = new Function("sinh") { 126 | @Override 127 | public double apply(double... args) { 128 | return Math.sinh(args[0]); 129 | } 130 | }; 131 | builtinFunctions[INDEX_SQRT] = new Function("sqrt") { 132 | @Override 133 | public double apply(double... args) { 134 | return Math.sqrt(args[0]); 135 | } 136 | }; 137 | builtinFunctions[INDEX_TANH] = new Function("tanh") { 138 | @Override 139 | public double apply(double... args) { 140 | return Math.tanh(args[0]); 141 | } 142 | }; 143 | builtinFunctions[INDEX_COSH] = new Function("cosh") { 144 | @Override 145 | public double apply(double... args) { 146 | return Math.cosh(args[0]); 147 | } 148 | }; 149 | builtinFunctions[INDEX_CEIL] = new Function("ceil") { 150 | @Override 151 | public double apply(double... args) { 152 | return Math.ceil(args[0]); 153 | } 154 | }; 155 | builtinFunctions[INDEX_POW] = new Function("pow", 2) { 156 | @Override 157 | public double apply(double... args) { 158 | return Math.pow(args[0], args[1]); 159 | } 160 | }; 161 | builtinFunctions[INDEX_EXP] = new Function("exp", 1) { 162 | @Override 163 | public double apply(double... args) { 164 | return Math.exp(args[0]); 165 | } 166 | }; 167 | builtinFunctions[INDEX_EXPM1] = new Function("expm1", 1) { 168 | @Override 169 | public double apply(double... args) { 170 | return Math.expm1(args[0]); 171 | } 172 | }; 173 | } 174 | 175 | /** 176 | * Get the builtin function for a given name 177 | * @param name te name of the function 178 | * @return a Function instance 179 | */ 180 | public static Function getBuiltinFunction(final String name) { 181 | 182 | if (name.equals("sin")) { 183 | return builtinFunctions[INDEX_SIN]; 184 | } else if (name.equals("cos")) { 185 | return builtinFunctions[INDEX_COS]; 186 | } else if (name.equals("tan")) { 187 | return builtinFunctions[INDEX_TAN]; 188 | } else if (name.equals("asin")) { 189 | return builtinFunctions[INDEX_ASIN]; 190 | } else if (name.equals("acos")) { 191 | return builtinFunctions[INDEX_ACOS]; 192 | } else if (name.equals("atan")) { 193 | return builtinFunctions[INDEX_ATAN]; 194 | } else if (name.equals("sinh")) { 195 | return builtinFunctions[INDEX_SINH]; 196 | } else if (name.equals("cosh")) { 197 | return builtinFunctions[INDEX_COSH]; 198 | } else if (name.equals("tanh")) { 199 | return builtinFunctions[INDEX_TANH]; 200 | } else if (name.equals("abs")) { 201 | return builtinFunctions[INDEX_ABS]; 202 | } else if (name.equals("log")) { 203 | return builtinFunctions[INDEX_LOG]; 204 | } else if (name.equals("log10")) { 205 | return builtinFunctions[INDEX_LOG10]; 206 | } else if (name.equals("log2")) { 207 | return builtinFunctions[INDEX_LOG2]; 208 | } else if (name.equals("log1p")) { 209 | return builtinFunctions[INDEX_LOG1P]; 210 | } else if (name.equals("ceil")) { 211 | return builtinFunctions[INDEX_CEIL]; 212 | } else if (name.equals("floor")) { 213 | return builtinFunctions[INDEX_FLOOR]; 214 | } else if (name.equals("sqrt")) { 215 | return builtinFunctions[INDEX_SQRT]; 216 | } else if (name.equals("cbrt")) { 217 | return builtinFunctions[INDEX_CBRT]; 218 | } else if (name.equals("pow")) { 219 | return builtinFunctions[INDEX_POW]; 220 | } else if (name.equals("exp")) { 221 | return builtinFunctions[INDEX_EXP]; 222 | } else if (name.equals("expm1")) { 223 | return builtinFunctions[INDEX_EXPM1]; 224 | } else { 225 | return null; 226 | } 227 | } 228 | 229 | } 230 | -------------------------------------------------------------------------------- /src/net/objecthunter/exp4j/operator/Operator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Frank Asseg 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.objecthunter.exp4j.operator; 17 | 18 | /** 19 | * Class representing operators that can be used in an expression 20 | */ 21 | public abstract class Operator { 22 | /** 23 | * The precedence value for the addition operation 24 | */ 25 | public static final int PRECEDENCE_ADDITION = 500; 26 | /** 27 | * The precedence value for the subtraction operation 28 | */ 29 | public static final int PRECEDENCE_SUBTRACTION = PRECEDENCE_ADDITION; 30 | /** 31 | * The precedence value for the multiplication operation 32 | */ 33 | public static final int PRECEDENCE_MULTIPLICATION = 1000; 34 | /** 35 | * The precedence value for the division operation 36 | */ 37 | public static final int PRECEDENCE_DIVISION = PRECEDENCE_MULTIPLICATION; 38 | /** 39 | * The precedence value for the modulo operation 40 | */ 41 | public static final int PRECEDENCE_MODULO = PRECEDENCE_DIVISION; 42 | /** 43 | * The precedence value for the power operation 44 | */ 45 | public static final int PRECEDENCE_POWER = 10000; 46 | /** 47 | * The precedence value for the unary minus operation 48 | */ 49 | public static final int PRECEDENCE_UNARY_MINUS = 5000; 50 | /** 51 | * The precedence value for the unary plus operation 52 | */ 53 | public static final int PRECEDENCE_UNARY_PLUS = PRECEDENCE_UNARY_MINUS; 54 | 55 | /** 56 | * The set of allowed operator chars 57 | */ 58 | public static final char[] ALLOWED_OPERATOR_CHARS = { '+', '-', '*', '/', 59 | '%', '^', '!', '#', '§', '$', '&', ';', ':', '~', '<', '>', '|', 60 | '='}; 61 | 62 | protected final int numOperands; 63 | protected final boolean leftAssociative; 64 | protected final String symbol; 65 | protected final int precedence; 66 | 67 | /** 68 | * Create a new operator for use in expressions 69 | * @param symbol the symbol of the operator 70 | * @param numberOfOperands the number of operands the operator takes (1 or 2) 71 | * @param leftAssociative set to true if the operator is left associative, false if it is right associative 72 | * @param precedence the precedence value of the operator 73 | */ 74 | public Operator(String symbol, int numberOfOperands, boolean leftAssociative, 75 | int precedence) { 76 | super(); 77 | this.numOperands = numberOfOperands; 78 | this.leftAssociative = leftAssociative; 79 | this.symbol = symbol; 80 | this.precedence = precedence; 81 | } 82 | 83 | /** 84 | * Check if a character is an allowed operator char 85 | * @param ch the char to check 86 | * @return true if the char is allowed an an operator symbol, false otherwise 87 | */ 88 | public static boolean isAllowedOperatorChar(char ch) { 89 | for (char allowed: ALLOWED_OPERATOR_CHARS) { 90 | if (ch == allowed) { 91 | return true; 92 | } 93 | } 94 | return false; 95 | } 96 | 97 | /** 98 | * Check if the operator is left associative 99 | * @return true os the operator is left associative, false otherwise 100 | */ 101 | public boolean isLeftAssociative() { 102 | return leftAssociative; 103 | } 104 | 105 | /** 106 | * Check the precedence value for the operator 107 | * @return the precedence value 108 | */ 109 | public int getPrecedence() { 110 | return precedence; 111 | } 112 | 113 | /** 114 | * Apply the operation on the given operands 115 | * @param args the operands for the operation 116 | * @return the calculated result of the operation 117 | */ 118 | public abstract double apply(double ... args); 119 | 120 | /** 121 | * Get the operator symbol 122 | * @return the symbol 123 | */ 124 | public String getSymbol() { 125 | return symbol; 126 | } 127 | 128 | /** 129 | * Get the number of operands 130 | * @return the number of operands 131 | */ 132 | public int getNumOperands() { 133 | return numOperands; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/net/objecthunter/exp4j/operator/Operators.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Frank Asseg 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.objecthunter.exp4j.operator; 17 | 18 | public abstract class Operators { 19 | private static final int INDEX_ADDITION = 0; 20 | private static final int INDEX_SUBTRACTION = 1; 21 | private static final int INDEX_MUTLIPLICATION = 2; 22 | private static final int INDEX_DIVISION = 3; 23 | private static final int INDEX_POWER = 4; 24 | private static final int INDEX_MODULO = 5; 25 | private static final int INDEX_UNARYMINUS = 6; 26 | private static final int INDEX_UNARYPLUS = 7; 27 | 28 | private static final Operator[] builtinOperators = new Operator[8]; 29 | 30 | static { 31 | builtinOperators[INDEX_ADDITION]= new Operator("+", 2, true, Operator.PRECEDENCE_ADDITION) { 32 | @Override 33 | public double apply(final double... args) { 34 | return args[0] + args[1]; 35 | } 36 | }; 37 | builtinOperators[INDEX_SUBTRACTION]= new Operator("-", 2, true, Operator.PRECEDENCE_ADDITION) { 38 | @Override 39 | public double apply(final double... args) { 40 | return args[0] - args[1]; 41 | } 42 | }; 43 | builtinOperators[INDEX_UNARYMINUS]= new Operator("-", 1, false, Operator.PRECEDENCE_UNARY_MINUS) { 44 | @Override 45 | public double apply(final double... args) { 46 | return -args[0]; 47 | } 48 | }; 49 | builtinOperators[INDEX_UNARYPLUS]= new Operator("+", 1, false, Operator.PRECEDENCE_UNARY_PLUS) { 50 | @Override 51 | public double apply(final double... args) { 52 | return args[0]; 53 | } 54 | }; 55 | builtinOperators[INDEX_MUTLIPLICATION]= new Operator("*", 2, true, Operator.PRECEDENCE_MULTIPLICATION) { 56 | @Override 57 | public double apply(final double... args) { 58 | return args[0] * args[1]; 59 | } 60 | }; 61 | builtinOperators[INDEX_DIVISION]= new Operator("/", 2, true, Operator.PRECEDENCE_DIVISION) { 62 | @Override 63 | public double apply(final double... args) { 64 | if (args[1] == 0d) { 65 | throw new ArithmeticException("Division by zero!"); 66 | } 67 | return args[0] / args[1]; 68 | } 69 | }; 70 | builtinOperators[INDEX_POWER]= new Operator("^", 2, false, Operator.PRECEDENCE_POWER) { 71 | @Override 72 | public double apply(final double... args) { 73 | return Math.pow(args[0], args[1]); 74 | } 75 | }; 76 | builtinOperators[INDEX_MODULO]= new Operator("%", 2, true, Operator.PRECEDENCE_MODULO) { 77 | @Override 78 | public double apply(final double... args) { 79 | if (args[1] == 0d) { 80 | throw new ArithmeticException("Division by zero!"); 81 | } 82 | return args[0] % args[1]; 83 | } 84 | }; 85 | } 86 | 87 | public static Operator getBuiltinOperator(final char symbol, final int numArguments) { 88 | switch(symbol) { 89 | case '+': 90 | if (numArguments != 1) { 91 | return builtinOperators[INDEX_ADDITION]; 92 | }else{ 93 | return builtinOperators[INDEX_UNARYPLUS]; 94 | } 95 | case '-': 96 | if (numArguments != 1) { 97 | return builtinOperators[INDEX_SUBTRACTION]; 98 | }else{ 99 | return builtinOperators[INDEX_UNARYMINUS]; 100 | } 101 | case '*': 102 | return builtinOperators[INDEX_MUTLIPLICATION]; 103 | case '/': 104 | return builtinOperators[INDEX_DIVISION]; 105 | case '^': 106 | return builtinOperators[INDEX_POWER]; 107 | case '%': 108 | return builtinOperators[INDEX_MODULO]; 109 | default: 110 | return null; 111 | } 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /src/net/objecthunter/exp4j/shuntingyard/ShuntingYard.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Frank Asseg 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.objecthunter.exp4j.shuntingyard; 17 | 18 | import java.util.*; 19 | 20 | import net.objecthunter.exp4j.function.Function; 21 | import net.objecthunter.exp4j.operator.Operator; 22 | import net.objecthunter.exp4j.tokenizer.OperatorToken; 23 | import net.objecthunter.exp4j.tokenizer.Token; 24 | import net.objecthunter.exp4j.tokenizer.Tokenizer; 25 | 26 | /** 27 | * Shunting yard implementation to convert infix to reverse polish notation 28 | */ 29 | public class ShuntingYard { 30 | 31 | /** 32 | * Convert a Set of tokens from infix to reverse polish notation 33 | * @param expression the expression to convert 34 | * @param userFunctions the custom functions used 35 | * @param userOperators the custom operators used 36 | * @param variableNames the variable names used in the expression 37 | * @return a {@link net.objecthunter.exp4j.tokenizer.Token} array containing the result 38 | */ 39 | public static Token[] convertToRPN(final String expression, final Map userFunctions, 40 | final Map userOperators, final Set variableNames){ 41 | final Stack stack = new Stack(); 42 | final List output = new ArrayList(); 43 | 44 | final Tokenizer tokenizer = new Tokenizer(expression, userFunctions, userOperators, variableNames); 45 | while (tokenizer.hasNext()) { 46 | Token token = tokenizer.nextToken(); 47 | switch (token.getType()) { 48 | case Token.TOKEN_NUMBER: 49 | case Token.TOKEN_VARIABLE: 50 | output.add(token); 51 | break; 52 | case Token.TOKEN_FUNCTION: 53 | stack.add(token); 54 | break; 55 | case Token.TOKEN_SEPARATOR: 56 | while (!stack.empty() && stack.peek().getType() != Token.TOKEN_PARENTHESES_OPEN) { 57 | output.add(stack.pop()); 58 | } 59 | if (stack.empty() || stack.peek().getType() != Token.TOKEN_PARENTHESES_OPEN) { 60 | throw new IllegalArgumentException("Misplaced function separator ',' or mismatched parentheses"); 61 | } 62 | break; 63 | case Token.TOKEN_OPERATOR: 64 | while (!stack.empty() && stack.peek().getType() == Token.TOKEN_OPERATOR) { 65 | OperatorToken o1 = (OperatorToken) token; 66 | OperatorToken o2 = (OperatorToken) stack.peek(); 67 | if (o1.getOperator().getNumOperands() == 1 && o2.getOperator().getNumOperands() == 2) { 68 | break; 69 | } else if ((o1.getOperator().isLeftAssociative() && o1.getOperator().getPrecedence() <= o2.getOperator().getPrecedence()) 70 | || (o1.getOperator().getPrecedence() < o2.getOperator().getPrecedence())) { 71 | output.add(stack.pop()); 72 | }else { 73 | break; 74 | } 75 | } 76 | stack.push(token); 77 | break; 78 | case Token.TOKEN_PARENTHESES_OPEN: 79 | stack.push(token); 80 | break; 81 | case Token.TOKEN_PARENTHESES_CLOSE: 82 | while (stack.peek().getType() != Token.TOKEN_PARENTHESES_OPEN) { 83 | output.add(stack.pop()); 84 | } 85 | stack.pop(); 86 | if (!stack.isEmpty() && stack.peek().getType() == Token.TOKEN_FUNCTION) { 87 | output.add(stack.pop()); 88 | } 89 | break; 90 | default: 91 | throw new IllegalArgumentException("Unknown Token type encountered. This should not happen"); 92 | } 93 | } 94 | while (!stack.empty()) { 95 | Token t = stack.pop(); 96 | if (t.getType() == Token.TOKEN_PARENTHESES_CLOSE || t.getType() == Token.TOKEN_PARENTHESES_OPEN) { 97 | throw new IllegalArgumentException("Mismatched parentheses detected. Please check the expression"); 98 | } else { 99 | output.add(t); 100 | } 101 | } 102 | return (Token[]) output.toArray(new Token[output.size()]); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/net/objecthunter/exp4j/tokenizer/ArgumentSeparatorToken.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Frank Asseg 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.objecthunter.exp4j.tokenizer; 17 | 18 | /** 19 | * Represents an argument separator in functions i.e: ',' 20 | */ 21 | class ArgumentSeparatorToken extends Token{ 22 | /** 23 | * Create a new instance 24 | */ 25 | ArgumentSeparatorToken() { 26 | super(Token.TOKEN_SEPARATOR); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/net/objecthunter/exp4j/tokenizer/CloseParenthesesToken.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Frank Asseg 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.objecthunter.exp4j.tokenizer; 17 | 18 | /** 19 | * represents closed parentheses 20 | */ 21 | class CloseParenthesesToken extends Token { 22 | 23 | /** 24 | * Creare a new instance 25 | */ 26 | CloseParenthesesToken() { 27 | super(Token.TOKEN_PARENTHESES_CLOSE); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/net/objecthunter/exp4j/tokenizer/FunctionToken.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Frank Asseg 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.objecthunter.exp4j.tokenizer; 17 | 18 | import net.objecthunter.exp4j.function.Function; 19 | 20 | public class FunctionToken extends Token{ 21 | private final Function function; 22 | public FunctionToken(final Function function) { 23 | super(Token.TOKEN_FUNCTION); 24 | this.function = function; 25 | } 26 | 27 | public Function getFunction() { 28 | return function; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/net/objecthunter/exp4j/tokenizer/NumberToken.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Frank Asseg 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.objecthunter.exp4j.tokenizer; 17 | 18 | /** 19 | * Represents a number in the expression 20 | */ 21 | public final class NumberToken extends Token { 22 | private final double value; 23 | 24 | /** 25 | * Create a new instance 26 | * @param value the value of the number 27 | */ 28 | public NumberToken(double value) { 29 | super(TOKEN_NUMBER); 30 | this.value = value; 31 | } 32 | 33 | NumberToken(final char[] expression, final int offset, final int len) { 34 | this(Double.parseDouble(String.valueOf(expression, offset, len))); 35 | } 36 | 37 | /** 38 | * Get the value of the number 39 | * @return the value 40 | */ 41 | public double getValue() { 42 | return value; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/net/objecthunter/exp4j/tokenizer/OpenParenthesesToken.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Frank Asseg 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.objecthunter.exp4j.tokenizer; 17 | 18 | class OpenParenthesesToken extends Token{ 19 | 20 | OpenParenthesesToken() { 21 | super(TOKEN_PARENTHESES_OPEN); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/net/objecthunter/exp4j/tokenizer/OperatorToken.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Frank Asseg 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.objecthunter.exp4j.tokenizer; 17 | 18 | import net.objecthunter.exp4j.operator.Operator; 19 | 20 | /** 21 | * Represents an operator used in expressions 22 | */ 23 | public class OperatorToken extends Token{ 24 | private final Operator operator; 25 | 26 | /** 27 | * Create a new instance 28 | * @param op the operator 29 | */ 30 | public OperatorToken(Operator op) { 31 | super(Token.TOKEN_OPERATOR); 32 | if (op == null) { 33 | throw new IllegalArgumentException("Operator is unknown for token."); 34 | } 35 | this.operator = op; 36 | } 37 | 38 | /** 39 | * Get the operator for that token 40 | * @return the operator 41 | */ 42 | public Operator getOperator() { 43 | return operator; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/net/objecthunter/exp4j/tokenizer/Token.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Frank Asseg 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.objecthunter.exp4j.tokenizer; 17 | 18 | /** 19 | * Abstract class for tokens used by exp4j to tokenize expressions 20 | */ 21 | public abstract class Token { 22 | public static final short TOKEN_NUMBER = 1; 23 | public static final short TOKEN_OPERATOR = 2; 24 | public static final short TOKEN_FUNCTION = 3; 25 | public static final short TOKEN_PARENTHESES_OPEN = 4; 26 | public static final short TOKEN_PARENTHESES_CLOSE = 5; 27 | public static final short TOKEN_VARIABLE = 6; 28 | public static final short TOKEN_SEPARATOR = 7; 29 | 30 | protected final int type; 31 | 32 | Token(int type) { 33 | this.type = type; 34 | } 35 | 36 | public int getType() { 37 | return type; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/net/objecthunter/exp4j/tokenizer/Tokenizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Frank Asseg 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* 18 | Includes change to allow variables with dot (e.g. "XXX.YYY") to use with FlightPlot fields. 19 | */ 20 | package net.objecthunter.exp4j.tokenizer; 21 | 22 | import java.util.Map; 23 | import java.util.Set; 24 | 25 | import net.objecthunter.exp4j.function.Function; 26 | import net.objecthunter.exp4j.function.Functions; 27 | import net.objecthunter.exp4j.operator.Operator; 28 | import net.objecthunter.exp4j.operator.Operators; 29 | 30 | public class Tokenizer { 31 | 32 | private final char[] expression; 33 | 34 | private final int expressionLength; 35 | 36 | private final Map userFunctions; 37 | 38 | private final Map userOperators; 39 | 40 | private final Set variableNames; 41 | 42 | private int pos = 0; 43 | 44 | private Token lastToken; 45 | 46 | public Tokenizer(String expression, final Map userFunctions, 47 | final Map userOperators, final Set variableNames) { 48 | this.expression = expression.trim().toCharArray(); 49 | this.expressionLength = this.expression.length; 50 | this.userFunctions = userFunctions; 51 | this.userOperators = userOperators; 52 | this.variableNames = variableNames; 53 | } 54 | 55 | public boolean hasNext() { 56 | return this.expression.length > pos; 57 | } 58 | 59 | public Token nextToken(){ 60 | char ch = expression[pos]; 61 | while (Character.isWhitespace(ch)) { 62 | ch = expression[++pos]; 63 | } 64 | if (Character.isDigit(ch) || ch == '.') { 65 | if (lastToken != null) { 66 | if (lastToken.getType() == Token.TOKEN_NUMBER) { 67 | throw new IllegalArgumentException("Unable to parse char '" + ch + "' (Code:" + (int) ch + ") at [" + pos + "]"); 68 | } else if ((lastToken.getType() != Token.TOKEN_OPERATOR 69 | && lastToken.getType() != Token.TOKEN_PARENTHESES_OPEN 70 | && lastToken.getType() != Token.TOKEN_FUNCTION 71 | && lastToken.getType() != Token.TOKEN_SEPARATOR)) { 72 | // insert an implicit multiplication token 73 | lastToken = new OperatorToken(Operators.getBuiltinOperator('*', 2)); 74 | return lastToken; 75 | } 76 | } 77 | return parseNumberToken(ch); 78 | } else if (isArgumentSeparator(ch)) { 79 | return parseArgumentSeparatorToken(ch); 80 | } else if (isOpenParentheses(ch)) { 81 | if (lastToken != null && 82 | (lastToken.getType() != Token.TOKEN_OPERATOR 83 | && lastToken.getType() != Token.TOKEN_PARENTHESES_OPEN 84 | && lastToken.getType() != Token.TOKEN_FUNCTION 85 | && lastToken.getType() != Token.TOKEN_SEPARATOR)) { 86 | // insert an implicit multiplication token 87 | lastToken = new OperatorToken(Operators.getBuiltinOperator('*', 2)); 88 | return lastToken; 89 | } 90 | return parseParentheses(true); 91 | } else if (isCloseParentheses(ch)) { 92 | return parseParentheses(false); 93 | } else if (Operator.isAllowedOperatorChar(ch)) { 94 | return parseOperatorToken(ch); 95 | } else if (isAlphabetic(ch) || ch == '_') { 96 | // parse the name which can be a setVariable or a function 97 | if (lastToken != null && 98 | (lastToken.getType() != Token.TOKEN_OPERATOR 99 | && lastToken.getType() != Token.TOKEN_PARENTHESES_OPEN 100 | && lastToken.getType() != Token.TOKEN_FUNCTION 101 | && lastToken.getType() != Token.TOKEN_SEPARATOR)) { 102 | // insert an implicit multiplication token 103 | lastToken = new OperatorToken(Operators.getBuiltinOperator('*', 2)); 104 | return lastToken; 105 | } 106 | return parseFunctionOrVariable(); 107 | 108 | } 109 | throw new IllegalArgumentException("Unable to parse char '" + ch + "' (Code:" + (int) ch + ") at [" + pos + "]"); 110 | } 111 | 112 | private Token parseArgumentSeparatorToken(char ch) { 113 | this.pos++; 114 | this.lastToken = new ArgumentSeparatorToken(); 115 | return lastToken; 116 | } 117 | 118 | private boolean isArgumentSeparator(char ch) { 119 | return ch == ','; 120 | } 121 | 122 | private Token parseParentheses(final boolean open) { 123 | if (open) { 124 | this.lastToken = new OpenParenthesesToken(); 125 | } else { 126 | this.lastToken = new CloseParenthesesToken(); 127 | } 128 | this.pos++; 129 | return lastToken; 130 | } 131 | 132 | private boolean isOpenParentheses(char ch) { 133 | return ch == '(' || ch == '{' || ch == '['; 134 | } 135 | 136 | private boolean isCloseParentheses(char ch) { 137 | return ch == ')' || ch == '}' || ch == ']'; 138 | } 139 | 140 | private Token parseFunctionOrVariable() { 141 | final int offset = this.pos; 142 | int lastValidLen = 1; 143 | Token lastValidToken = null; 144 | int len = 1; 145 | if (isEndOfExpression(offset)) { 146 | this.pos++; 147 | } 148 | while (!isEndOfExpression(offset + len - 1) && 149 | (isAlphabetic(expression[offset + len - 1]) || 150 | Character.isDigit(expression[offset + len - 1]) || 151 | expression[offset + len - 1] == '[' || 152 | expression[offset + len - 1] == ']' || 153 | expression[offset + len - 1] == '_' || 154 | expression[offset + len - 1] == '.')) { 155 | String name = new String(expression, offset, len); 156 | if (variableNames != null && variableNames.contains(name)) { 157 | lastValidLen = len; 158 | lastValidToken = new VariableToken(name); 159 | } else { 160 | final Function f = getFunction(name); 161 | if (f != null) { 162 | lastValidLen = len; 163 | lastValidToken = new FunctionToken(f); 164 | } 165 | } 166 | len++; 167 | } 168 | if (lastValidToken == null) { 169 | throw new IllegalArgumentException("Unable to parse setVariable or function starting at pos " + pos + " in expression '" + new String(expression) + "'"); 170 | } 171 | pos += lastValidLen; 172 | lastToken = lastValidToken; 173 | return lastToken; 174 | } 175 | 176 | private Function getFunction(String name) { 177 | Function f = null; 178 | if (this.userFunctions != null) { 179 | f = this.userFunctions.get(name); 180 | } 181 | if (f == null) { 182 | f = Functions.getBuiltinFunction(name); 183 | } 184 | return f; 185 | } 186 | 187 | private Token parseOperatorToken(char firstChar) { 188 | final int offset = this.pos; 189 | int len = 1; 190 | final StringBuilder symbol = new StringBuilder(); 191 | Operator lastValid = null; 192 | symbol.append(firstChar); 193 | 194 | while (!isEndOfExpression(offset + len) && Operator.isAllowedOperatorChar(expression[offset + len])) { 195 | symbol.append(expression[offset + len++]); 196 | } 197 | 198 | while (symbol.length() > 0) { 199 | Operator op = this.getOperator(symbol.toString()); 200 | if (op == null) { 201 | symbol.setLength(symbol.length() - 1); 202 | }else{ 203 | lastValid = op; 204 | break; 205 | } 206 | } 207 | 208 | pos += symbol.length(); 209 | lastToken = new OperatorToken(lastValid); 210 | return lastToken; 211 | } 212 | 213 | private Operator getOperator(String symbol) { 214 | Operator op = null; 215 | if (this.userOperators != null) { 216 | op = this.userOperators.get(symbol); 217 | } 218 | if (op == null && symbol.length() == 1) { 219 | final int argc = 220 | (lastToken == null || 221 | lastToken.getType() == Token.TOKEN_OPERATOR || 222 | lastToken.getType() == Token.TOKEN_PARENTHESES_OPEN || 223 | lastToken.getType() == Token.TOKEN_SEPARATOR) 224 | ? 1 : 2; 225 | op = Operators.getBuiltinOperator(symbol.charAt(0), argc); 226 | } 227 | return op; 228 | } 229 | 230 | private Token parseNumberToken(final char firstChar) { 231 | final int offset = this.pos; 232 | int len = 1; 233 | this.pos++; 234 | if (isEndOfExpression(offset + len)) { 235 | lastToken = new NumberToken(Double.parseDouble(String.valueOf(firstChar))); 236 | return lastToken; 237 | } 238 | while (!isEndOfExpression(offset + len) && 239 | isNumeric(expression[offset + len], expression[offset + len - 1] == 'e' || 240 | expression[offset + len - 1] == 'E')) { 241 | len++; 242 | this.pos++; 243 | } 244 | // check if the e is at the end 245 | if (expression[offset + len - 1] == 'e' || expression[offset + len - 1] == 'E') { 246 | // since the e is at the end it's not part of the number and a rollback is necessary 247 | len--; 248 | pos--; 249 | } 250 | lastToken = new NumberToken(expression, offset, len); 251 | return lastToken; 252 | } 253 | 254 | private static boolean isNumeric(char ch, boolean lastCharE) { 255 | return Character.isDigit(ch) || ch == '.' || ch == 'e' || ch == 'E' || 256 | (lastCharE && (ch == '-' || ch == '+')); 257 | } 258 | 259 | public static boolean isAlphabetic(int codePoint) { 260 | return Character.isLetter(codePoint); 261 | } 262 | 263 | private boolean isEndOfExpression(int offset) { 264 | return this.expressionLength <= offset; 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /src/net/objecthunter/exp4j/tokenizer/VariableToken.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Frank Asseg 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.objecthunter.exp4j.tokenizer; 17 | 18 | /** 19 | * represents a setVariable used in an expression 20 | */ 21 | public class VariableToken extends Token { 22 | private final String name; 23 | 24 | /** 25 | * Get the name of the setVariable 26 | * @return the name 27 | */ 28 | public String getName() { 29 | return name; 30 | } 31 | 32 | /** 33 | * Create a new instance 34 | * @param name the name of the setVariable 35 | */ 36 | public VariableToken(String name) { 37 | super(TOKEN_VARIABLE); 38 | this.name = name; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/org/json/JSONException.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /** 4 | * The JSONException is thrown by the JSON.org classes when things are amiss. 5 | * 6 | * @author JSON.org 7 | * @version 2013-02-10 8 | */ 9 | public class JSONException extends RuntimeException { 10 | private static final long serialVersionUID = 0; 11 | private Throwable cause; 12 | 13 | /** 14 | * Constructs a JSONException with an explanatory message. 15 | * 16 | * @param message 17 | * Detail about the reason for the exception. 18 | */ 19 | public JSONException(String message) { 20 | super(message); 21 | } 22 | 23 | /** 24 | * Constructs a new JSONException with the specified cause. 25 | */ 26 | public JSONException(Throwable cause) { 27 | super(cause.getMessage()); 28 | this.cause = cause; 29 | } 30 | 31 | /** 32 | * Returns the cause of this exception or null if the cause is nonexistent 33 | * or unknown. 34 | * 35 | * @returns the cause of this exception or null if the cause is nonexistent 36 | * or unknown. 37 | */ 38 | public Throwable getCause() { 39 | return this.cause; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/org/json/JSONString.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | /** 3 | * The JSONString interface allows a toJSONString() 4 | * method so that a class can change the behavior of 5 | * JSONObject.toString(), JSONArray.toString(), 6 | * and JSONWriter.value(Object). The 7 | * toJSONString method will be used instead of the default behavior 8 | * of using the Object's toString() method and quoting the result. 9 | */ 10 | public interface JSONString { 11 | /** 12 | * The toJSONString method allows a class to produce its own JSON 13 | * serialization. 14 | * 15 | * @return A strictly syntactically correct JSON text. 16 | */ 17 | public String toJSONString(); 18 | } 19 | --------------------------------------------------------------------------------