From 9441ed331af3aa6b3ef45bf165e40faad18bc7fd Mon Sep 17 00:00:00 2001 From: Ben Burwell Date: Sat, 19 Nov 2016 17:39:05 -0500 Subject: Use gradle --- .gitignore | 2 + build.gradle | 35 ++++ src/com/benburwell/planes/data/Aircraft.java | 90 --------- src/com/benburwell/planes/data/AircraftStore.java | 34 ---- .../planes/data/AircraftStoreListener.java | 9 - src/com/benburwell/planes/data/Position.java | 46 ----- .../planes/gui/AircraftTableComponent.java | 26 --- .../benburwell/planes/gui/AircraftTableModel.java | 85 -------- src/com/benburwell/planes/gui/GraphicsTheme.java | 27 --- src/com/benburwell/planes/gui/Main1090.java | 93 --------- src/com/benburwell/planes/gui/MenuBarProvider.java | 41 ---- .../planes/gui/TCPConnectionOptionDialog.java | 45 ----- src/com/benburwell/planes/gui/ViewComponent.java | 10 - .../planes/gui/aircraftmap/AircraftMap.java | 174 ----------------- .../gui/aircraftmap/AircraftMapComponent.java | 106 ---------- .../planes/gui/aircraftmap/Drawable.java | 10 - .../planes/gui/aircraftmap/GeoPoint.java | 36 ---- .../benburwell/planes/gui/aircraftmap/Plane.java | 126 ------------ .../benburwell/planes/sbs/AggregateDataSource.java | 48 ----- src/com/benburwell/planes/sbs/DataListener.java | 8 - src/com/benburwell/planes/sbs/DataSource.java | 10 - .../planes/sbs/MalformedPacketException.java | 15 -- src/com/benburwell/planes/sbs/MessageType.java | 31 --- src/com/benburwell/planes/sbs/SBSPacket.java | 216 --------------------- src/com/benburwell/planes/sbs/TCPDataSource.java | 99 ---------- .../benburwell/planes/sbs/TransmissionType.java | 34 ---- .../sbs/UnrecognizedMessageTypeException.java | 20 -- .../sbs/UnrecognizedTransmissionTypeException.java | 20 -- .../java/com/benburwell/planes/data/Aircraft.java | 90 +++++++++ .../com/benburwell/planes/data/AircraftStore.java | 34 ++++ .../planes/data/AircraftStoreListener.java | 9 + .../com/benburwell/planes/data/NavigationAid.java | 187 ++++++++++++++++++ .../java/com/benburwell/planes/data/Position.java | 46 +++++ .../planes/gui/AircraftTableComponent.java | 26 +++ .../benburwell/planes/gui/AircraftTableModel.java | 85 ++++++++ .../com/benburwell/planes/gui/GraphicsTheme.java | 27 +++ .../java/com/benburwell/planes/gui/Main1090.java | 93 +++++++++ .../com/benburwell/planes/gui/MenuBarProvider.java | 41 ++++ .../planes/gui/TCPConnectionOptionDialog.java | 45 +++++ .../com/benburwell/planes/gui/ViewComponent.java | 10 + .../planes/gui/aircraftmap/AircraftMap.java | 174 +++++++++++++++++ .../gui/aircraftmap/AircraftMapComponent.java | 106 ++++++++++ .../planes/gui/aircraftmap/Drawable.java | 10 + .../planes/gui/aircraftmap/GeoPoint.java | 36 ++++ .../benburwell/planes/gui/aircraftmap/Plane.java | 126 ++++++++++++ .../benburwell/planes/sbs/AggregateDataSource.java | 48 +++++ .../com/benburwell/planes/sbs/DataListener.java | 8 + .../java/com/benburwell/planes/sbs/DataSource.java | 10 + .../planes/sbs/MalformedPacketException.java | 15 ++ .../com/benburwell/planes/sbs/MessageType.java | 31 +++ .../java/com/benburwell/planes/sbs/SBSPacket.java | 216 +++++++++++++++++++++ .../com/benburwell/planes/sbs/TCPDataSource.java | 99 ++++++++++ .../benburwell/planes/sbs/TransmissionType.java | 34 ++++ .../sbs/UnrecognizedMessageTypeException.java | 20 ++ .../sbs/UnrecognizedTransmissionTypeException.java | 20 ++ 55 files changed, 1683 insertions(+), 1459 deletions(-) create mode 100644 build.gradle delete mode 100644 src/com/benburwell/planes/data/Aircraft.java delete mode 100644 src/com/benburwell/planes/data/AircraftStore.java delete mode 100644 src/com/benburwell/planes/data/AircraftStoreListener.java delete mode 100644 src/com/benburwell/planes/data/Position.java delete mode 100644 src/com/benburwell/planes/gui/AircraftTableComponent.java delete mode 100644 src/com/benburwell/planes/gui/AircraftTableModel.java delete mode 100644 src/com/benburwell/planes/gui/GraphicsTheme.java delete mode 100644 src/com/benburwell/planes/gui/Main1090.java delete mode 100644 src/com/benburwell/planes/gui/MenuBarProvider.java delete mode 100644 src/com/benburwell/planes/gui/TCPConnectionOptionDialog.java delete mode 100644 src/com/benburwell/planes/gui/ViewComponent.java delete mode 100644 src/com/benburwell/planes/gui/aircraftmap/AircraftMap.java delete mode 100644 src/com/benburwell/planes/gui/aircraftmap/AircraftMapComponent.java delete mode 100644 src/com/benburwell/planes/gui/aircraftmap/Drawable.java delete mode 100644 src/com/benburwell/planes/gui/aircraftmap/GeoPoint.java delete mode 100644 src/com/benburwell/planes/gui/aircraftmap/Plane.java delete mode 100644 src/com/benburwell/planes/sbs/AggregateDataSource.java delete mode 100644 src/com/benburwell/planes/sbs/DataListener.java delete mode 100644 src/com/benburwell/planes/sbs/DataSource.java delete mode 100644 src/com/benburwell/planes/sbs/MalformedPacketException.java delete mode 100644 src/com/benburwell/planes/sbs/MessageType.java delete mode 100644 src/com/benburwell/planes/sbs/SBSPacket.java delete mode 100644 src/com/benburwell/planes/sbs/TCPDataSource.java delete mode 100644 src/com/benburwell/planes/sbs/TransmissionType.java delete mode 100644 src/com/benburwell/planes/sbs/UnrecognizedMessageTypeException.java delete mode 100644 src/com/benburwell/planes/sbs/UnrecognizedTransmissionTypeException.java create mode 100644 src/main/java/com/benburwell/planes/data/Aircraft.java create mode 100644 src/main/java/com/benburwell/planes/data/AircraftStore.java create mode 100644 src/main/java/com/benburwell/planes/data/AircraftStoreListener.java create mode 100644 src/main/java/com/benburwell/planes/data/NavigationAid.java create mode 100644 src/main/java/com/benburwell/planes/data/Position.java create mode 100644 src/main/java/com/benburwell/planes/gui/AircraftTableComponent.java create mode 100644 src/main/java/com/benburwell/planes/gui/AircraftTableModel.java create mode 100644 src/main/java/com/benburwell/planes/gui/GraphicsTheme.java create mode 100644 src/main/java/com/benburwell/planes/gui/Main1090.java create mode 100644 src/main/java/com/benburwell/planes/gui/MenuBarProvider.java create mode 100644 src/main/java/com/benburwell/planes/gui/TCPConnectionOptionDialog.java create mode 100644 src/main/java/com/benburwell/planes/gui/ViewComponent.java create mode 100644 src/main/java/com/benburwell/planes/gui/aircraftmap/AircraftMap.java create mode 100644 src/main/java/com/benburwell/planes/gui/aircraftmap/AircraftMapComponent.java create mode 100644 src/main/java/com/benburwell/planes/gui/aircraftmap/Drawable.java create mode 100644 src/main/java/com/benburwell/planes/gui/aircraftmap/GeoPoint.java create mode 100644 src/main/java/com/benburwell/planes/gui/aircraftmap/Plane.java create mode 100644 src/main/java/com/benburwell/planes/sbs/AggregateDataSource.java create mode 100644 src/main/java/com/benburwell/planes/sbs/DataListener.java create mode 100644 src/main/java/com/benburwell/planes/sbs/DataSource.java create mode 100644 src/main/java/com/benburwell/planes/sbs/MalformedPacketException.java create mode 100644 src/main/java/com/benburwell/planes/sbs/MessageType.java create mode 100644 src/main/java/com/benburwell/planes/sbs/SBSPacket.java create mode 100644 src/main/java/com/benburwell/planes/sbs/TCPDataSource.java create mode 100644 src/main/java/com/benburwell/planes/sbs/TransmissionType.java create mode 100644 src/main/java/com/benburwell/planes/sbs/UnrecognizedMessageTypeException.java create mode 100644 src/main/java/com/benburwell/planes/sbs/UnrecognizedTransmissionTypeException.java diff --git a/.gitignore b/.gitignore index 023292b..d3a5d60 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ out/ planes.jar .idea/ 1090.iml +build/ +.gradle/ diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..ab2feff --- /dev/null +++ b/build.gradle @@ -0,0 +1,35 @@ +apply plugin: 'java' +apply plugin: 'application' + +compileJava.options.encoding = 'UTF-8' + + +mainClassName = 'com.benburwell.planes.gui.Main1090' + +// In this section you declare where to find the dependencies of your project +repositories { + // Use 'jcenter' for resolving your dependencies. + // You can declare any Maven/Ivy/file repository here. + jcenter() +} + +// In this section you declare the dependencies for your production and test code +dependencies { + // The production code uses the SLF4J logging API at compile time + //compile 'org.slf4j:slf4j-api:1.7.18' + + // Declare the dependency for your favourite test framework you want to use in your tests. + // TestNG is also supported by the Gradle Test task. Just change the + // testCompile dependency to testCompile 'org.testng:testng:6.8.1' and add + // 'test.useTestNG()' to your build script. + //testCompile 'junit:junit:4.12' +} + +jar { + manifest { + attributes( + 'Class-Path': configurations.compile.collect { it.getName() }.join(' '), + 'Main-Class': 'com.benburwell.planes.gui.Main1090' + ) + } +} diff --git a/src/com/benburwell/planes/data/Aircraft.java b/src/com/benburwell/planes/data/Aircraft.java deleted file mode 100644 index 66a7a46..0000000 --- a/src/com/benburwell/planes/data/Aircraft.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.benburwell.planes.data; - -import com.benburwell.planes.sbs.SBSPacket; - -import java.util.List; -import java.util.ArrayList; - -/** - * Created by ben on 11/15/16. - */ -public class Aircraft implements Comparable { - private final String hexIdent; - private Position currentPosition = new Position(); - private List positionHistory = new ArrayList<>(); - private String callsign = ""; - private String squawk = ""; - private long packetCount = 0; - private double track; - private double groundSpeed; - private double verticalRate; - - public Aircraft(String hexIdent) { - this.hexIdent = hexIdent; - } - - public void handleUpdate(SBSPacket packet) { - this.packetCount++; - if (packet.getAltitude() != null) { - this.currentPosition.setAltitude(packet.getAltitude()); - } - if (packet.getLatitude() != null) { - this.currentPosition.setLatitude(packet.getLatitude()); - } - if (packet.getLongitude() != null) { - this.currentPosition.setLongitude(packet.getLongitude()); - } - if (packet.getCallsign() != null && !packet.getCallsign().isEmpty()) { - this.callsign = packet.getCallsign(); - } - if (packet.getSquawk() != null && !packet.getSquawk().isEmpty()) { - this.callsign = packet.getSquawk(); - } - if (packet.getTrack() != null) { - this.track = packet.getTrack(); - } - if (packet.getGroundSpeed() != null) { - this.groundSpeed = packet.getGroundSpeed(); - } - if (packet.getVerticalRate() != null) { - this.verticalRate = packet.getVerticalRate(); - } - } - - public Position getCurrentPosition() { - return currentPosition; - } - - public String getCallsign() { - return callsign; - } - - public String getSquawk() { - return squawk; - } - - public Long getPacketCount() { - return packetCount; - } - - public String getHexIdent() { - return this.hexIdent; - } - - public double getTrack() { - return this.track; - } - - public double getGroundSpeed() { - return this.groundSpeed; - } - - public double getVerticalRate() { - return this.verticalRate; - } - - @Override - public int compareTo(Aircraft that) { - return this.getHexIdent().compareTo(that.getHexIdent()); - } -} diff --git a/src/com/benburwell/planes/data/AircraftStore.java b/src/com/benburwell/planes/data/AircraftStore.java deleted file mode 100644 index 076701d..0000000 --- a/src/com/benburwell/planes/data/AircraftStore.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.benburwell.planes.data; - -import com.benburwell.planes.sbs.SBSPacket; - -import java.util.HashMap; -import java.util.Map; -import java.util.List; -import java.util.ArrayList; - -/** - * Created by ben on 11/17/16. - */ -public class AircraftStore { - private Map aircraftMap = new HashMap<>(); - private List aircraftListeners = new ArrayList<>(); - - public Map getAircraft() { - return this.aircraftMap; - } - - public void addPacket(SBSPacket packet) { - if (!this.aircraftMap.containsKey(packet.getHexIdent())) { - this.aircraftMap.put(packet.getHexIdent(), new Aircraft(packet.getHexIdent())); - } - this.aircraftMap.get(packet.getHexIdent()).handleUpdate(packet); - this.aircraftListeners.stream() - .filter(listener -> listener.respondTo(packet.getHexIdent())) - .forEach(AircraftStoreListener::aircraftStoreChanged); - } - - public void subscribe(AircraftStoreListener listener) { - this.aircraftListeners.add(listener); - } -} diff --git a/src/com/benburwell/planes/data/AircraftStoreListener.java b/src/com/benburwell/planes/data/AircraftStoreListener.java deleted file mode 100644 index 2ef635f..0000000 --- a/src/com/benburwell/planes/data/AircraftStoreListener.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.benburwell.planes.data; - -/** - * Created by ben on 11/17/16. - */ -public interface AircraftStoreListener { - void aircraftStoreChanged(); - boolean respondTo(String aircraftId); -} diff --git a/src/com/benburwell/planes/data/Position.java b/src/com/benburwell/planes/data/Position.java deleted file mode 100644 index 4b37235..0000000 --- a/src/com/benburwell/planes/data/Position.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.benburwell.planes.data; - -import java.util.Date; - -/** - * Created by ben on 11/15/16. - */ -public class Position { - private Date timestamp = new Date(System.currentTimeMillis()); - private Double latitude = 0.0; - private Double longitude = 0.0; - - public Date getTimestamp() { - return timestamp; - } - - public void setTimestamp(Date timestamp) { - this.timestamp = timestamp; - } - - public double getLatitude() { - return latitude; - } - - public void setLatitude(double latitude) { - this.latitude = latitude; - } - - public double getLongitude() { - return longitude; - } - - public void setLongitude(double longitude) { - this.longitude = longitude; - } - - public double getAltitude() { - return altitude; - } - - public void setAltitude(double altitude) { - this.altitude = altitude; - } - - private double altitude = 0; -} diff --git a/src/com/benburwell/planes/gui/AircraftTableComponent.java b/src/com/benburwell/planes/gui/AircraftTableComponent.java deleted file mode 100644 index 221bdda..0000000 --- a/src/com/benburwell/planes/gui/AircraftTableComponent.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.benburwell.planes.gui; - -import com.benburwell.planes.data.AircraftStore; - -import javax.swing.*; - -/** - * Created by ben on 11/17/16. - */ -public class AircraftTableComponent implements ViewComponent{ - private JTable table; - private AircraftTableModel tableModel; - private JScrollPane scrollPane; - - public AircraftTableComponent(AircraftStore store) { - this.tableModel = new AircraftTableModel(store); - this.table = new JTable(this.tableModel); - this.table.setFillsViewportHeight(true); - this.scrollPane = new JScrollPane(table); - } - - @Override - public JComponent getComponent() { - return this.scrollPane; - } -} diff --git a/src/com/benburwell/planes/gui/AircraftTableModel.java b/src/com/benburwell/planes/gui/AircraftTableModel.java deleted file mode 100644 index 3931893..0000000 --- a/src/com/benburwell/planes/gui/AircraftTableModel.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.benburwell.planes.gui; - -import com.benburwell.planes.data.Aircraft; -import com.benburwell.planes.data.AircraftStore; -import com.benburwell.planes.data.AircraftStoreListener; - -import javax.swing.table.AbstractTableModel; -import java.util.Map; -import java.util.List; -import java.util.ArrayList; -import java.util.Collections; - -/** - * Created by ben on 11/15/16. - */ -public class AircraftTableModel extends AbstractTableModel { - private Map aircraftMap; - private String[] columnNames = { "Hex", "Callsign", "Squawk", "Latitude", "Longitude", "Altitude", "Vertical Rate", "Track", "Ground Speed", "Packets" }; - - public AircraftTableModel(AircraftStore store) { - this.aircraftMap = store.getAircraft(); - store.subscribe(new AircraftStoreListener() { - @Override - public void aircraftStoreChanged() { - AircraftTableModel.super.fireTableDataChanged(); - } - - @Override - public boolean respondTo(String aircraftId) { - // listen for all changes - return true; - } - }); - } - - @Override - public int getRowCount() { - return this.aircraftMap.keySet().size(); - } - - @Override - public int getColumnCount() { - return this.columnNames.length; - } - - @Override - public String getColumnName(int col) { - return this.columnNames[col]; - } - - @Override - public Object getValueAt(int rowIndex, int columnIndex) { - List aircraftList = this.getAircraftList(); - Aircraft aircraft = aircraftList.get(rowIndex); - switch (columnIndex) { - case 0: - return aircraft.getHexIdent(); - case 1: - return aircraft.getCallsign(); - case 2: - return aircraft.getSquawk(); - case 3: - return aircraft.getCurrentPosition().getLatitude(); - case 4: - return aircraft.getCurrentPosition().getLongitude(); - case 5: - return aircraft.getCurrentPosition().getAltitude(); - case 6: - return aircraft.getVerticalRate(); - case 7: - return aircraft.getTrack(); - case 8: - return aircraft.getGroundSpeed(); - case 9: - return aircraft.getPacketCount(); - } - return ""; - } - - private List getAircraftList() { - List aircraftList = new ArrayList<>(this.aircraftMap.values()); - Collections.sort(aircraftList); - return aircraftList; - } -} diff --git a/src/com/benburwell/planes/gui/GraphicsTheme.java b/src/com/benburwell/planes/gui/GraphicsTheme.java deleted file mode 100644 index cece138..0000000 --- a/src/com/benburwell/planes/gui/GraphicsTheme.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.benburwell.planes.gui; - -import java.awt.*; - -/** - * Created by ben on 11/18/16. - */ -public class GraphicsTheme { - public static class Colors { - public static final Color BASE_0 = new Color(12, 16, 20); - public static final Color BASE_1 = new Color(17, 21, 28); - public static final Color BASE_2 = new Color(9, 31, 46); - public static final Color BASE_3 = new Color(10, 55, 73); - public static final Color BASE_4 = new Color(36, 83, 97); - public static final Color BASE_5 = new Color(89, 156, 171); - public static final Color BASE_6 = new Color(153, 209, 206); - public static final Color BASE_7 = new Color(211, 235, 233); - public static final Color RED = new Color(194, 49, 39); - public static final Color ORANGE = new Color(210, 105, 55); - public static final Color YELLOW = new Color(237, 180, 67); - public static final Color MAGENTA = new Color(136, 140, 166); - public static final Color VIOLET = new Color(78, 81, 102); - public static final Color BLUE = new Color(25, 84, 102); - public static final Color CYAN = new Color(51, 133, 158); - public static final Color GREEN = new Color(42, 168, 137); - } -} diff --git a/src/com/benburwell/planes/gui/Main1090.java b/src/com/benburwell/planes/gui/Main1090.java deleted file mode 100644 index d7fc830..0000000 --- a/src/com/benburwell/planes/gui/Main1090.java +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Created by ben on 11/15/16. - */ - -package com.benburwell.planes.gui; - -import com.benburwell.planes.sbs.*; -import com.benburwell.planes.data.*; -import com.benburwell.planes.gui.aircraftmap.*; - -import java.awt.*; -import javax.swing.*; -import java.awt.event.ActionEvent; - -public class Main1090 extends JFrame { - private AggregateDataSource sbsDataSource = new AggregateDataSource(); - private AircraftStore aircraft = new AircraftStore(); - private int currentTcpConnection = 0; - private JTabbedPane tabbedPane = new JTabbedPane(); - - public Main1090() { - this.initUI(); - } - - private void initUI() { - this.createMenuBar(); - - this.setTitle("1090"); - this.setSize(600, 400); - this.setLocationRelativeTo(null); - this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); - - this.openDataSource(); - - this.createTabs(); - } - - private void createTabs() { - AircraftTableComponent aircraftData = new AircraftTableComponent(this.aircraft); - this.tabbedPane.addTab("Aircraft Data", aircraftData.getComponent()); - - AircraftMapComponent aircraftMap = new AircraftMapComponent(this.aircraft); - this.tabbedPane.addTab("Live Map", aircraftMap.getComponent()); - - this.add(this.tabbedPane); - this.tabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); - - this.tabbedPane.setSelectedIndex(1); - } - - private void createMenuBar() { - MenuBarProvider provider = new MenuBarProvider(); - this.setJMenuBar(provider.getMenuBar()); - - provider.getDataConnectItem().addActionListener((ActionEvent event) -> { - if (this.currentTcpConnection == 0) { - TCPConnectionOptionDialog dialog = new TCPConnectionOptionDialog(); - JOptionPane.showMessageDialog(this, dialog.getComponent(), "New Network Data Source", JOptionPane.PLAIN_MESSAGE); - this.currentTcpConnection = this.addTcpSource(dialog.getHost(), dialog.getPort()); - provider.getDataConnectItem().setEnabled(false); - provider.getDataDisconnectItem().setEnabled(true); - } - }); - provider.getDataDisconnectItem().addActionListener((ActionEvent event) -> { - if (this.currentTcpConnection != 0) { - this.sbsDataSource.closeSource(this.currentTcpConnection); - provider.getDataConnectItem().setEnabled(true); - provider.getDataDisconnectItem().setEnabled(false); - this.currentTcpConnection = 0; - } - }); - } - - private void openDataSource() { - this.sbsDataSource.subscribe((SBSPacket packet) -> { - this.aircraft.addPacket(packet); - }); - this.sbsDataSource.open(); - } - - private int addTcpSource(String host, int port) { - TCPDataSource source = new TCPDataSource(host, port); - source.open(); - return this.sbsDataSource.addSource(source); - } - - public static void main(String[] args) { - EventQueue.invokeLater(() -> { - Main1090 app = new Main1090(); - app.setVisible(true); - }); - } -} diff --git a/src/com/benburwell/planes/gui/MenuBarProvider.java b/src/com/benburwell/planes/gui/MenuBarProvider.java deleted file mode 100644 index add77ce..0000000 --- a/src/com/benburwell/planes/gui/MenuBarProvider.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.benburwell.planes.gui; - -import javax.swing.*; -import java.awt.event.ActionEvent; - -/** - * Created by ben on 11/17/16. - */ -public class MenuBarProvider { - private JMenuBar menubar = new JMenuBar(); - private JMenu file = new JMenu("1090"); - private JMenuItem fileQuitMenuItem = new JMenuItem("Quit"); - private JMenu data = new JMenu("Data Source"); - private JMenuItem dataConnectItem = new JMenuItem("Connect to Remote..."); - private JMenuItem dataDisconnectItem = new JMenuItem("Disconnect"); - - public MenuBarProvider() { - fileQuitMenuItem.addActionListener((ActionEvent event) -> { - System.exit(0); - }); - file.add(fileQuitMenuItem); - menubar.add(file); - - dataDisconnectItem.setEnabled(false); - data.add(dataConnectItem); - data.add(dataDisconnectItem); - menubar.add(data); - } - - public JMenuBar getMenuBar() { - return this.menubar; - } - - public JMenuItem getDataConnectItem() { - return this.dataConnectItem; - } - - public JMenuItem getDataDisconnectItem() { - return this.dataDisconnectItem; - } -} diff --git a/src/com/benburwell/planes/gui/TCPConnectionOptionDialog.java b/src/com/benburwell/planes/gui/TCPConnectionOptionDialog.java deleted file mode 100644 index 609a70a..0000000 --- a/src/com/benburwell/planes/gui/TCPConnectionOptionDialog.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.benburwell.planes.gui; - -import javax.swing.*; - -/** - * Created by ben on 11/17/16. - */ -public class TCPConnectionOptionDialog implements ViewComponent { - public static final String DEFAULT_HOSTNAME = "10.0.0.111"; - public static final int DEFAULT_TCP_PORT = 30003; - - private JPanel dialog = new JPanel(); - private JTextField hostField = new JTextField(10); - private JTextField portField = new JTextField(5); - private JLabel descriptionLabel = new JLabel("Add a network data source that provides data in the SBS-1 format"); - - @Override - public JComponent getComponent() { - // set properties - hostField.setText(DEFAULT_HOSTNAME); - hostField.setToolTipText("Hostname or IP address"); - - portField.setText(String.valueOf(DEFAULT_TCP_PORT)); - portField.setToolTipText("Port number"); - - // create layout - dialog.add(descriptionLabel); - dialog.add(hostField); - dialog.add(portField); - - return dialog; - } - - public String getHost() { - return this.hostField.getText(); - } - - public int getPort() { - try { - return Integer.valueOf(this.portField.getText()); - } catch (NumberFormatException e) { - return DEFAULT_TCP_PORT; - } - } -} diff --git a/src/com/benburwell/planes/gui/ViewComponent.java b/src/com/benburwell/planes/gui/ViewComponent.java deleted file mode 100644 index 91c21cd..0000000 --- a/src/com/benburwell/planes/gui/ViewComponent.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.benburwell.planes.gui; - -import javax.swing.*; - -/** - * Created by ben on 11/17/16. - */ -public interface ViewComponent { - JComponent getComponent(); -} diff --git a/src/com/benburwell/planes/gui/aircraftmap/AircraftMap.java b/src/com/benburwell/planes/gui/aircraftmap/AircraftMap.java deleted file mode 100644 index fad1082..0000000 --- a/src/com/benburwell/planes/gui/aircraftmap/AircraftMap.java +++ /dev/null @@ -1,174 +0,0 @@ -package com.benburwell.planes.gui.aircraftmap; - -import com.benburwell.planes.gui.GraphicsTheme; - -import javax.swing.*; -import java.awt.*; -import java.util.ArrayList; -import java.util.List; - -/** - * Created by ben on 11/19/16. - */ -public class AircraftMap extends JPanel { - // geographic constants - public final double MAX_LATITUDE = 90.0; - public final double MIN_LATITUDE = -90.0; - public final double MAX_LONGITUDE = 180.0; - public final double MIN_LONGITUDE = -180.0; - public final double NAUTICAL_MILES_PER_DEGREE_LATITUDE = 60.0; - - // drawing constants - public final float FONT_SIZE = 12; - public final int TEXT_PADDING = 5; - public final int NUMBER_OF_RANGE_RINGS = 5; - - // map manipulation constants - public final int MIN_ZOOM_PIXELS_PER_MILE = 1; - public final int MAX_ZOOM_PIXELS_PER_MILE = 2000; - public final double PAN_INTERVAL_MILES = 1.0; - - // instance fields - private List planes = new ArrayList<>(); - private double centerLatitude; - private double centerLongitude; - private int pixelsPerNauticalMile = 10; - - /** - * Construct a map - */ - public AircraftMap() { - super(); - this.setBackground(GraphicsTheme.Colors.BASE_1); - this.setBorder(BorderFactory.createEmptyBorder()); - this.setCenter(0, 0); - } - - /** - * Paint the ViewComponent on a Graphics instance - * - * @param g the graphics context to paint on - */ - @Override - public void paintComponent(Graphics g) { - super.paintComponent(g); - this.drawPositionAndScale(g); - this.drawRange(g); - this.planes.forEach(item -> item.drawOn(g, this)); - } - - public void drawPositionAndScale(Graphics g) { - Font currentFont = g.getFont(); - Font newFont = currentFont.deriveFont(FONT_SIZE); - g.setFont(newFont); - g.setColor(GraphicsTheme.Colors.BLUE); - g.drawString(String.format("%08.5f N", this.centerLatitude), TEXT_PADDING, (int) FONT_SIZE + TEXT_PADDING); - g.drawString(String.format("%08.5f E", this.centerLongitude), TEXT_PADDING, (int) FONT_SIZE * 2 + TEXT_PADDING); - g.drawString(String.format("%d nm", this.getRangeRadius()), TEXT_PADDING, (int) FONT_SIZE * 3 + TEXT_PADDING); - } - - public int getRangeRadius() { - double milesHigh = this.getHeight() / this.getPixelsPerNauticalMile(); - double milesWide = this.getWidth() / this.getPixelsPerNauticalMile(); - double screenMiles = Math.min(milesHigh, milesWide); - int milesPerRing = (int) screenMiles / NUMBER_OF_RANGE_RINGS; - return milesPerRing; - } - - public List getRangeRadii() { - int rangeRadius = this.getRangeRadius(); - List radii = new ArrayList<>(); - for (int ringNumber = 1; ringNumber <= NUMBER_OF_RANGE_RINGS; ringNumber++) { - radii.add(rangeRadius * ringNumber); - } - return radii; - } - - public void drawRange(Graphics g) { - int centerX = this.getWidth() / 2; - int centerY = this.getHeight() / 2; - g.setColor(GraphicsTheme.Colors.BASE_3); - for (Integer radius : this.getRangeRadii()) { - int pixelRadius = (int) (this.getPixelsPerNauticalMile() * radius); - g.drawOval(centerX - pixelRadius, centerY - pixelRadius, pixelRadius * 2, pixelRadius * 2); - } - g.drawLine(centerX, 0, centerX, this.getHeight()); - g.drawLine(0, centerY, this.getWidth(), centerY); - } - - public void setPlanes(List planes) { - this.planes = planes; - this.redraw(); - } - - public void redraw() { - this.invalidate(); - this.validate(); - this.repaint(); - } - - public void setCenter(double latitude, double longitude) { - this.centerLatitude = latitude; - this.centerLongitude = longitude; - this.redraw(); - } - - public double getCenterLatitude() { - return this.centerLatitude; - } - - public double getCenterLongitude() { - return this.centerLongitude; - } - - public double getPixelsPerDegreeLatitude() { - return this.pixelsPerNauticalMile * NAUTICAL_MILES_PER_DEGREE_LATITUDE; - } - - public double getPixelsPerDegreeLongitude() { - return this.pixelsPerNauticalMile * this.getNauticalMilesPerDegreeLongitude(); - } - - public double getNauticalMilesPerDegreeLongitude() { - double milesPerDegree = Math.abs(Math.cos(Math.toRadians(this.centerLatitude)) * NAUTICAL_MILES_PER_DEGREE_LATITUDE); - return milesPerDegree; - } - - public double getPixelsPerNauticalMile() { - return this.pixelsPerNauticalMile; - } - - public void zoomIn() { - this.pixelsPerNauticalMile = Math.min(MAX_ZOOM_PIXELS_PER_MILE, this.pixelsPerNauticalMile * 2); - this.redraw(); - } - - public void zoomOut() { - this.pixelsPerNauticalMile = Math.max(MIN_ZOOM_PIXELS_PER_MILE, this.pixelsPerNauticalMile / 2); - this.redraw(); - } - - public void moveEast() { - double degreesToMove = PAN_INTERVAL_MILES / this.getNauticalMilesPerDegreeLongitude(); - this.centerLongitude = Math.min(this.centerLongitude + degreesToMove, MAX_LONGITUDE); - this.redraw(); - } - - public void moveWest() { - double degreesToMove = PAN_INTERVAL_MILES / this.getNauticalMilesPerDegreeLongitude(); - this.centerLongitude = Math.max(this.centerLongitude - degreesToMove, MIN_LONGITUDE); - this.redraw(); - } - - public void moveNorth() { - double degreesToMove = PAN_INTERVAL_MILES / NAUTICAL_MILES_PER_DEGREE_LATITUDE; - this.centerLatitude = Math.min(this.centerLatitude + degreesToMove, MAX_LATITUDE); - this.redraw(); - } - - public void moveSouth() { - double degreesToMove = PAN_INTERVAL_MILES / NAUTICAL_MILES_PER_DEGREE_LATITUDE; - this.centerLatitude = Math.max(this.centerLatitude - degreesToMove, MIN_LATITUDE); - this.redraw(); - } -} diff --git a/src/com/benburwell/planes/gui/aircraftmap/AircraftMapComponent.java b/src/com/benburwell/planes/gui/aircraftmap/AircraftMapComponent.java deleted file mode 100644 index e490dae..0000000 --- a/src/com/benburwell/planes/gui/aircraftmap/AircraftMapComponent.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.benburwell.planes.gui.aircraftmap; - -import com.benburwell.planes.data.AircraftStore; -import com.benburwell.planes.data.AircraftStoreListener; -import com.benburwell.planes.data.Position; -import com.benburwell.planes.gui.ViewComponent; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.KeyEvent; -import java.util.List; -import java.util.ArrayList; -import java.util.Collections; - -/** - * Created by ben on 11/18/16. - */ -public class AircraftMapComponent implements ViewComponent { - private AircraftStore store; - private AircraftMap mapPanel; - private String focusedAircraftIdentifier = null; - - public AircraftMapComponent(AircraftStore store) { - this.store = store; - this.setupMap(); - this.bindKeys(); - this.subscribeToChanges(); - } - - public void focusNextAircraft() { - // List aircraftIdentifiers = new ArrayList<>(this.store.getAircraft().keySet()); - // Collections.sort(aircraftIdentifiers); - // if (this.focusedAircraftIdentifier == null && aircraftIdentifiers.size() > 0) { - // this.focusedAircraftIdentifier = aircraftIdentifiers.get(0); - // } else { - // int idx = aircraftIdentifiers.indexOf(this.focusedAircraftIdentifier); - // if (idx > 0 && idx < aircraftIdentifiers.size() - 1) { - // this.focusedAircraftIdentifier = aircraftIdentifiers.get(idx++); - // } else if (aircraftIdentifiers.size() > 0) { - // this.focusedAircraftIdentifier = aircraftIdentifiers.get(0); - // } else { - // this.focusedAircraftIdentifier = null; - // } - // } - } - - private void setupMap() { - this.mapPanel = new AircraftMap(); - this.mapPanel.setCenter(40.6188942, -75.4947205); - } - - private void bindKeys() { - KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(e -> { - if (e.getKeyCode() == KeyEvent.VK_EQUALS && e.isShiftDown() && e.getID() == KeyEvent.KEY_PRESSED) { - this.mapPanel.zoomIn(); - } else if (e.getKeyCode() == KeyEvent.VK_MINUS && e.getID() == KeyEvent.KEY_PRESSED) { - this.mapPanel.zoomOut(); - } else if (e.getKeyCode() == KeyEvent.VK_L && e.getID() == KeyEvent.KEY_PRESSED) { - this.mapPanel.moveEast(); - } else if (e.getKeyCode() == KeyEvent.VK_H && e.getID() == KeyEvent.KEY_PRESSED) { - this.mapPanel.moveWest(); - } else if (e.getKeyCode() == KeyEvent.VK_J && e.getID() == KeyEvent.KEY_PRESSED) { - this.mapPanel.moveSouth(); - } else if (e.getKeyCode() == KeyEvent.VK_K && e.getID() == KeyEvent.KEY_PRESSED) { - this.mapPanel.moveNorth(); - } else if (e.getKeyCode() == KeyEvent.VK_0 && e.getID() == KeyEvent.KEY_PRESSED) { - this.mapPanel.setCenter(40.6188942, -75.4947205); - } else if (e.getKeyCode() == KeyEvent.VK_TAB && e.getID() == KeyEvent.KEY_PRESSED) { - this.focusNextAircraft(); - this.centerMapOnPlane(this.focusedAircraftIdentifier); - } - return false; - }); - } - - private void centerMapOnPlane(String identifier) { - if (identifier != null) { - Position pos = this.store.getAircraft().get(identifier).getCurrentPosition(); - this.mapPanel.setCenter(pos.getLatitude(), pos.getLongitude()); - } - } - - private void subscribeToChanges() { - this.store.subscribe(new AircraftStoreListener() { - @Override - public void aircraftStoreChanged() { - List planes = new ArrayList<>(); - store.getAircraft().values().forEach(aircraft -> planes.add(new Plane(aircraft))); - mapPanel.setPlanes(planes); - mapPanel.validate(); - mapPanel.repaint(); - } - - @Override - public boolean respondTo(String aircraftId) { - return true; - } - }); - } - - @Override - public JComponent getComponent() { - return this.mapPanel; - } - -} diff --git a/src/com/benburwell/planes/gui/aircraftmap/Drawable.java b/src/com/benburwell/planes/gui/aircraftmap/Drawable.java deleted file mode 100644 index 01c16ba..0000000 --- a/src/com/benburwell/planes/gui/aircraftmap/Drawable.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.benburwell.planes.gui.aircraftmap; - -import java.awt.*; - -/** - * Created by ben on 11/19/16. - */ -public interface Drawable { - void drawOn(Graphics graphicsContext, AircraftMap map); -} diff --git a/src/com/benburwell/planes/gui/aircraftmap/GeoPoint.java b/src/com/benburwell/planes/gui/aircraftmap/GeoPoint.java deleted file mode 100644 index d3eda40..0000000 --- a/src/com/benburwell/planes/gui/aircraftmap/GeoPoint.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.benburwell.planes.gui.aircraftmap; - -/** - * Created by ben on 11/19/16. - */ -public class GeoPoint { - private double latitude; - private double longitude; - private double altitude; - - public GeoPoint(double latitude, double longitude, double altitude) { - this.latitude = latitude; - this.longitude = longitude; - this.altitude = altitude; - } - - public int getX(AircraftMap map) { - double degreesFromCenter = map.getCenterLongitude() - this.longitude; - double pixelsFromCenter = degreesFromCenter * map.getPixelsPerDegreeLongitude(); - double centerPixels = map.getSize().getWidth() / 2; - int xPosition = (int) (centerPixels - pixelsFromCenter); - return xPosition; - } - - public int getY(AircraftMap map) { - double degreesFromCenter = map.getCenterLatitude() - this.latitude; - double pixelsFromCenter = degreesFromCenter * map.getPixelsPerDegreeLatitude(); - double centerPixels = map.getSize().getHeight() / 2; - int yPosition = (int) (centerPixels + pixelsFromCenter); - return yPosition; - } - - public double getAltitude() { - return this.altitude; - } -} diff --git a/src/com/benburwell/planes/gui/aircraftmap/Plane.java b/src/com/benburwell/planes/gui/aircraftmap/Plane.java deleted file mode 100644 index 2f98bab..0000000 --- a/src/com/benburwell/planes/gui/aircraftmap/Plane.java +++ /dev/null @@ -1,126 +0,0 @@ -package com.benburwell.planes.gui.aircraftmap; - -import com.benburwell.planes.data.Aircraft; -import com.benburwell.planes.data.Position; -import com.benburwell.planes.gui.GraphicsTheme; - -import java.awt.*; -import java.awt.geom.AffineTransform; - -/** - * Created by ben on 11/19/16. - */ -public class Plane extends GeoPoint implements Drawable { - public final int TRIANGLE_HEIGHT = 6; - public final int TRIANGLE_WIDTH = 4; - public final int TEXT_OFFSET_X = 10; - public final int TEXT_OFFSET_Y = 15; - public final int MIN_COLOR_HEIGHT = 0; - public final int MAX_COLOR_HEIGHT = 50000; - private String name; - private double heading; - private double speed; - private double verticalRate; - - public Plane(Aircraft ac) { - this(ac.getCallsign(), ac.getCurrentPosition(), ac.getTrack(), ac.getGroundSpeed(), ac.getVerticalRate()); - } - - public Plane(String name, Position position, double heading, double speed, double verticalRate) { - this(name, position.getLatitude(), position.getLongitude(), position.getAltitude(), heading, speed, verticalRate); - } - - public Plane(String name, double latitude, double longitude, double altitude, double heading, double speed, double verticalRate) { - super(latitude, longitude, altitude); - this.name = name; - this.heading = heading; - this.speed = speed; - this.verticalRate = verticalRate; - } - - public int getFlightLevel() { - return (int) this.getAltitude() / 100; - } - - public Color getPlaneColor() { - Color minColor = GraphicsTheme.Colors.RED; - Color maxColor = GraphicsTheme.Colors.GREEN; - - float[] minHsb = Color.RGBtoHSB(minColor.getRed(), minColor.getGreen(), minColor.getBlue(), null); - float[] maxHsb = Color.RGBtoHSB(maxColor.getRed(), maxColor.getGreen(), maxColor.getBlue(), null); - - float minHue = minHsb[0]; - float maxHue = maxHsb[0]; - float minSat = minHsb[1]; - float maxSat = maxHsb[1]; - float minBright = minHsb[2]; - float maxBright = maxHsb[2]; - - double planePosition = (this.getAltitude() / (MAX_COLOR_HEIGHT - MIN_COLOR_HEIGHT)) + MIN_COLOR_HEIGHT; - float huePosition = (float) (planePosition * (maxHue - minHue) + minHue); - float satPosition = (float) (planePosition * (maxSat - minSat) + minSat); - float brightPosition = (float) (planePosition * (maxBright - minBright) + minBright); - - Color c = Color.getHSBColor(huePosition, satPosition, brightPosition); - return c; - } - - public double getAngle() { - return Math.toRadians(this.heading); - } - - public double getSpeed() { - return this.speed; - } - - public void drawTriangle(Graphics2D ctx, int x, int y, int predictionLength) { - AffineTransform at = new AffineTransform(); - at.setToRotation(this.getAngle(), x, y); - ctx.setTransform(at); - int[] xs = new int[]{ x - TRIANGLE_WIDTH, x, x + TRIANGLE_WIDTH, x - TRIANGLE_WIDTH }; - int[] ys = new int[]{ y + TRIANGLE_HEIGHT, y - TRIANGLE_HEIGHT, y + TRIANGLE_HEIGHT, y + TRIANGLE_HEIGHT }; - ctx.fillPolygon(xs, ys, 4); - ctx.drawLine(x, y, x, y - predictionLength); - } - - public String getVerticalRateIndicator() { - if (this.verticalRate > 0) { - return "\u2191"; // ↑ - } else if (this.verticalRate < 0) { - return "\u2193"; // ↓ - } - return ""; - } - - public String getDisplayName() { - if (this.name == null || this.name.isEmpty()) { - return "-----"; - } - return this.name; - } - - public int getPredictionLength(double pixelsPerNauticalMile) { - return (int) (this.speed / 60.0 * pixelsPerNauticalMile); - } - - public void drawOn(Graphics g, AircraftMap map) { - int x = this.getX(map); - int y = this.getY(map); - - if (x >= 0 && x <= map.getSize().getWidth() && y >= 0 && y <= map.getSize().getHeight()) { - // draw the plane dot - Graphics2D g2d = (Graphics2D) g.create(); - g2d.setColor(this.getPlaneColor()); - int predictedTrack = this.getPredictionLength(map.getPixelsPerNauticalMile()); - this.drawTriangle(g2d, x, y, predictedTrack); - g2d.dispose(); - - - // draw the name of the plane - g.setColor(GraphicsTheme.Colors.BASE_5); - g.drawString(this.getDisplayName(), x + TEXT_OFFSET_X, y + TEXT_OFFSET_Y); - String infoString = String.format("%d%s %.1f", this.getFlightLevel(), this.getVerticalRateIndicator(), this.getSpeed()); - g.drawString(infoString, x + TEXT_OFFSET_X, y + TEXT_OFFSET_Y + g.getFontMetrics().getHeight()); - } - } -} diff --git a/src/com/benburwell/planes/sbs/AggregateDataSource.java b/src/com/benburwell/planes/sbs/AggregateDataSource.java deleted file mode 100644 index 2850404..0000000 --- a/src/com/benburwell/planes/sbs/AggregateDataSource.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.benburwell.planes.sbs; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.HashMap; - -/** - * Created by ben on 11/15/16. - */ -public class AggregateDataSource implements DataSource { - private List subscribers = new ArrayList<>(); - private boolean isOpen = false; - private int nextSourceNumber = 1; - private Map sources = new HashMap<>(); - - public int addSource(DataSource source) { - int thisSourceNumber = this.nextSourceNumber++; - this.sources.put(thisSourceNumber, source); - source.subscribe((SBSPacket packet) -> { - if (isOpen) { - for (DataListener listener : subscribers) { - listener.handleMessage(packet); - } - } - }); - return thisSourceNumber; - } - - public void subscribe(DataListener listener) { - this.subscribers.add(listener); - } - - public void open() { - this.isOpen = true; - } - - public void close() { - this.isOpen = false; - } - - public void closeSource(int sourceNumber) { - if (this.sources.containsKey(sourceNumber)) { - this.sources.get(sourceNumber).close(); - this.sources.remove(sourceNumber); - } - } -} diff --git a/src/com/benburwell/planes/sbs/DataListener.java b/src/com/benburwell/planes/sbs/DataListener.java deleted file mode 100644 index b0da2ef..0000000 --- a/src/com/benburwell/planes/sbs/DataListener.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.benburwell.planes.sbs; - -/** - * Created by ben on 11/15/16. - */ -public interface DataListener { - void handleMessage(SBSPacket packet); -} diff --git a/src/com/benburwell/planes/sbs/DataSource.java b/src/com/benburwell/planes/sbs/DataSource.java deleted file mode 100644 index 961c6e2..0000000 --- a/src/com/benburwell/planes/sbs/DataSource.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.benburwell.planes.sbs; - -/** - * Created by ben on 11/15/16. - */ -public interface DataSource { - void subscribe(DataListener listener); - void open(); - void close(); -} diff --git a/src/com/benburwell/planes/sbs/MalformedPacketException.java b/src/com/benburwell/planes/sbs/MalformedPacketException.java deleted file mode 100644 index 6cbd1a3..0000000 --- a/src/com/benburwell/planes/sbs/MalformedPacketException.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.benburwell.planes.sbs; - -/** - * Created by ben on 11/15/16. - */ -public class MalformedPacketException extends Exception { - private String message; - public MalformedPacketException(String message) { - this.message = message; - } - - public String getMessage() { - return this.message; - } -} diff --git a/src/com/benburwell/planes/sbs/MessageType.java b/src/com/benburwell/planes/sbs/MessageType.java deleted file mode 100644 index a2ecc66..0000000 --- a/src/com/benburwell/planes/sbs/MessageType.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.benburwell.planes.sbs; - -/** - * Created by ben on 11/15/16. - */ -public enum MessageType { - SELECTION_CHANGE("SEL"), - NEW_ID("ID"), - NEW_AIRCRAFT("AIR"), - STATUS_CHANGE("STA"), - CLICK("CLK"), - TRANSMISSION("MSG"); - - private String code; - MessageType(String code) { - this.code = code; - } - - public String getCode() { - return this.code; - } - - public static MessageType parse(String messageType) throws UnrecognizedMessageTypeException { - for (MessageType type : MessageType.values()) { - if (type.getCode().equals(messageType)) { - return type; - } - } - throw new UnrecognizedMessageTypeException(messageType); - } -} diff --git a/src/com/benburwell/planes/sbs/SBSPacket.java b/src/com/benburwell/planes/sbs/SBSPacket.java deleted file mode 100644 index 5684803..0000000 --- a/src/com/benburwell/planes/sbs/SBSPacket.java +++ /dev/null @@ -1,216 +0,0 @@ -package com.benburwell.planes.sbs; - -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; - -/** - * Created by ben on 11/15/16. - */ -public class SBSPacket { - private MessageType messageType; - private TransmissionType transmissionType = null; - private String sessionId = null; - private String aircraftId = null; - private String hexIdent = null; - private String flightId = null; - private Date dateGenerated = null; - private Date dateLogged = null; - private String callsign = null; - private Double altitude = null; - private Double groundSpeed = null; - private Double track = null; - private Double latitude = null; - private Double longitude = null; - private Double verticalRate = null; - private String squawk = null; - private Boolean alert = null; - private Boolean emergency = null; - private Boolean spi = null; - private Boolean isOnGround = null; - - public SBSPacket(String packet) throws MalformedPacketException { - this.parse(packet); - } - - public void parse(String packet) throws MalformedPacketException { - String[] segments = packet.split(",", -1); - if (segments.length < 11) { - throw new MalformedPacketException("Packet must have at least 11 fields, but has only " + segments.length); - } - - // get the message type - try { - this.messageType = MessageType.parse(segments[0]); - } catch (UnrecognizedMessageTypeException e) { - throw new MalformedPacketException("Packet has an unrecognized message type: " + e.getType()); - } - - // get the transmission type - if (this.messageType.equals(MessageType.TRANSMISSION)) { - try { - this.transmissionType = TransmissionType.parse(segments[1]); - } catch (UnrecognizedTransmissionTypeException e) { - throw new MalformedPacketException("Packet has an unrecognized transmission type code: " + e.getCode()); - } - } - - this.sessionId = segments[2]; - this.aircraftId = segments[3]; - this.hexIdent = segments[4]; - this.flightId = segments[5]; - this.dateGenerated = this.parseDateAndTime(segments[6], segments[7]); - this.dateLogged = this.parseDateAndTime(segments[8], segments[9]); - this.callsign = segments[10]; - - if (this.messageType.equals(MessageType.TRANSMISSION)) { - if (segments.length < 22) { - throw new MalformedPacketException("Packet is a message (22 fields), but only has " + segments.length); - } - if (segments[11].length() > 0) { - this.altitude = Double.parseDouble(segments[11]); - } - if (segments[12].length() > 0) { - this.groundSpeed = Double.parseDouble(segments[12]); - } - if (segments[13].length() > 0) { - this.track = Double.parseDouble(segments[13]); - } - if (segments[14].length() > 0) { - this.latitude = Double.parseDouble(segments[14]); - } - if (segments[15].length() > 0) { - this.longitude = Double.parseDouble(segments[15]); - } - if (segments[16].length() > 0) { - this.verticalRate = Double.parseDouble(segments[16]); - } - this.squawk = segments[17]; - if (segments[18].length() > 0) { - this.alert = segments[18].equals("1"); - } - if (segments[19].length() > 0) { - this.emergency = segments[19].equals("1"); - } - if (segments[20].length() > 0) { - this.spi = segments[20].equals("1"); - } - if (segments[21].length() > 0) { - this.isOnGround = segments[21].equals("1"); - } - } - } - - public Date parseDateAndTime(String date, String time) { - String combined = ""; - Calendar now = Calendar.getInstance(); - if (date == null || date.isEmpty()) { - combined += now.get(Calendar.YEAR) + "/" + now.get(Calendar.MONTH) + "/" + now.get(Calendar.DAY_OF_MONTH); - } else { - combined += date; - } - - combined += " "; - - if (time == null || time.isEmpty()) { - combined += now.get(Calendar.HOUR_OF_DAY) + ":" + - now.get(Calendar.MINUTE) + ":" + - now.get(Calendar.SECOND) + "." + - now.get(Calendar.MILLISECOND); - } else { - combined += time; - } - - SimpleDateFormat fmt = new SimpleDateFormat("yyyy/MM/dd hh:mm:ss.sss"); - try { - return fmt.parse(combined); - } catch (ParseException e) { - return null; - } - } - - public String toString() { - return this.messageType.name(); - } - - public MessageType getMessageType() { - return messageType; - } - - public TransmissionType getTransmissionType() { - return transmissionType; - } - - public String getSessionId() { - return sessionId; - } - - public String getAircraftId() { - return aircraftId; - } - - public String getHexIdent() { - return hexIdent; - } - - public String getFlightId() { - return flightId; - } - - public Date getDateGenerated() { - return dateGenerated; - } - - public Date getDateLogged() { - return dateLogged; - } - - public String getCallsign() { - return callsign; - } - - public Double getAltitude() { - return altitude; - } - - public Double getGroundSpeed() { - return groundSpeed; - } - - public Double getTrack() { - return track; - } - - public Double getLatitude() { - return latitude; - } - - public Double getLongitude() { - return longitude; - } - - public Double getVerticalRate() { - return verticalRate; - } - - public String getSquawk() { - return squawk; - } - - public Boolean isAlert() { - return alert; - } - - public Boolean isEmergency() { - return emergency; - } - - public Boolean isSpi() { - return spi; - } - - public Boolean isOnGround() { - return isOnGround; - } -} diff --git a/src/com/benburwell/planes/sbs/TCPDataSource.java b/src/com/benburwell/planes/sbs/TCPDataSource.java deleted file mode 100644 index 2224d36..0000000 --- a/src/com/benburwell/planes/sbs/TCPDataSource.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.benburwell.planes.sbs; - -import java.util.List; -import java.util.ArrayList; -import java.io.*; -import java.net.*; - -/** - * Created by ben on 11/15/16. - */ -public class TCPDataSource implements DataSource { - private List subscribers = new ArrayList<>(); - private String host; - private int port; - private Thread clientThread = null; - private SocketClient client = null; - - public TCPDataSource(String host, int port) { - this.host = host; - this.port = port; - } - - public void subscribe(DataListener listener) { - this.subscribers.add(listener); - } - - public void open() { - this.client = new SocketClient(this.host, this.port); - this.clientThread = new Thread(this.client); - this.clientThread.start(); - } - - public void close() { - if (this.client != null) { - this.client.terminate(); - try { - this.clientThread.join(); - } catch (InterruptedException ignored) {} - } - } - - private class SocketClient implements Runnable { - private String host; - private int port; - private Socket clientSocket = null; - - public SocketClient(String host, int port) { - this.host = host; - this.port = port; - } - - public void terminate() { - if (this.clientSocket != null) { - try { - this.clientSocket.close(); - } catch (IOException e) { - System.out.println("Got exception closing socket: " + e.getMessage()); - } - } - } - - @Override - public void run() { - System.out.println("Starting socket client"); - BufferedReader socketReader; - try { - this.clientSocket = new Socket(this.host, this.port); - } catch (IOException e) { - System.out.println("Could not connect to " + this.host + " on port " + this.port + ": " + e.getMessage()); - return; - } - try { - socketReader = new BufferedReader(new InputStreamReader(this.clientSocket.getInputStream())); - } catch (IOException e) { - System.out.println("Could not create socket reader: " + e.getMessage()); - return; - } - - String receivedMessage; - while (true) { - try { - receivedMessage = socketReader.readLine(); - } catch (IOException e) { - System.out.println("Error reading from socket: " + e.getMessage()); - return; - } - try { - SBSPacket packet = new SBSPacket(receivedMessage); - for (DataListener subscriber : subscribers) { - subscriber.handleMessage(packet); - } - } catch (MalformedPacketException e) { - System.out.println("Discarding malformed packet: " + receivedMessage); - System.out.println(e.getMessage()); - } - } - } - } -} diff --git a/src/com/benburwell/planes/sbs/TransmissionType.java b/src/com/benburwell/planes/sbs/TransmissionType.java deleted file mode 100644 index fb1761e..0000000 --- a/src/com/benburwell/planes/sbs/TransmissionType.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.benburwell.planes.sbs; - -/** - * Created by ben on 11/15/16. - */ -public enum TransmissionType { - ES_IDENTIFICATION(1), - ES_SURFACE_POSITION(2), - ES_AIRBORNE_POSITION(3), - ES_AIRBORNE_VELOCITY(4), - SURVEILLANCE_ALT(5), - SURVEILLANCE_ID(6), - AIR_TO_AIR(7), - ALL_CALL_REPLY(8); - - private int id; - TransmissionType(int id) { - this.id = id; - } - - public int getId() { - return this.id; - } - - public static TransmissionType parse(String codeString) throws UnrecognizedTransmissionTypeException { - int code = Integer.parseInt(codeString); - for (TransmissionType transmissionType : TransmissionType.values()) { - if (transmissionType.getId() == code) { - return transmissionType; - } - } - throw new UnrecognizedTransmissionTypeException(code); - } -} diff --git a/src/com/benburwell/planes/sbs/UnrecognizedMessageTypeException.java b/src/com/benburwell/planes/sbs/UnrecognizedMessageTypeException.java deleted file mode 100644 index ee30a87..0000000 --- a/src/com/benburwell/planes/sbs/UnrecognizedMessageTypeException.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.benburwell.planes.sbs; - -/** - * Created by ben on 11/15/16. - */ -public class UnrecognizedMessageTypeException extends Exception { - private String type; - - public UnrecognizedMessageTypeException(String type) { - this.type = type; - } - - public String getType() { - return this.type; - } - - public String getMessage() { - return "Unrecognized message type: " + this.getType(); - } -} diff --git a/src/com/benburwell/planes/sbs/UnrecognizedTransmissionTypeException.java b/src/com/benburwell/planes/sbs/UnrecognizedTransmissionTypeException.java deleted file mode 100644 index abab067..0000000 --- a/src/com/benburwell/planes/sbs/UnrecognizedTransmissionTypeException.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.benburwell.planes.sbs; - -/** - * Created by ben on 11/15/16. - */ -public class UnrecognizedTransmissionTypeException extends Exception { - private int code; - - public UnrecognizedTransmissionTypeException(int code) { - this.code = code; - } - - public int getCode() { - return this.code; - } - - public String getMessage() { - return "Unrecognized transmission type: " + this.getCode(); - } -} diff --git a/src/main/java/com/benburwell/planes/data/Aircraft.java b/src/main/java/com/benburwell/planes/data/Aircraft.java new file mode 100644 index 0000000..66a7a46 --- /dev/null +++ b/src/main/java/com/benburwell/planes/data/Aircraft.java @@ -0,0 +1,90 @@ +package com.benburwell.planes.data; + +import com.benburwell.planes.sbs.SBSPacket; + +import java.util.List; +import java.util.ArrayList; + +/** + * Created by ben on 11/15/16. + */ +public class Aircraft implements Comparable { + private final String hexIdent; + private Position currentPosition = new Position(); + private List positionHistory = new ArrayList<>(); + private String callsign = ""; + private String squawk = ""; + private long packetCount = 0; + private double track; + private double groundSpeed; + private double verticalRate; + + public Aircraft(String hexIdent) { + this.hexIdent = hexIdent; + } + + public void handleUpdate(SBSPacket packet) { + this.packetCount++; + if (packet.getAltitude() != null) { + this.currentPosition.setAltitude(packet.getAltitude()); + } + if (packet.getLatitude() != null) { + this.currentPosition.setLatitude(packet.getLatitude()); + } + if (packet.getLongitude() != null) { + this.currentPosition.setLongitude(packet.getLongitude()); + } + if (packet.getCallsign() != null && !packet.getCallsign().isEmpty()) { + this.callsign = packet.getCallsign(); + } + if (packet.getSquawk() != null && !packet.getSquawk().isEmpty()) { + this.callsign = packet.getSquawk(); + } + if (packet.getTrack() != null) { + this.track = packet.getTrack(); + } + if (packet.getGroundSpeed() != null) { + this.groundSpeed = packet.getGroundSpeed(); + } + if (packet.getVerticalRate() != null) { + this.verticalRate = packet.getVerticalRate(); + } + } + + public Position getCurrentPosition() { + return currentPosition; + } + + public String getCallsign() { + return callsign; + } + + public String getSquawk() { + return squawk; + } + + public Long getPacketCount() { + return packetCount; + } + + public String getHexIdent() { + return this.hexIdent; + } + + public double getTrack() { + return this.track; + } + + public double getGroundSpeed() { + return this.groundSpeed; + } + + public double getVerticalRate() { + return this.verticalRate; + } + + @Override + public int compareTo(Aircraft that) { + return this.getHexIdent().compareTo(that.getHexIdent()); + } +} diff --git a/src/main/java/com/benburwell/planes/data/AircraftStore.java b/src/main/java/com/benburwell/planes/data/AircraftStore.java new file mode 100644 index 0000000..076701d --- /dev/null +++ b/src/main/java/com/benburwell/planes/data/AircraftStore.java @@ -0,0 +1,34 @@ +package com.benburwell.planes.data; + +import com.benburwell.planes.sbs.SBSPacket; + +import java.util.HashMap; +import java.util.Map; +import java.util.List; +import java.util.ArrayList; + +/** + * Created by ben on 11/17/16. + */ +public class AircraftStore { + private Map aircraftMap = new HashMap<>(); + private List aircraftListeners = new ArrayList<>(); + + public Map getAircraft() { + return this.aircraftMap; + } + + public void addPacket(SBSPacket packet) { + if (!this.aircraftMap.containsKey(packet.getHexIdent())) { + this.aircraftMap.put(packet.getHexIdent(), new Aircraft(packet.getHexIdent())); + } + this.aircraftMap.get(packet.getHexIdent()).handleUpdate(packet); + this.aircraftListeners.stream() + .filter(listener -> listener.respondTo(packet.getHexIdent())) + .forEach(AircraftStoreListener::aircraftStoreChanged); + } + + public void subscribe(AircraftStoreListener listener) { + this.aircraftListeners.add(listener); + } +} diff --git a/src/main/java/com/benburwell/planes/data/AircraftStoreListener.java b/src/main/java/com/benburwell/planes/data/AircraftStoreListener.java new file mode 100644 index 0000000..2ef635f --- /dev/null +++ b/src/main/java/com/benburwell/planes/data/AircraftStoreListener.java @@ -0,0 +1,9 @@ +package com.benburwell.planes.data; + +/** + * Created by ben on 11/17/16. + */ +public interface AircraftStoreListener { + void aircraftStoreChanged(); + boolean respondTo(String aircraftId); +} diff --git a/src/main/java/com/benburwell/planes/data/NavigationAid.java b/src/main/java/com/benburwell/planes/data/NavigationAid.java new file mode 100644 index 0000000..09521a0 --- /dev/null +++ b/src/main/java/com/benburwell/planes/data/NavigationAid.java @@ -0,0 +1,187 @@ +package com.benburwell.planes.data; + +/** + * Frequencies in kHz, elevations in ft + * + * Created by ben on 11/19/16. + */ +public class NavigationAid { + private int id; + private String filename; + private String ident; + private String type; + private int frequency; + private double latitude; + private double longitude; + private int elevation; + private String isoCountry; + private double dmeFrequency; + private String dmeChannel; + private double dmeLatitude; + private double dmeLongitude; + private int dmeElevation; + private double slavedVariation; + private double magneticVariation; + private String usageType; + private String power; + private String associatedAirport; + + public NavigationAid() {} + + public static NavigationAid fromCSV(String row) { + NavigationAid aid = new NavigationAid(); + return aid; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getFilename() { + return filename; + } + + public void setFilename(String filename) { + this.filename = filename; + } + + public String getIdent() { + return ident; + } + + public void setIdent(String ident) { + this.ident = ident; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public int getFrequency() { + return frequency; + } + + public void setFrequency(int frequency) { + this.frequency = frequency; + } + + public double getLatitude() { + return latitude; + } + + public void setLatitude(double latitude) { + this.latitude = latitude; + } + + public double getLongitude() { + return longitude; + } + + public void setLongitude(double longitude) { + this.longitude = longitude; + } + + public int getElevation() { + return elevation; + } + + public void setElevation(int elevation) { + this.elevation = elevation; + } + + public String getIsoCountry() { + return isoCountry; + } + + public void setIsoCountry(String isoCountry) { + this.isoCountry = isoCountry; + } + + public double getDmeFrequency() { + return dmeFrequency; + } + + public void setDmeFrequency(double dmeFrequency) { + this.dmeFrequency = dmeFrequency; + } + + public String getDmeChannel() { + return dmeChannel; + } + + public void setDmeChannel(String dmeChannel) { + this.dmeChannel = dmeChannel; + } + + public double getDmeLatitude() { + return dmeLatitude; + } + + public void setDmeLatitude(double dmeLatitude) { + this.dmeLatitude = dmeLatitude; + } + + public double getDmeLongitude() { + return dmeLongitude; + } + + public void setDmeLongitude(double dmeLongitude) { + this.dmeLongitude = dmeLongitude; + } + + public int getDmeElevation() { + return dmeElevation; + } + + public void setDmeElevation(int dmeElevation) { + this.dmeElevation = dmeElevation; + } + + public double getSlavedVariation() { + return slavedVariation; + } + + public void setSlavedVariation(double slavedVariation) { + this.slavedVariation = slavedVariation; + } + + public double getMagneticVariation() { + return magneticVariation; + } + + public void setMagneticVariation(double magneticVariation) { + this.magneticVariation = magneticVariation; + } + + public String getUsageType() { + return usageType; + } + + public void setUsageType(String usageType) { + this.usageType = usageType; + } + + public String getPower() { + return power; + } + + public void setPower(String power) { + this.power = power; + } + + public String getAssociatedAirport() { + return associatedAirport; + } + + public void setAssociatedAirport(String associatedAirport) { + this.associatedAirport = associatedAirport; + } +} diff --git a/src/main/java/com/benburwell/planes/data/Position.java b/src/main/java/com/benburwell/planes/data/Position.java new file mode 100644 index 0000000..4b37235 --- /dev/null +++ b/src/main/java/com/benburwell/planes/data/Position.java @@ -0,0 +1,46 @@ +package com.benburwell.planes.data; + +import java.util.Date; + +/** + * Created by ben on 11/15/16. + */ +public class Position { + private Date timestamp = new Date(System.currentTimeMillis()); + private Double latitude = 0.0; + private Double longitude = 0.0; + + public Date getTimestamp() { + return timestamp; + } + + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + + public double getLatitude() { + return latitude; + } + + public void setLatitude(double latitude) { + this.latitude = latitude; + } + + public double getLongitude() { + return longitude; + } + + public void setLongitude(double longitude) { + this.longitude = longitude; + } + + public double getAltitude() { + return altitude; + } + + public void setAltitude(double altitude) { + this.altitude = altitude; + } + + private double altitude = 0; +} diff --git a/src/main/java/com/benburwell/planes/gui/AircraftTableComponent.java b/src/main/java/com/benburwell/planes/gui/AircraftTableComponent.java new file mode 100644 index 0000000..221bdda --- /dev/null +++ b/src/main/java/com/benburwell/planes/gui/AircraftTableComponent.java @@ -0,0 +1,26 @@ +package com.benburwell.planes.gui; + +import com.benburwell.planes.data.AircraftStore; + +import javax.swing.*; + +/** + * Created by ben on 11/17/16. + */ +public class AircraftTableComponent implements ViewComponent{ + private JTable table; + private AircraftTableModel tableModel; + private JScrollPane scrollPane; + + public AircraftTableComponent(AircraftStore store) { + this.tableModel = new AircraftTableModel(store); + this.table = new JTable(this.tableModel); + this.table.setFillsViewportHeight(true); + this.scrollPane = new JScrollPane(table); + } + + @Override + public JComponent getComponent() { + return this.scrollPane; + } +} diff --git a/src/main/java/com/benburwell/planes/gui/AircraftTableModel.java b/src/main/java/com/benburwell/planes/gui/AircraftTableModel.java new file mode 100644 index 0000000..3931893 --- /dev/null +++ b/src/main/java/com/benburwell/planes/gui/AircraftTableModel.java @@ -0,0 +1,85 @@ +package com.benburwell.planes.gui; + +import com.benburwell.planes.data.Aircraft; +import com.benburwell.planes.data.AircraftStore; +import com.benburwell.planes.data.AircraftStoreListener; + +import javax.swing.table.AbstractTableModel; +import java.util.Map; +import java.util.List; +import java.util.ArrayList; +import java.util.Collections; + +/** + * Created by ben on 11/15/16. + */ +public class AircraftTableModel extends AbstractTableModel { + private Map aircraftMap; + private String[] columnNames = { "Hex", "Callsign", "Squawk", "Latitude", "Longitude", "Altitude", "Vertical Rate", "Track", "Ground Speed", "Packets" }; + + public AircraftTableModel(AircraftStore store) { + this.aircraftMap = store.getAircraft(); + store.subscribe(new AircraftStoreListener() { + @Override + public void aircraftStoreChanged() { + AircraftTableModel.super.fireTableDataChanged(); + } + + @Override + public boolean respondTo(String aircraftId) { + // listen for all changes + return true; + } + }); + } + + @Override + public int getRowCount() { + return this.aircraftMap.keySet().size(); + } + + @Override + public int getColumnCount() { + return this.columnNames.length; + } + + @Override + public String getColumnName(int col) { + return this.columnNames[col]; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + List aircraftList = this.getAircraftList(); + Aircraft aircraft = aircraftList.get(rowIndex); + switch (columnIndex) { + case 0: + return aircraft.getHexIdent(); + case 1: + return aircraft.getCallsign(); + case 2: + return aircraft.getSquawk(); + case 3: + return aircraft.getCurrentPosition().getLatitude(); + case 4: + return aircraft.getCurrentPosition().getLongitude(); + case 5: + return aircraft.getCurrentPosition().getAltitude(); + case 6: + return aircraft.getVerticalRate(); + case 7: + return aircraft.getTrack(); + case 8: + return aircraft.getGroundSpeed(); + case 9: + return aircraft.getPacketCount(); + } + return ""; + } + + private List getAircraftList() { + List aircraftList = new ArrayList<>(this.aircraftMap.values()); + Collections.sort(aircraftList); + return aircraftList; + } +} diff --git a/src/main/java/com/benburwell/planes/gui/GraphicsTheme.java b/src/main/java/com/benburwell/planes/gui/GraphicsTheme.java new file mode 100644 index 0000000..cece138 --- /dev/null +++ b/src/main/java/com/benburwell/planes/gui/GraphicsTheme.java @@ -0,0 +1,27 @@ +package com.benburwell.planes.gui; + +import java.awt.*; + +/** + * Created by ben on 11/18/16. + */ +public class GraphicsTheme { + public static class Colors { + public static final Color BASE_0 = new Color(12, 16, 20); + public static final Color BASE_1 = new Color(17, 21, 28); + public static final Color BASE_2 = new Color(9, 31, 46); + public static final Color BASE_3 = new Color(10, 55, 73); + public static final Color BASE_4 = new Color(36, 83, 97); + public static final Color BASE_5 = new Color(89, 156, 171); + public static final Color BASE_6 = new Color(153, 209, 206); + public static final Color BASE_7 = new Color(211, 235, 233); + public static final Color RED = new Color(194, 49, 39); + public static final Color ORANGE = new Color(210, 105, 55); + public static final Color YELLOW = new Color(237, 180, 67); + public static final Color MAGENTA = new Color(136, 140, 166); + public static final Color VIOLET = new Color(78, 81, 102); + public static final Color BLUE = new Color(25, 84, 102); + public static final Color CYAN = new Color(51, 133, 158); + public static final Color GREEN = new Color(42, 168, 137); + } +} diff --git a/src/main/java/com/benburwell/planes/gui/Main1090.java b/src/main/java/com/benburwell/planes/gui/Main1090.java new file mode 100644 index 0000000..d7fc830 --- /dev/null +++ b/src/main/java/com/benburwell/planes/gui/Main1090.java @@ -0,0 +1,93 @@ +/** + * Created by ben on 11/15/16. + */ + +package com.benburwell.planes.gui; + +import com.benburwell.planes.sbs.*; +import com.benburwell.planes.data.*; +import com.benburwell.planes.gui.aircraftmap.*; + +import java.awt.*; +import javax.swing.*; +import java.awt.event.ActionEvent; + +public class Main1090 extends JFrame { + private AggregateDataSource sbsDataSource = new AggregateDataSource(); + private AircraftStore aircraft = new AircraftStore(); + private int currentTcpConnection = 0; + private JTabbedPane tabbedPane = new JTabbedPane(); + + public Main1090() { + this.initUI(); + } + + private void initUI() { + this.createMenuBar(); + + this.setTitle("1090"); + this.setSize(600, 400); + this.setLocationRelativeTo(null); + this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + + this.openDataSource(); + + this.createTabs(); + } + + private void createTabs() { + AircraftTableComponent aircraftData = new AircraftTableComponent(this.aircraft); + this.tabbedPane.addTab("Aircraft Data", aircraftData.getComponent()); + + AircraftMapComponent aircraftMap = new AircraftMapComponent(this.aircraft); + this.tabbedPane.addTab("Live Map", aircraftMap.getComponent()); + + this.add(this.tabbedPane); + this.tabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); + + this.tabbedPane.setSelectedIndex(1); + } + + private void createMenuBar() { + MenuBarProvider provider = new MenuBarProvider(); + this.setJMenuBar(provider.getMenuBar()); + + provider.getDataConnectItem().addActionListener((ActionEvent event) -> { + if (this.currentTcpConnection == 0) { + TCPConnectionOptionDialog dialog = new TCPConnectionOptionDialog(); + JOptionPane.showMessageDialog(this, dialog.getComponent(), "New Network Data Source", JOptionPane.PLAIN_MESSAGE); + this.currentTcpConnection = this.addTcpSource(dialog.getHost(), dialog.getPort()); + provider.getDataConnectItem().setEnabled(false); + provider.getDataDisconnectItem().setEnabled(true); + } + }); + provider.getDataDisconnectItem().addActionListener((ActionEvent event) -> { + if (this.currentTcpConnection != 0) { + this.sbsDataSource.closeSource(this.currentTcpConnection); + provider.getDataConnectItem().setEnabled(true); + provider.getDataDisconnectItem().setEnabled(false); + this.currentTcpConnection = 0; + } + }); + } + + private void openDataSource() { + this.sbsDataSource.subscribe((SBSPacket packet) -> { + this.aircraft.addPacket(packet); + }); + this.sbsDataSource.open(); + } + + private int addTcpSource(String host, int port) { + TCPDataSource source = new TCPDataSource(host, port); + source.open(); + return this.sbsDataSource.addSource(source); + } + + public static void main(String[] args) { + EventQueue.invokeLater(() -> { + Main1090 app = new Main1090(); + app.setVisible(true); + }); + } +} diff --git a/src/main/java/com/benburwell/planes/gui/MenuBarProvider.java b/src/main/java/com/benburwell/planes/gui/MenuBarProvider.java new file mode 100644 index 0000000..add77ce --- /dev/null +++ b/src/main/java/com/benburwell/planes/gui/MenuBarProvider.java @@ -0,0 +1,41 @@ +package com.benburwell.planes.gui; + +import javax.swing.*; +import java.awt.event.ActionEvent; + +/** + * Created by ben on 11/17/16. + */ +public class MenuBarProvider { + private JMenuBar menubar = new JMenuBar(); + private JMenu file = new JMenu("1090"); + private JMenuItem fileQuitMenuItem = new JMenuItem("Quit"); + private JMenu data = new JMenu("Data Source"); + private JMenuItem dataConnectItem = new JMenuItem("Connect to Remote..."); + private JMenuItem dataDisconnectItem = new JMenuItem("Disconnect"); + + public MenuBarProvider() { + fileQuitMenuItem.addActionListener((ActionEvent event) -> { + System.exit(0); + }); + file.add(fileQuitMenuItem); + menubar.add(file); + + dataDisconnectItem.setEnabled(false); + data.add(dataConnectItem); + data.add(dataDisconnectItem); + menubar.add(data); + } + + public JMenuBar getMenuBar() { + return this.menubar; + } + + public JMenuItem getDataConnectItem() { + return this.dataConnectItem; + } + + public JMenuItem getDataDisconnectItem() { + return this.dataDisconnectItem; + } +} diff --git a/src/main/java/com/benburwell/planes/gui/TCPConnectionOptionDialog.java b/src/main/java/com/benburwell/planes/gui/TCPConnectionOptionDialog.java new file mode 100644 index 0000000..609a70a --- /dev/null +++ b/src/main/java/com/benburwell/planes/gui/TCPConnectionOptionDialog.java @@ -0,0 +1,45 @@ +package com.benburwell.planes.gui; + +import javax.swing.*; + +/** + * Created by ben on 11/17/16. + */ +public class TCPConnectionOptionDialog implements ViewComponent { + public static final String DEFAULT_HOSTNAME = "10.0.0.111"; + public static final int DEFAULT_TCP_PORT = 30003; + + private JPanel dialog = new JPanel(); + private JTextField hostField = new JTextField(10); + private JTextField portField = new JTextField(5); + private JLabel descriptionLabel = new JLabel("Add a network data source that provides data in the SBS-1 format"); + + @Override + public JComponent getComponent() { + // set properties + hostField.setText(DEFAULT_HOSTNAME); + hostField.setToolTipText("Hostname or IP address"); + + portField.setText(String.valueOf(DEFAULT_TCP_PORT)); + portField.setToolTipText("Port number"); + + // create layout + dialog.add(descriptionLabel); + dialog.add(hostField); + dialog.add(portField); + + return dialog; + } + + public String getHost() { + return this.hostField.getText(); + } + + public int getPort() { + try { + return Integer.valueOf(this.portField.getText()); + } catch (NumberFormatException e) { + return DEFAULT_TCP_PORT; + } + } +} diff --git a/src/main/java/com/benburwell/planes/gui/ViewComponent.java b/src/main/java/com/benburwell/planes/gui/ViewComponent.java new file mode 100644 index 0000000..91c21cd --- /dev/null +++ b/src/main/java/com/benburwell/planes/gui/ViewComponent.java @@ -0,0 +1,10 @@ +package com.benburwell.planes.gui; + +import javax.swing.*; + +/** + * Created by ben on 11/17/16. + */ +public interface ViewComponent { + JComponent getComponent(); +} diff --git a/src/main/java/com/benburwell/planes/gui/aircraftmap/AircraftMap.java b/src/main/java/com/benburwell/planes/gui/aircraftmap/AircraftMap.java new file mode 100644 index 0000000..fad1082 --- /dev/null +++ b/src/main/java/com/benburwell/planes/gui/aircraftmap/AircraftMap.java @@ -0,0 +1,174 @@ +package com.benburwell.planes.gui.aircraftmap; + +import com.benburwell.planes.gui.GraphicsTheme; + +import javax.swing.*; +import java.awt.*; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by ben on 11/19/16. + */ +public class AircraftMap extends JPanel { + // geographic constants + public final double MAX_LATITUDE = 90.0; + public final double MIN_LATITUDE = -90.0; + public final double MAX_LONGITUDE = 180.0; + public final double MIN_LONGITUDE = -180.0; + public final double NAUTICAL_MILES_PER_DEGREE_LATITUDE = 60.0; + + // drawing constants + public final float FONT_SIZE = 12; + public final int TEXT_PADDING = 5; + public final int NUMBER_OF_RANGE_RINGS = 5; + + // map manipulation constants + public final int MIN_ZOOM_PIXELS_PER_MILE = 1; + public final int MAX_ZOOM_PIXELS_PER_MILE = 2000; + public final double PAN_INTERVAL_MILES = 1.0; + + // instance fields + private List planes = new ArrayList<>(); + private double centerLatitude; + private double centerLongitude; + private int pixelsPerNauticalMile = 10; + + /** + * Construct a map + */ + public AircraftMap() { + super(); + this.setBackground(GraphicsTheme.Colors.BASE_1); + this.setBorder(BorderFactory.createEmptyBorder()); + this.setCenter(0, 0); + } + + /** + * Paint the ViewComponent on a Graphics instance + * + * @param g the graphics context to paint on + */ + @Override + public void paintComponent(Graphics g) { + super.paintComponent(g); + this.drawPositionAndScale(g); + this.drawRange(g); + this.planes.forEach(item -> item.drawOn(g, this)); + } + + public void drawPositionAndScale(Graphics g) { + Font currentFont = g.getFont(); + Font newFont = currentFont.deriveFont(FONT_SIZE); + g.setFont(newFont); + g.setColor(GraphicsTheme.Colors.BLUE); + g.drawString(String.format("%08.5f N", this.centerLatitude), TEXT_PADDING, (int) FONT_SIZE + TEXT_PADDING); + g.drawString(String.format("%08.5f E", this.centerLongitude), TEXT_PADDING, (int) FONT_SIZE * 2 + TEXT_PADDING); + g.drawString(String.format("%d nm", this.getRangeRadius()), TEXT_PADDING, (int) FONT_SIZE * 3 + TEXT_PADDING); + } + + public int getRangeRadius() { + double milesHigh = this.getHeight() / this.getPixelsPerNauticalMile(); + double milesWide = this.getWidth() / this.getPixelsPerNauticalMile(); + double screenMiles = Math.min(milesHigh, milesWide); + int milesPerRing = (int) screenMiles / NUMBER_OF_RANGE_RINGS; + return milesPerRing; + } + + public List getRangeRadii() { + int rangeRadius = this.getRangeRadius(); + List radii = new ArrayList<>(); + for (int ringNumber = 1; ringNumber <= NUMBER_OF_RANGE_RINGS; ringNumber++) { + radii.add(rangeRadius * ringNumber); + } + return radii; + } + + public void drawRange(Graphics g) { + int centerX = this.getWidth() / 2; + int centerY = this.getHeight() / 2; + g.setColor(GraphicsTheme.Colors.BASE_3); + for (Integer radius : this.getRangeRadii()) { + int pixelRadius = (int) (this.getPixelsPerNauticalMile() * radius); + g.drawOval(centerX - pixelRadius, centerY - pixelRadius, pixelRadius * 2, pixelRadius * 2); + } + g.drawLine(centerX, 0, centerX, this.getHeight()); + g.drawLine(0, centerY, this.getWidth(), centerY); + } + + public void setPlanes(List planes) { + this.planes = planes; + this.redraw(); + } + + public void redraw() { + this.invalidate(); + this.validate(); + this.repaint(); + } + + public void setCenter(double latitude, double longitude) { + this.centerLatitude = latitude; + this.centerLongitude = longitude; + this.redraw(); + } + + public double getCenterLatitude() { + return this.centerLatitude; + } + + public double getCenterLongitude() { + return this.centerLongitude; + } + + public double getPixelsPerDegreeLatitude() { + return this.pixelsPerNauticalMile * NAUTICAL_MILES_PER_DEGREE_LATITUDE; + } + + public double getPixelsPerDegreeLongitude() { + return this.pixelsPerNauticalMile * this.getNauticalMilesPerDegreeLongitude(); + } + + public double getNauticalMilesPerDegreeLongitude() { + double milesPerDegree = Math.abs(Math.cos(Math.toRadians(this.centerLatitude)) * NAUTICAL_MILES_PER_DEGREE_LATITUDE); + return milesPerDegree; + } + + public double getPixelsPerNauticalMile() { + return this.pixelsPerNauticalMile; + } + + public void zoomIn() { + this.pixelsPerNauticalMile = Math.min(MAX_ZOOM_PIXELS_PER_MILE, this.pixelsPerNauticalMile * 2); + this.redraw(); + } + + public void zoomOut() { + this.pixelsPerNauticalMile = Math.max(MIN_ZOOM_PIXELS_PER_MILE, this.pixelsPerNauticalMile / 2); + this.redraw(); + } + + public void moveEast() { + double degreesToMove = PAN_INTERVAL_MILES / this.getNauticalMilesPerDegreeLongitude(); + this.centerLongitude = Math.min(this.centerLongitude + degreesToMove, MAX_LONGITUDE); + this.redraw(); + } + + public void moveWest() { + double degreesToMove = PAN_INTERVAL_MILES / this.getNauticalMilesPerDegreeLongitude(); + this.centerLongitude = Math.max(this.centerLongitude - degreesToMove, MIN_LONGITUDE); + this.redraw(); + } + + public void moveNorth() { + double degreesToMove = PAN_INTERVAL_MILES / NAUTICAL_MILES_PER_DEGREE_LATITUDE; + this.centerLatitude = Math.min(this.centerLatitude + degreesToMove, MAX_LATITUDE); + this.redraw(); + } + + public void moveSouth() { + double degreesToMove = PAN_INTERVAL_MILES / NAUTICAL_MILES_PER_DEGREE_LATITUDE; + this.centerLatitude = Math.max(this.centerLatitude - degreesToMove, MIN_LATITUDE); + this.redraw(); + } +} diff --git a/src/main/java/com/benburwell/planes/gui/aircraftmap/AircraftMapComponent.java b/src/main/java/com/benburwell/planes/gui/aircraftmap/AircraftMapComponent.java new file mode 100644 index 0000000..e490dae --- /dev/null +++ b/src/main/java/com/benburwell/planes/gui/aircraftmap/AircraftMapComponent.java @@ -0,0 +1,106 @@ +package com.benburwell.planes.gui.aircraftmap; + +import com.benburwell.planes.data.AircraftStore; +import com.benburwell.planes.data.AircraftStoreListener; +import com.benburwell.planes.data.Position; +import com.benburwell.planes.gui.ViewComponent; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.KeyEvent; +import java.util.List; +import java.util.ArrayList; +import java.util.Collections; + +/** + * Created by ben on 11/18/16. + */ +public class AircraftMapComponent implements ViewComponent { + private AircraftStore store; + private AircraftMap mapPanel; + private String focusedAircraftIdentifier = null; + + public AircraftMapComponent(AircraftStore store) { + this.store = store; + this.setupMap(); + this.bindKeys(); + this.subscribeToChanges(); + } + + public void focusNextAircraft() { + // List aircraftIdentifiers = new ArrayList<>(this.store.getAircraft().keySet()); + // Collections.sort(aircraftIdentifiers); + // if (this.focusedAircraftIdentifier == null && aircraftIdentifiers.size() > 0) { + // this.focusedAircraftIdentifier = aircraftIdentifiers.get(0); + // } else { + // int idx = aircraftIdentifiers.indexOf(this.focusedAircraftIdentifier); + // if (idx > 0 && idx < aircraftIdentifiers.size() - 1) { + // this.focusedAircraftIdentifier = aircraftIdentifiers.get(idx++); + // } else if (aircraftIdentifiers.size() > 0) { + // this.focusedAircraftIdentifier = aircraftIdentifiers.get(0); + // } else { + // this.focusedAircraftIdentifier = null; + // } + // } + } + + private void setupMap() { + this.mapPanel = new AircraftMap(); + this.mapPanel.setCenter(40.6188942, -75.4947205); + } + + private void bindKeys() { + KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(e -> { + if (e.getKeyCode() == KeyEvent.VK_EQUALS && e.isShiftDown() && e.getID() == KeyEvent.KEY_PRESSED) { + this.mapPanel.zoomIn(); + } else if (e.getKeyCode() == KeyEvent.VK_MINUS && e.getID() == KeyEvent.KEY_PRESSED) { + this.mapPanel.zoomOut(); + } else if (e.getKeyCode() == KeyEvent.VK_L && e.getID() == KeyEvent.KEY_PRESSED) { + this.mapPanel.moveEast(); + } else if (e.getKeyCode() == KeyEvent.VK_H && e.getID() == KeyEvent.KEY_PRESSED) { + this.mapPanel.moveWest(); + } else if (e.getKeyCode() == KeyEvent.VK_J && e.getID() == KeyEvent.KEY_PRESSED) { + this.mapPanel.moveSouth(); + } else if (e.getKeyCode() == KeyEvent.VK_K && e.getID() == KeyEvent.KEY_PRESSED) { + this.mapPanel.moveNorth(); + } else if (e.getKeyCode() == KeyEvent.VK_0 && e.getID() == KeyEvent.KEY_PRESSED) { + this.mapPanel.setCenter(40.6188942, -75.4947205); + } else if (e.getKeyCode() == KeyEvent.VK_TAB && e.getID() == KeyEvent.KEY_PRESSED) { + this.focusNextAircraft(); + this.centerMapOnPlane(this.focusedAircraftIdentifier); + } + return false; + }); + } + + private void centerMapOnPlane(String identifier) { + if (identifier != null) { + Position pos = this.store.getAircraft().get(identifier).getCurrentPosition(); + this.mapPanel.setCenter(pos.getLatitude(), pos.getLongitude()); + } + } + + private void subscribeToChanges() { + this.store.subscribe(new AircraftStoreListener() { + @Override + public void aircraftStoreChanged() { + List planes = new ArrayList<>(); + store.getAircraft().values().forEach(aircraft -> planes.add(new Plane(aircraft))); + mapPanel.setPlanes(planes); + mapPanel.validate(); + mapPanel.repaint(); + } + + @Override + public boolean respondTo(String aircraftId) { + return true; + } + }); + } + + @Override + public JComponent getComponent() { + return this.mapPanel; + } + +} diff --git a/src/main/java/com/benburwell/planes/gui/aircraftmap/Drawable.java b/src/main/java/com/benburwell/planes/gui/aircraftmap/Drawable.java new file mode 100644 index 0000000..01c16ba --- /dev/null +++ b/src/main/java/com/benburwell/planes/gui/aircraftmap/Drawable.java @@ -0,0 +1,10 @@ +package com.benburwell.planes.gui.aircraftmap; + +import java.awt.*; + +/** + * Created by ben on 11/19/16. + */ +public interface Drawable { + void drawOn(Graphics graphicsContext, AircraftMap map); +} diff --git a/src/main/java/com/benburwell/planes/gui/aircraftmap/GeoPoint.java b/src/main/java/com/benburwell/planes/gui/aircraftmap/GeoPoint.java new file mode 100644 index 0000000..d3eda40 --- /dev/null +++ b/src/main/java/com/benburwell/planes/gui/aircraftmap/GeoPoint.java @@ -0,0 +1,36 @@ +package com.benburwell.planes.gui.aircraftmap; + +/** + * Created by ben on 11/19/16. + */ +public class GeoPoint { + private double latitude; + private double longitude; + private double altitude; + + public GeoPoint(double latitude, double longitude, double altitude) { + this.latitude = latitude; + this.longitude = longitude; + this.altitude = altitude; + } + + public int getX(AircraftMap map) { + double degreesFromCenter = map.getCenterLongitude() - this.longitude; + double pixelsFromCenter = degreesFromCenter * map.getPixelsPerDegreeLongitude(); + double centerPixels = map.getSize().getWidth() / 2; + int xPosition = (int) (centerPixels - pixelsFromCenter); + return xPosition; + } + + public int getY(AircraftMap map) { + double degreesFromCenter = map.getCenterLatitude() - this.latitude; + double pixelsFromCenter = degreesFromCenter * map.getPixelsPerDegreeLatitude(); + double centerPixels = map.getSize().getHeight() / 2; + int yPosition = (int) (centerPixels + pixelsFromCenter); + return yPosition; + } + + public double getAltitude() { + return this.altitude; + } +} diff --git a/src/main/java/com/benburwell/planes/gui/aircraftmap/Plane.java b/src/main/java/com/benburwell/planes/gui/aircraftmap/Plane.java new file mode 100644 index 0000000..2f98bab --- /dev/null +++ b/src/main/java/com/benburwell/planes/gui/aircraftmap/Plane.java @@ -0,0 +1,126 @@ +package com.benburwell.planes.gui.aircraftmap; + +import com.benburwell.planes.data.Aircraft; +import com.benburwell.planes.data.Position; +import com.benburwell.planes.gui.GraphicsTheme; + +import java.awt.*; +import java.awt.geom.AffineTransform; + +/** + * Created by ben on 11/19/16. + */ +public class Plane extends GeoPoint implements Drawable { + public final int TRIANGLE_HEIGHT = 6; + public final int TRIANGLE_WIDTH = 4; + public final int TEXT_OFFSET_X = 10; + public final int TEXT_OFFSET_Y = 15; + public final int MIN_COLOR_HEIGHT = 0; + public final int MAX_COLOR_HEIGHT = 50000; + private String name; + private double heading; + private double speed; + private double verticalRate; + + public Plane(Aircraft ac) { + this(ac.getCallsign(), ac.getCurrentPosition(), ac.getTrack(), ac.getGroundSpeed(), ac.getVerticalRate()); + } + + public Plane(String name, Position position, double heading, double speed, double verticalRate) { + this(name, position.getLatitude(), position.getLongitude(), position.getAltitude(), heading, speed, verticalRate); + } + + public Plane(String name, double latitude, double longitude, double altitude, double heading, double speed, double verticalRate) { + super(latitude, longitude, altitude); + this.name = name; + this.heading = heading; + this.speed = speed; + this.verticalRate = verticalRate; + } + + public int getFlightLevel() { + return (int) this.getAltitude() / 100; + } + + public Color getPlaneColor() { + Color minColor = GraphicsTheme.Colors.RED; + Color maxColor = GraphicsTheme.Colors.GREEN; + + float[] minHsb = Color.RGBtoHSB(minColor.getRed(), minColor.getGreen(), minColor.getBlue(), null); + float[] maxHsb = Color.RGBtoHSB(maxColor.getRed(), maxColor.getGreen(), maxColor.getBlue(), null); + + float minHue = minHsb[0]; + float maxHue = maxHsb[0]; + float minSat = minHsb[1]; + float maxSat = maxHsb[1]; + float minBright = minHsb[2]; + float maxBright = maxHsb[2]; + + double planePosition = (this.getAltitude() / (MAX_COLOR_HEIGHT - MIN_COLOR_HEIGHT)) + MIN_COLOR_HEIGHT; + float huePosition = (float) (planePosition * (maxHue - minHue) + minHue); + float satPosition = (float) (planePosition * (maxSat - minSat) + minSat); + float brightPosition = (float) (planePosition * (maxBright - minBright) + minBright); + + Color c = Color.getHSBColor(huePosition, satPosition, brightPosition); + return c; + } + + public double getAngle() { + return Math.toRadians(this.heading); + } + + public double getSpeed() { + return this.speed; + } + + public void drawTriangle(Graphics2D ctx, int x, int y, int predictionLength) { + AffineTransform at = new AffineTransform(); + at.setToRotation(this.getAngle(), x, y); + ctx.setTransform(at); + int[] xs = new int[]{ x - TRIANGLE_WIDTH, x, x + TRIANGLE_WIDTH, x - TRIANGLE_WIDTH }; + int[] ys = new int[]{ y + TRIANGLE_HEIGHT, y - TRIANGLE_HEIGHT, y + TRIANGLE_HEIGHT, y + TRIANGLE_HEIGHT }; + ctx.fillPolygon(xs, ys, 4); + ctx.drawLine(x, y, x, y - predictionLength); + } + + public String getVerticalRateIndicator() { + if (this.verticalRate > 0) { + return "\u2191"; // ↑ + } else if (this.verticalRate < 0) { + return "\u2193"; // ↓ + } + return ""; + } + + public String getDisplayName() { + if (this.name == null || this.name.isEmpty()) { + return "-----"; + } + return this.name; + } + + public int getPredictionLength(double pixelsPerNauticalMile) { + return (int) (this.speed / 60.0 * pixelsPerNauticalMile); + } + + public void drawOn(Graphics g, AircraftMap map) { + int x = this.getX(map); + int y = this.getY(map); + + if (x >= 0 && x <= map.getSize().getWidth() && y >= 0 && y <= map.getSize().getHeight()) { + // draw the plane dot + Graphics2D g2d = (Graphics2D) g.create(); + g2d.setColor(this.getPlaneColor()); + int predictedTrack = this.getPredictionLength(map.getPixelsPerNauticalMile()); + this.drawTriangle(g2d, x, y, predictedTrack); + g2d.dispose(); + + + // draw the name of the plane + g.setColor(GraphicsTheme.Colors.BASE_5); + g.drawString(this.getDisplayName(), x + TEXT_OFFSET_X, y + TEXT_OFFSET_Y); + String infoString = String.format("%d%s %.1f", this.getFlightLevel(), this.getVerticalRateIndicator(), this.getSpeed()); + g.drawString(infoString, x + TEXT_OFFSET_X, y + TEXT_OFFSET_Y + g.getFontMetrics().getHeight()); + } + } +} diff --git a/src/main/java/com/benburwell/planes/sbs/AggregateDataSource.java b/src/main/java/com/benburwell/planes/sbs/AggregateDataSource.java new file mode 100644 index 0000000..2850404 --- /dev/null +++ b/src/main/java/com/benburwell/planes/sbs/AggregateDataSource.java @@ -0,0 +1,48 @@ +package com.benburwell.planes.sbs; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.HashMap; + +/** + * Created by ben on 11/15/16. + */ +public class AggregateDataSource implements DataSource { + private List subscribers = new ArrayList<>(); + private boolean isOpen = false; + private int nextSourceNumber = 1; + private Map sources = new HashMap<>(); + + public int addSource(DataSource source) { + int thisSourceNumber = this.nextSourceNumber++; + this.sources.put(thisSourceNumber, source); + source.subscribe((SBSPacket packet) -> { + if (isOpen) { + for (DataListener listener : subscribers) { + listener.handleMessage(packet); + } + } + }); + return thisSourceNumber; + } + + public void subscribe(DataListener listener) { + this.subscribers.add(listener); + } + + public void open() { + this.isOpen = true; + } + + public void close() { + this.isOpen = false; + } + + public void closeSource(int sourceNumber) { + if (this.sources.containsKey(sourceNumber)) { + this.sources.get(sourceNumber).close(); + this.sources.remove(sourceNumber); + } + } +} diff --git a/src/main/java/com/benburwell/planes/sbs/DataListener.java b/src/main/java/com/benburwell/planes/sbs/DataListener.java new file mode 100644 index 0000000..b0da2ef --- /dev/null +++ b/src/main/java/com/benburwell/planes/sbs/DataListener.java @@ -0,0 +1,8 @@ +package com.benburwell.planes.sbs; + +/** + * Created by ben on 11/15/16. + */ +public interface DataListener { + void handleMessage(SBSPacket packet); +} diff --git a/src/main/java/com/benburwell/planes/sbs/DataSource.java b/src/main/java/com/benburwell/planes/sbs/DataSource.java new file mode 100644 index 0000000..961c6e2 --- /dev/null +++ b/src/main/java/com/benburwell/planes/sbs/DataSource.java @@ -0,0 +1,10 @@ +package com.benburwell.planes.sbs; + +/** + * Created by ben on 11/15/16. + */ +public interface DataSource { + void subscribe(DataListener listener); + void open(); + void close(); +} diff --git a/src/main/java/com/benburwell/planes/sbs/MalformedPacketException.java b/src/main/java/com/benburwell/planes/sbs/MalformedPacketException.java new file mode 100644 index 0000000..6cbd1a3 --- /dev/null +++ b/src/main/java/com/benburwell/planes/sbs/MalformedPacketException.java @@ -0,0 +1,15 @@ +package com.benburwell.planes.sbs; + +/** + * Created by ben on 11/15/16. + */ +public class MalformedPacketException extends Exception { + private String message; + public MalformedPacketException(String message) { + this.message = message; + } + + public String getMessage() { + return this.message; + } +} diff --git a/src/main/java/com/benburwell/planes/sbs/MessageType.java b/src/main/java/com/benburwell/planes/sbs/MessageType.java new file mode 100644 index 0000000..a2ecc66 --- /dev/null +++ b/src/main/java/com/benburwell/planes/sbs/MessageType.java @@ -0,0 +1,31 @@ +package com.benburwell.planes.sbs; + +/** + * Created by ben on 11/15/16. + */ +public enum MessageType { + SELECTION_CHANGE("SEL"), + NEW_ID("ID"), + NEW_AIRCRAFT("AIR"), + STATUS_CHANGE("STA"), + CLICK("CLK"), + TRANSMISSION("MSG"); + + private String code; + MessageType(String code) { + this.code = code; + } + + public String getCode() { + return this.code; + } + + public static MessageType parse(String messageType) throws UnrecognizedMessageTypeException { + for (MessageType type : MessageType.values()) { + if (type.getCode().equals(messageType)) { + return type; + } + } + throw new UnrecognizedMessageTypeException(messageType); + } +} diff --git a/src/main/java/com/benburwell/planes/sbs/SBSPacket.java b/src/main/java/com/benburwell/planes/sbs/SBSPacket.java new file mode 100644 index 0000000..5684803 --- /dev/null +++ b/src/main/java/com/benburwell/planes/sbs/SBSPacket.java @@ -0,0 +1,216 @@ +package com.benburwell.planes.sbs; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; + +/** + * Created by ben on 11/15/16. + */ +public class SBSPacket { + private MessageType messageType; + private TransmissionType transmissionType = null; + private String sessionId = null; + private String aircraftId = null; + private String hexIdent = null; + private String flightId = null; + private Date dateGenerated = null; + private Date dateLogged = null; + private String callsign = null; + private Double altitude = null; + private Double groundSpeed = null; + private Double track = null; + private Double latitude = null; + private Double longitude = null; + private Double verticalRate = null; + private String squawk = null; + private Boolean alert = null; + private Boolean emergency = null; + private Boolean spi = null; + private Boolean isOnGround = null; + + public SBSPacket(String packet) throws MalformedPacketException { + this.parse(packet); + } + + public void parse(String packet) throws MalformedPacketException { + String[] segments = packet.split(",", -1); + if (segments.length < 11) { + throw new MalformedPacketException("Packet must have at least 11 fields, but has only " + segments.length); + } + + // get the message type + try { + this.messageType = MessageType.parse(segments[0]); + } catch (UnrecognizedMessageTypeException e) { + throw new MalformedPacketException("Packet has an unrecognized message type: " + e.getType()); + } + + // get the transmission type + if (this.messageType.equals(MessageType.TRANSMISSION)) { + try { + this.transmissionType = TransmissionType.parse(segments[1]); + } catch (UnrecognizedTransmissionTypeException e) { + throw new MalformedPacketException("Packet has an unrecognized transmission type code: " + e.getCode()); + } + } + + this.sessionId = segments[2]; + this.aircraftId = segments[3]; + this.hexIdent = segments[4]; + this.flightId = segments[5]; + this.dateGenerated = this.parseDateAndTime(segments[6], segments[7]); + this.dateLogged = this.parseDateAndTime(segments[8], segments[9]); + this.callsign = segments[10]; + + if (this.messageType.equals(MessageType.TRANSMISSION)) { + if (segments.length < 22) { + throw new MalformedPacketException("Packet is a message (22 fields), but only has " + segments.length); + } + if (segments[11].length() > 0) { + this.altitude = Double.parseDouble(segments[11]); + } + if (segments[12].length() > 0) { + this.groundSpeed = Double.parseDouble(segments[12]); + } + if (segments[13].length() > 0) { + this.track = Double.parseDouble(segments[13]); + } + if (segments[14].length() > 0) { + this.latitude = Double.parseDouble(segments[14]); + } + if (segments[15].length() > 0) { + this.longitude = Double.parseDouble(segments[15]); + } + if (segments[16].length() > 0) { + this.verticalRate = Double.parseDouble(segments[16]); + } + this.squawk = segments[17]; + if (segments[18].length() > 0) { + this.alert = segments[18].equals("1"); + } + if (segments[19].length() > 0) { + this.emergency = segments[19].equals("1"); + } + if (segments[20].length() > 0) { + this.spi = segments[20].equals("1"); + } + if (segments[21].length() > 0) { + this.isOnGround = segments[21].equals("1"); + } + } + } + + public Date parseDateAndTime(String date, String time) { + String combined = ""; + Calendar now = Calendar.getInstance(); + if (date == null || date.isEmpty()) { + combined += now.get(Calendar.YEAR) + "/" + now.get(Calendar.MONTH) + "/" + now.get(Calendar.DAY_OF_MONTH); + } else { + combined += date; + } + + combined += " "; + + if (time == null || time.isEmpty()) { + combined += now.get(Calendar.HOUR_OF_DAY) + ":" + + now.get(Calendar.MINUTE) + ":" + + now.get(Calendar.SECOND) + "." + + now.get(Calendar.MILLISECOND); + } else { + combined += time; + } + + SimpleDateFormat fmt = new SimpleDateFormat("yyyy/MM/dd hh:mm:ss.sss"); + try { + return fmt.parse(combined); + } catch (ParseException e) { + return null; + } + } + + public String toString() { + return this.messageType.name(); + } + + public MessageType getMessageType() { + return messageType; + } + + public TransmissionType getTransmissionType() { + return transmissionType; + } + + public String getSessionId() { + return sessionId; + } + + public String getAircraftId() { + return aircraftId; + } + + public String getHexIdent() { + return hexIdent; + } + + public String getFlightId() { + return flightId; + } + + public Date getDateGenerated() { + return dateGenerated; + } + + public Date getDateLogged() { + return dateLogged; + } + + public String getCallsign() { + return callsign; + } + + public Double getAltitude() { + return altitude; + } + + public Double getGroundSpeed() { + return groundSpeed; + } + + public Double getTrack() { + return track; + } + + public Double getLatitude() { + return latitude; + } + + public Double getLongitude() { + return longitude; + } + + public Double getVerticalRate() { + return verticalRate; + } + + public String getSquawk() { + return squawk; + } + + public Boolean isAlert() { + return alert; + } + + public Boolean isEmergency() { + return emergency; + } + + public Boolean isSpi() { + return spi; + } + + public Boolean isOnGround() { + return isOnGround; + } +} diff --git a/src/main/java/com/benburwell/planes/sbs/TCPDataSource.java b/src/main/java/com/benburwell/planes/sbs/TCPDataSource.java new file mode 100644 index 0000000..2224d36 --- /dev/null +++ b/src/main/java/com/benburwell/planes/sbs/TCPDataSource.java @@ -0,0 +1,99 @@ +package com.benburwell.planes.sbs; + +import java.util.List; +import java.util.ArrayList; +import java.io.*; +import java.net.*; + +/** + * Created by ben on 11/15/16. + */ +public class TCPDataSource implements DataSource { + private List subscribers = new ArrayList<>(); + private String host; + private int port; + private Thread clientThread = null; + private SocketClient client = null; + + public TCPDataSource(String host, int port) { + this.host = host; + this.port = port; + } + + public void subscribe(DataListener listener) { + this.subscribers.add(listener); + } + + public void open() { + this.client = new SocketClient(this.host, this.port); + this.clientThread = new Thread(this.client); + this.clientThread.start(); + } + + public void close() { + if (this.client != null) { + this.client.terminate(); + try { + this.clientThread.join(); + } catch (InterruptedException ignored) {} + } + } + + private class SocketClient implements Runnable { + private String host; + private int port; + private Socket clientSocket = null; + + public SocketClient(String host, int port) { + this.host = host; + this.port = port; + } + + public void terminate() { + if (this.clientSocket != null) { + try { + this.clientSocket.close(); + } catch (IOException e) { + System.out.println("Got exception closing socket: " + e.getMessage()); + } + } + } + + @Override + public void run() { + System.out.println("Starting socket client"); + BufferedReader socketReader; + try { + this.clientSocket = new Socket(this.host, this.port); + } catch (IOException e) { + System.out.println("Could not connect to " + this.host + " on port " + this.port + ": " + e.getMessage()); + return; + } + try { + socketReader = new BufferedReader(new InputStreamReader(this.clientSocket.getInputStream())); + } catch (IOException e) { + System.out.println("Could not create socket reader: " + e.getMessage()); + return; + } + + String receivedMessage; + while (true) { + try { + receivedMessage = socketReader.readLine(); + } catch (IOException e) { + System.out.println("Error reading from socket: " + e.getMessage()); + return; + } + try { + SBSPacket packet = new SBSPacket(receivedMessage); + for (DataListener subscriber : subscribers) { + subscriber.handleMessage(packet); + } + } catch (MalformedPacketException e) { + System.out.println("Discarding malformed packet: " + receivedMessage); + System.out.println(e.getMessage()); + } + } + } + } +} diff --git a/src/main/java/com/benburwell/planes/sbs/TransmissionType.java b/src/main/java/com/benburwell/planes/sbs/TransmissionType.java new file mode 100644 index 0000000..fb1761e --- /dev/null +++ b/src/main/java/com/benburwell/planes/sbs/TransmissionType.java @@ -0,0 +1,34 @@ +package com.benburwell.planes.sbs; + +/** + * Created by ben on 11/15/16. + */ +public enum TransmissionType { + ES_IDENTIFICATION(1), + ES_SURFACE_POSITION(2), + ES_AIRBORNE_POSITION(3), + ES_AIRBORNE_VELOCITY(4), + SURVEILLANCE_ALT(5), + SURVEILLANCE_ID(6), + AIR_TO_AIR(7), + ALL_CALL_REPLY(8); + + private int id; + TransmissionType(int id) { + this.id = id; + } + + public int getId() { + return this.id; + } + + public static TransmissionType parse(String codeString) throws UnrecognizedTransmissionTypeException { + int code = Integer.parseInt(codeString); + for (TransmissionType transmissionType : TransmissionType.values()) { + if (transmissionType.getId() == code) { + return transmissionType; + } + } + throw new UnrecognizedTransmissionTypeException(code); + } +} diff --git a/src/main/java/com/benburwell/planes/sbs/UnrecognizedMessageTypeException.java b/src/main/java/com/benburwell/planes/sbs/UnrecognizedMessageTypeException.java new file mode 100644 index 0000000..ee30a87 --- /dev/null +++ b/src/main/java/com/benburwell/planes/sbs/UnrecognizedMessageTypeException.java @@ -0,0 +1,20 @@ +package com.benburwell.planes.sbs; + +/** + * Created by ben on 11/15/16. + */ +public class UnrecognizedMessageTypeException extends Exception { + private String type; + + public UnrecognizedMessageTypeException(String type) { + this.type = type; + } + + public String getType() { + return this.type; + } + + public String getMessage() { + return "Unrecognized message type: " + this.getType(); + } +} diff --git a/src/main/java/com/benburwell/planes/sbs/UnrecognizedTransmissionTypeException.java b/src/main/java/com/benburwell/planes/sbs/UnrecognizedTransmissionTypeException.java new file mode 100644 index 0000000..abab067 --- /dev/null +++ b/src/main/java/com/benburwell/planes/sbs/UnrecognizedTransmissionTypeException.java @@ -0,0 +1,20 @@ +package com.benburwell.planes.sbs; + +/** + * Created by ben on 11/15/16. + */ +public class UnrecognizedTransmissionTypeException extends Exception { + private int code; + + public UnrecognizedTransmissionTypeException(int code) { + this.code = code; + } + + public int getCode() { + return this.code; + } + + public String getMessage() { + return "Unrecognized transmission type: " + this.getCode(); + } +} -- cgit v1.2.3