mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-09 15:17:24 -05:00
#1849 Map enhancements. Adds track list, zoom to selection, history trail size and other features. (#1920)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
@@ -222,7 +222,7 @@ public class SDRTrunk implements Listener<TunerEvent>
|
||||
mPlaylistManager.getChannelProcessingManager().addAudioSegmentListener(mAudioRecordingManager);
|
||||
mPlaylistManager.getChannelProcessingManager().addAudioSegmentListener(mAudioStreamingManager);
|
||||
|
||||
MapService mapService = new MapService(mIconModel);
|
||||
MapService mapService = new MapService(aliasModel, mIconModel);
|
||||
mPlaylistManager.getChannelProcessingManager().addDecodeEventListener(mapService);
|
||||
|
||||
mNowPlayingDetailsVisible = mPreferences.getBoolean(PREFERENCE_NOW_PLAYING_DETAILS_VISIBLE, true);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -14,12 +14,16 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.map;
|
||||
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.icon.IconModel;
|
||||
import io.github.dsheirer.identifier.Form;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.IdentifierClass;
|
||||
import io.github.dsheirer.identifier.Role;
|
||||
import io.github.dsheirer.settings.MapViewSetting;
|
||||
import io.github.dsheirer.settings.SettingsManager;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
@@ -31,18 +35,67 @@ import org.jdesktop.swingx.mapviewer.DefaultTileFactory;
|
||||
import org.jdesktop.swingx.mapviewer.GeoPosition;
|
||||
import org.jdesktop.swingx.mapviewer.TileFactoryInfo;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JSpinner;
|
||||
import javax.swing.JSplitPane;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.JToggleButton;
|
||||
import javax.swing.SpinnerNumberModel;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import javax.swing.event.TableModelEvent;
|
||||
import java.awt.EventQueue;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Swing map panel.
|
||||
*/
|
||||
public class MapPanel extends JPanel implements IPlottableUpdateListener
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final int ZOOM_MINIMUM = 1;
|
||||
private static final int ZOOM_MAXIMUM = 16;
|
||||
|
||||
private static final String FOLLOW = "Follow";
|
||||
private static final String UNFOLLOW = "Unfollow";
|
||||
private static final String SELECT_A_TRACK = "(select a track)";
|
||||
private static final String NO_SYSTEM_NAME = "(no system name)";
|
||||
private SettingsManager mSettingsManager;
|
||||
private MapService mMapService;
|
||||
private JXMapViewer mMapViewer = new JXMapViewer();
|
||||
private JXMapViewer mMapViewer;
|
||||
private PlottableEntityPainter mMapPainter;
|
||||
private TrackGenerator mTrackGenerator;
|
||||
private JToggleButton mTrackGeneratorToggle;
|
||||
private JTable mPlottedTracksTable;
|
||||
private JButton mClearMapButton;
|
||||
private JButton mReplotAllTracksButton;
|
||||
private JButton mDeleteAllTracksButton;
|
||||
private JButton mDeleteTrackButton;
|
||||
private JButton mFollowButton;
|
||||
private JLabel mFollowedEntityLabel;
|
||||
private JCheckBox mCenterOnSelectedCheckBox;
|
||||
private PlottableEntityHistory mFollowedTrack;
|
||||
private JComboBox<Integer> mTrackHistoryLengthComboBox;
|
||||
private JSpinner mMapZoomSpinner;
|
||||
private SpinnerNumberModel mMapZoomSpinnerModel;
|
||||
private TrackHistoryModel mTrackHistoryModel = new TrackHistoryModel();
|
||||
private JTable mTrackHistoryTable;
|
||||
private JLabel mSelectedTrackSystemLabel;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param mapService for accessing entities to plot
|
||||
* @param aliasModel for alias lookup
|
||||
* @param iconModel for icon lookup
|
||||
* @param settingsManager for user specified options/settings.
|
||||
*/
|
||||
public MapPanel(MapService mapService, AliasModel aliasModel, IconModel iconModel, SettingsManager settingsManager)
|
||||
{
|
||||
mSettingsManager = settingsManager;
|
||||
@@ -55,62 +108,548 @@ public class MapPanel extends JPanel implements IPlottableUpdateListener
|
||||
private void init()
|
||||
{
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
|
||||
|
||||
/**
|
||||
* Set the entity painter as the overlay painter and register this panel
|
||||
* to receive new messages (plots)
|
||||
*/
|
||||
mMapViewer.setOverlayPainter(mMapPainter);
|
||||
mMapService.addListener(this);
|
||||
|
||||
/**
|
||||
* Map image source
|
||||
*/
|
||||
TileFactoryInfo info = new OSMTileFactoryInfo();
|
||||
DefaultTileFactory tileFactory = new DefaultTileFactory(info);
|
||||
mMapViewer.setTileFactory(tileFactory);
|
||||
JPanel cp = new JPanel();
|
||||
cp.setLayout(new MigLayout("insets 5", "[grow,fill][grow,fill][grow,fill]",
|
||||
"[grow,fill][][][][][][grow,fill][]"));
|
||||
cp.add(new JScrollPane(getPlottedTracksTable()), "span 3,wrap");
|
||||
|
||||
/**
|
||||
* Defines how many threads will be used to fetch the background map
|
||||
* tiles (graphics)
|
||||
*/
|
||||
tileFactory.setThreadPoolSize(8);
|
||||
cp.add(getMapZoomSpinner());
|
||||
JPanel zoomCenterPanel = new JPanel();
|
||||
zoomCenterPanel.setLayout(new MigLayout("insets 0", "[]5[grow,fill]", ""));
|
||||
zoomCenterPanel.add(new JLabel("Zoom"));
|
||||
zoomCenterPanel.add(getCenterOnSelectedCheckBox());
|
||||
cp.add(zoomCenterPanel, "span 2, wrap");
|
||||
|
||||
/**
|
||||
* Set initial location and zoom for the map upon display
|
||||
*/
|
||||
GeoPosition syracuse = new GeoPosition(43.048, -76.147);
|
||||
int zoom = 7;
|
||||
cp.add(getDeleteTrackButton());
|
||||
cp.add(getDeleteAllTracksButton());
|
||||
cp.add(getClearMapButton(), "wrap");
|
||||
|
||||
MapViewSetting view = mSettingsManager.getMapViewSetting("Default", syracuse, zoom);
|
||||
cp.add(getReplotAllTracksButton());
|
||||
cp.add(getFollowButton());
|
||||
cp.add(getFollowedEntityLabel(), "wrap");
|
||||
|
||||
mMapViewer.setAddressLocation(view.getGeoPosition());
|
||||
mMapViewer.setZoom(view.getZoom());
|
||||
cp.add(getTrackHistoryLengthComboBox());
|
||||
cp.add(new JLabel("Track History Length"), "span 2, wrap");
|
||||
|
||||
/**
|
||||
* Add a mouse adapter for panning and scrolling
|
||||
*/
|
||||
MapMouseListener listener = new MapMouseListener(mMapViewer, mSettingsManager);
|
||||
mMapViewer.addMouseListener(listener);
|
||||
mMapViewer.addMouseMotionListener(listener);
|
||||
cp.add(new JLabel("Selected System:"), "align right");
|
||||
cp.add(getSelectedTrackSystemLabel(), "span 2, wrap");
|
||||
|
||||
/* Map zoom listener */
|
||||
mMapViewer.addMouseWheelListener(new ZoomMouseWheelListenerCursor(mMapViewer));
|
||||
cp.add(new JScrollPane(getTrackHistoryTable()), "span 3,wrap");
|
||||
|
||||
/* Keyboard panning listener */
|
||||
mMapViewer.addKeyListener(new PanKeyListener(mMapViewer));
|
||||
// cp.add(getTrackGeneratorToggle(), "span 3,wrap");
|
||||
|
||||
/**
|
||||
* Add a selection listener
|
||||
*/
|
||||
SelectionAdapter sa = new SelectionAdapter(mMapViewer);
|
||||
mMapViewer.addMouseListener(sa);
|
||||
mMapViewer.addMouseMotionListener(sa);
|
||||
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
|
||||
splitPane.setDividerLocation(300);
|
||||
splitPane.add(cp);
|
||||
splitPane.add(getMapViewer());
|
||||
add(splitPane, "span");
|
||||
}
|
||||
|
||||
/**
|
||||
* Map component
|
||||
*/
|
||||
add(mMapViewer, "span");
|
||||
private void setSelected(PlottableEntityHistory selected)
|
||||
{
|
||||
mTrackHistoryModel.load(selected);
|
||||
|
||||
if(selected != null)
|
||||
{
|
||||
Identifier system = selected.getIdentifierCollection().getIdentifier(IdentifierClass.CONFIGURATION, Form.SYSTEM, Role.ANY);
|
||||
|
||||
if(system != null)
|
||||
{
|
||||
getSelectedTrackSystemLabel().setText(system.toString());
|
||||
}
|
||||
else
|
||||
{
|
||||
getSelectedTrackSystemLabel().setText(NO_SYSTEM_NAME);
|
||||
}
|
||||
|
||||
if(mMapPainter.addEntity(getSelected()))
|
||||
{
|
||||
getMapViewer().repaint();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
getSelectedTrackSystemLabel().setText(SELECT_A_TRACK);
|
||||
}
|
||||
|
||||
if(getCenterOnSelectedCheckBox().isSelected())
|
||||
{
|
||||
centerOn(selected);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replots all tracks to the map (after a clear operation).
|
||||
* @return button
|
||||
*/
|
||||
private JButton getReplotAllTracksButton()
|
||||
{
|
||||
if(mReplotAllTracksButton == null)
|
||||
{
|
||||
mReplotAllTracksButton = new JButton("Replot All");
|
||||
mReplotAllTracksButton.addActionListener(e ->
|
||||
{
|
||||
boolean added = mMapPainter.addAll(mMapService.getPlottableEntityModel().getAll());
|
||||
|
||||
if(added)
|
||||
{
|
||||
getMapViewer().repaint();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mReplotAllTracksButton;
|
||||
}
|
||||
|
||||
private JLabel getSelectedTrackSystemLabel()
|
||||
{
|
||||
if(mSelectedTrackSystemLabel == null)
|
||||
{
|
||||
mSelectedTrackSystemLabel = new JLabel(SELECT_A_TRACK);
|
||||
}
|
||||
|
||||
return mSelectedTrackSystemLabel;
|
||||
}
|
||||
|
||||
private JTable getTrackHistoryTable()
|
||||
{
|
||||
if(mTrackHistoryTable == null)
|
||||
{
|
||||
mTrackHistoryTable = new JTable(mTrackHistoryModel);
|
||||
// mTrackHistoryTable.getSelectionModel().addListSelectionListener(e ->
|
||||
// {
|
||||
// if(getCenterOnSelectedCheckBox().isSelected())
|
||||
// {
|
||||
// int modelIndex = getTrackHistoryTable().convertRowIndexToModel(getTrackHistoryTable().getSelectedRow());
|
||||
// TimestampedGeoPosition geo = mTrackHistoryModel.get(modelIndex);
|
||||
//
|
||||
// if(geo != null)
|
||||
// {
|
||||
// mMapViewer.setCenterPosition(geo);
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
return mTrackHistoryTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map zoom level combo box
|
||||
*/
|
||||
private JSpinner getMapZoomSpinner()
|
||||
{
|
||||
if(mMapZoomSpinner == null)
|
||||
{
|
||||
mMapZoomSpinnerModel = new SpinnerNumberModel(2, ZOOM_MINIMUM, ZOOM_MAXIMUM, 1);
|
||||
mMapZoomSpinnerModel.addChangeListener(new ChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent e)
|
||||
{
|
||||
Number number = mMapZoomSpinnerModel.getNumber();
|
||||
mMapViewer.setZoom(number.intValue());
|
||||
}
|
||||
});
|
||||
|
||||
mMapZoomSpinner = new JSpinner(mMapZoomSpinnerModel);
|
||||
}
|
||||
|
||||
return mMapZoomSpinner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plotted track history trail length selection combo box.
|
||||
*/
|
||||
private JComboBox<Integer> getTrackHistoryLengthComboBox()
|
||||
{
|
||||
if(mTrackHistoryLengthComboBox == null)
|
||||
{
|
||||
List<Integer> lengths = new ArrayList<>();
|
||||
for(int length = 1; length <= 10; length++)
|
||||
{
|
||||
lengths.add(length);
|
||||
}
|
||||
|
||||
mTrackHistoryLengthComboBox = new JComboBox<>(lengths.toArray(new Integer[]{lengths.size()}));
|
||||
mTrackHistoryLengthComboBox.setSelectedItem(mMapPainter.getTrackHistoryLength());
|
||||
|
||||
mTrackHistoryLengthComboBox.addActionListener(e ->
|
||||
{
|
||||
int length = (int)getTrackHistoryLengthComboBox().getSelectedItem();
|
||||
mMapPainter.setTrackHistoryLength(length);
|
||||
});
|
||||
}
|
||||
|
||||
return mTrackHistoryLengthComboBox;
|
||||
}
|
||||
|
||||
/**
|
||||
* Label to show the followed entity
|
||||
*/
|
||||
private JLabel getFollowedEntityLabel()
|
||||
{
|
||||
if(mFollowedEntityLabel == null)
|
||||
{
|
||||
mFollowedEntityLabel = new JLabel(" ");
|
||||
}
|
||||
|
||||
return mFollowedEntityLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the following state for an entity.
|
||||
*/
|
||||
private JButton getFollowButton()
|
||||
{
|
||||
if(mFollowButton == null)
|
||||
{
|
||||
mFollowButton = new JButton(FOLLOW);
|
||||
mFollowButton.setEnabled(false);
|
||||
mFollowButton.addActionListener(e ->
|
||||
{
|
||||
if(getFollowButton().getText().equals(FOLLOW))
|
||||
{
|
||||
follow(getSelected());
|
||||
}
|
||||
else
|
||||
{
|
||||
follow(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mFollowButton;
|
||||
}
|
||||
|
||||
private JButton getClearMapButton()
|
||||
{
|
||||
if(mClearMapButton == null)
|
||||
{
|
||||
mClearMapButton = new JButton("Clear Map");
|
||||
mClearMapButton.addActionListener(e ->
|
||||
{
|
||||
mMapPainter.clearAllEntities();
|
||||
repaint();
|
||||
});
|
||||
}
|
||||
|
||||
return mClearMapButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the behavior of centering on the selected track when a user selects a track in the table.
|
||||
* @return check box.
|
||||
*/
|
||||
private JToggleButton getCenterOnSelectedCheckBox()
|
||||
{
|
||||
if(mCenterOnSelectedCheckBox == null)
|
||||
{
|
||||
mCenterOnSelectedCheckBox = new JCheckBox("Center on Selection");
|
||||
mCenterOnSelectedCheckBox.setSelected(true);
|
||||
}
|
||||
|
||||
return mCenterOnSelectedCheckBox;
|
||||
}
|
||||
|
||||
private JButton getDeleteAllTracksButton()
|
||||
{
|
||||
if(mDeleteAllTracksButton == null)
|
||||
{
|
||||
mDeleteAllTracksButton = new JButton("Delete All");
|
||||
mDeleteAllTracksButton.addActionListener(e -> {
|
||||
mMapService.getPlottableEntityModel().deleteAllTracks();
|
||||
mMapPainter.clearAllEntities();
|
||||
//Clear followed entity
|
||||
follow(null);
|
||||
getMapViewer().repaint();
|
||||
});
|
||||
}
|
||||
|
||||
return mDeleteAllTracksButton;
|
||||
}
|
||||
|
||||
private JButton getDeleteTrackButton()
|
||||
{
|
||||
if(mDeleteTrackButton == null)
|
||||
{
|
||||
mDeleteTrackButton = new JButton("Delete");
|
||||
mDeleteTrackButton.setEnabled(false);
|
||||
mDeleteTrackButton.addActionListener(e -> {
|
||||
|
||||
List<PlottableEntityHistory> toDelete = new ArrayList<>();
|
||||
int[] selectedIndices = getPlottedTracksTable().getSelectionModel().getSelectedIndices();
|
||||
|
||||
for(int selectedIndex : selectedIndices)
|
||||
{
|
||||
int modelIndex = getPlottedTracksTable().convertRowIndexToModel(selectedIndex);
|
||||
PlottableEntityHistory entity = mMapService.getPlottableEntityModel().get(modelIndex);
|
||||
if(entity != null)
|
||||
{
|
||||
toDelete.add(entity);
|
||||
|
||||
//Clear followed entity if it's being deleted
|
||||
if(entity.equals(mFollowedTrack))
|
||||
{
|
||||
follow(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
mMapService.getPlottableEntityModel().delete(toDelete);
|
||||
mMapPainter.clearEntities(toDelete);
|
||||
getMapViewer().repaint();
|
||||
});
|
||||
}
|
||||
|
||||
return mDeleteTrackButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the selected entity history.
|
||||
* @return selected entity or null of one is not selected.
|
||||
*/
|
||||
private PlottableEntityHistory getSelected()
|
||||
{
|
||||
if(getPlottedTracksTable().getSelectedRow() >= 0)
|
||||
{
|
||||
int modelIndex = getPlottedTracksTable().convertRowIndexToModel(getPlottedTracksTable().getSelectedRow());
|
||||
return mMapService.getPlottableEntityModel().get(modelIndex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Centers on the plottable entity.
|
||||
* @param entityHistory to center on
|
||||
*/
|
||||
private void centerOn(PlottableEntityHistory entityHistory)
|
||||
{
|
||||
if(entityHistory != null)
|
||||
{
|
||||
GeoPosition geoPosition = entityHistory.getLatestPosition();
|
||||
|
||||
if(geoPosition != null)
|
||||
{
|
||||
mMapViewer.setCenterPosition(geoPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Follow or unfollow an entity.
|
||||
* @param entityHistory to follow or null to unfollow.
|
||||
*/
|
||||
private void follow(PlottableEntityHistory entityHistory)
|
||||
{
|
||||
mFollowedTrack = entityHistory;
|
||||
|
||||
if(mFollowedTrack != null)
|
||||
{
|
||||
centerOn(mFollowedTrack);
|
||||
getFollowButton().setText(UNFOLLOW);
|
||||
getFollowButton().setEnabled(true);
|
||||
getFollowedEntityLabel().setText("Following: " + mFollowedTrack.getIdentifier());
|
||||
getCenterOnSelectedCheckBox().setEnabled(false); //Disabled while we're following
|
||||
}
|
||||
else
|
||||
{
|
||||
getFollowButton().setText(FOLLOW);
|
||||
getFollowButton().setEnabled(getSelected() != null);
|
||||
getFollowedEntityLabel().setText(null);
|
||||
getCenterOnSelectedCheckBox().setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
private JTable getPlottedTracksTable()
|
||||
{
|
||||
if(mPlottedTracksTable == null)
|
||||
{
|
||||
mPlottedTracksTable = new JTable(mMapService.getPlottableEntityModel());
|
||||
|
||||
mMapService.getPlottableEntityModel().addTableModelListener(e ->
|
||||
{
|
||||
//Update the followed entity for DELETE/UPDATE operations
|
||||
if(mFollowedTrack != null)
|
||||
{
|
||||
if(e.getType() == TableModelEvent.DELETE)
|
||||
{
|
||||
for(int x = e.getFirstRow(); x <= e.getLastRow(); x++)
|
||||
{
|
||||
if(mMapService.getPlottableEntityModel().get(x).equals(mFollowedTrack))
|
||||
{
|
||||
follow(null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(e.getType() == TableModelEvent.UPDATE)
|
||||
{
|
||||
if(e.getFirstRow() == 0 && e.getLastRow() == Integer.MAX_VALUE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for(int x = e.getFirstRow(); x <= e.getLastRow(); x++)
|
||||
{
|
||||
PlottableEntityHistory entity = mMapService.getPlottableEntityModel().get(x);
|
||||
|
||||
if(entity.equals(getSelected()))
|
||||
{
|
||||
mTrackHistoryModel.update();
|
||||
}
|
||||
|
||||
if(entity != null && entity.equals(mFollowedTrack))
|
||||
{
|
||||
centerOn(mMapService.getPlottableEntityModel().get(x));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(e.getType() == TableModelEvent.UPDATE && !(e.getFirstRow() == 0 && e.getLastRow() == Integer.MAX_VALUE))
|
||||
{
|
||||
for(int x = e.getFirstRow(); x <= e.getLastRow(); x++)
|
||||
{
|
||||
PlottableEntityHistory entity = mMapService.getPlottableEntityModel().get(x);
|
||||
|
||||
if(entity != null && entity.equals(getSelected()))
|
||||
{
|
||||
mTrackHistoryModel.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(e.getType() == TableModelEvent.DELETE && getSelected() == null)
|
||||
{
|
||||
mTrackHistoryModel.load(null);
|
||||
}
|
||||
});
|
||||
|
||||
//Register selection listener to update button/label states
|
||||
mPlottedTracksTable.getSelectionModel().addListSelectionListener(e ->
|
||||
{
|
||||
//Toggle the enabled state of the delete (single) track button
|
||||
int count = mPlottedTracksTable.getSelectionModel().getSelectedItemsCount();
|
||||
getDeleteTrackButton().setEnabled(count > 0);
|
||||
|
||||
PlottableEntityHistory selected = getSelected();
|
||||
setSelected(selected);
|
||||
|
||||
//Refresh the followed entity button/label states
|
||||
follow(mFollowedTrack);
|
||||
});
|
||||
}
|
||||
|
||||
return mPlottedTracksTable;
|
||||
}
|
||||
|
||||
private JToggleButton getTrackGeneratorToggle()
|
||||
{
|
||||
if(mTrackGeneratorToggle == null)
|
||||
{
|
||||
mTrackGeneratorToggle = new JToggleButton("Track Generator");
|
||||
mTrackGeneratorToggle.addActionListener(e -> {
|
||||
if(mTrackGeneratorToggle.isSelected())
|
||||
{
|
||||
getTrackGenerator().start();
|
||||
}
|
||||
else
|
||||
{
|
||||
getTrackGenerator().stop();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mTrackGeneratorToggle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional test track generator
|
||||
*/
|
||||
private TrackGenerator getTrackGenerator()
|
||||
{
|
||||
if(mTrackGenerator == null)
|
||||
{
|
||||
mTrackGenerator = new TrackGenerator(mMapService);
|
||||
}
|
||||
|
||||
return mTrackGenerator;
|
||||
}
|
||||
|
||||
public JXMapViewer getMapViewer()
|
||||
{
|
||||
if(mMapViewer == null)
|
||||
{
|
||||
mMapViewer = new JXMapViewer();
|
||||
|
||||
/**
|
||||
* Set the entity painter as the overlay painter and register this panel to receive new messages (plots)
|
||||
*/
|
||||
mMapViewer.setOverlayPainter(mMapPainter);
|
||||
|
||||
/**
|
||||
* Map image source
|
||||
*/
|
||||
TileFactoryInfo info = new OSMTileFactoryInfo();
|
||||
DefaultTileFactory tileFactory = new DefaultTileFactory(info);
|
||||
mMapViewer.setTileFactory(tileFactory);
|
||||
|
||||
/**
|
||||
* Defines how many threads will be used to fetch the background map tiles (graphics)
|
||||
*/
|
||||
tileFactory.setThreadPoolSize(8);
|
||||
|
||||
/**
|
||||
* Set initial location and zoom for the map upon display
|
||||
*/
|
||||
GeoPosition syracuse = new GeoPosition(43.048, -76.147);
|
||||
int zoom = 7;
|
||||
|
||||
MapViewSetting view = mSettingsManager.getMapViewSetting("Default", syracuse, zoom);
|
||||
|
||||
mMapViewer.setAddressLocation(view.getGeoPosition());
|
||||
mMapZoomSpinnerModel.setValue(view.getZoom());
|
||||
|
||||
/**
|
||||
* Add a mouse adapter for panning and scrolling
|
||||
*/
|
||||
MapMouseListener listener = new MapMouseListener(mMapViewer, mSettingsManager);
|
||||
mMapViewer.addMouseListener(listener);
|
||||
mMapViewer.addMouseMotionListener(listener);
|
||||
|
||||
/* Map zoom listener */
|
||||
mMapViewer.addMouseWheelListener(new ZoomMouseWheelListenerCursor(this));
|
||||
|
||||
/* Keyboard panning listener */
|
||||
mMapViewer.addKeyListener(new PanKeyListener(mMapViewer));
|
||||
|
||||
/**
|
||||
* Add a selection listener
|
||||
*/
|
||||
SelectionAdapter sa = new SelectionAdapter(mMapViewer);
|
||||
mMapViewer.addMouseListener(sa);
|
||||
mMapViewer.addMouseMotionListener(sa);
|
||||
}
|
||||
|
||||
return mMapViewer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the zoom level by the specified value.
|
||||
* @param adjustment zoom value.
|
||||
*/
|
||||
public void adjustZoom(int adjustment)
|
||||
{
|
||||
Number currentZoom = mMapZoomSpinnerModel.getNumber();
|
||||
int updatedZoom = currentZoom.intValue() + adjustment;
|
||||
|
||||
if(ZOOM_MINIMUM <= updatedZoom && updatedZoom <= ZOOM_MAXIMUM)
|
||||
{
|
||||
mMapZoomSpinnerModel.setValue(currentZoom.intValue() + adjustment);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -15,78 +14,59 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.map;
|
||||
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.icon.IconModel;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.module.decode.event.IDecodeEvent;
|
||||
import io.github.dsheirer.module.decode.event.PlottableDecodeEvent;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class MapService implements Listener<IDecodeEvent>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(MapService.class);
|
||||
|
||||
private int mMaxHistory = 2;
|
||||
private List<IPlottableUpdateListener> mListeners = new ArrayList<IPlottableUpdateListener>();
|
||||
private Map<Identifier,PlottableEntityHistory> mEntityHistories = new HashMap<>();
|
||||
private IconModel mIconModel;
|
||||
private PlottableEntityModel mPlottableEntityModel;
|
||||
|
||||
public MapService(IconModel resourceManager)
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param aliasModel to lookup aliases
|
||||
* @param iconModel to lookup icons from entity aliases.
|
||||
*/
|
||||
public MapService(AliasModel aliasModel, IconModel iconModel)
|
||||
{
|
||||
mIconModel = resourceManager;
|
||||
mPlottableEntityModel = new PlottableEntityModel(aliasModel);
|
||||
mIconModel = iconModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Table model that holds the plottable entity history
|
||||
*/
|
||||
public PlottableEntityModel getPlottableEntityModel()
|
||||
{
|
||||
return mPlottableEntityModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(IDecodeEvent decodeEvent)
|
||||
{
|
||||
if(decodeEvent instanceof PlottableDecodeEvent)
|
||||
if(decodeEvent instanceof PlottableDecodeEvent plottableDecodeEvent)
|
||||
{
|
||||
PlottableDecodeEvent plottableDecodeEvent = (PlottableDecodeEvent)decodeEvent;
|
||||
Identifier from = plottableDecodeEvent.getIdentifierCollection().getFromIdentifier();
|
||||
|
||||
if(from != null)
|
||||
{
|
||||
PlottableEntityHistory entityHistory = mEntityHistories.get(from);
|
||||
|
||||
if(entityHistory == null)
|
||||
{
|
||||
entityHistory = new PlottableEntityHistory(from, plottableDecodeEvent);
|
||||
mEntityHistories.put(from, entityHistory);
|
||||
}
|
||||
else
|
||||
{
|
||||
entityHistory.add(plottableDecodeEvent);
|
||||
}
|
||||
|
||||
for(IPlottableUpdateListener listener : mListeners)
|
||||
{
|
||||
listener.addPlottableEntity(entityHistory);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.warn("Received plottable decode event that does not contain a FROM identifier - cannot plot");
|
||||
}
|
||||
mPlottableEntityModel.receive(plottableDecodeEvent);
|
||||
}
|
||||
}
|
||||
|
||||
public void addListener(IPlottableUpdateListener listener)
|
||||
{
|
||||
mListeners.add(listener);
|
||||
mPlottableEntityModel.addListener(listener);
|
||||
}
|
||||
|
||||
public void removeListener(IPlottableUpdateListener listener)
|
||||
{
|
||||
mListeners.remove(listener);
|
||||
mPlottableEntityModel.removeListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,8 @@ import org.jdesktop.swingx.mapviewer.GeoPosition;
|
||||
*/
|
||||
public class PlottableEntityHistory
|
||||
{
|
||||
private List<GeoPosition> mLocationHistory = new ArrayList<>();
|
||||
public static final int MAX_LOCATION_HISTORY = 10;
|
||||
private List<TimestampedGeoPosition> mLocationHistory = new ArrayList<>();
|
||||
private PlottableDecodeEvent mCurrentEvent;
|
||||
private Identifier mIdentifier;
|
||||
|
||||
@@ -47,11 +48,24 @@ public class PlottableEntityHistory
|
||||
/**
|
||||
* Location history for this entity
|
||||
*/
|
||||
public List<GeoPosition> getLocationHistory()
|
||||
public List<TimestampedGeoPosition> getLocationHistory()
|
||||
{
|
||||
return new ArrayList<>(mLocationHistory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Latest position for this entity.
|
||||
*/
|
||||
public TimestampedGeoPosition getLatestPosition()
|
||||
{
|
||||
if(mLocationHistory.size() > 0)
|
||||
{
|
||||
return mLocationHistory.get(0);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifier for this plottable
|
||||
*/
|
||||
@@ -74,6 +88,11 @@ public class PlottableEntityHistory
|
||||
public void add(PlottableDecodeEvent event)
|
||||
{
|
||||
mCurrentEvent = event;
|
||||
mLocationHistory.add(event.getLocation());
|
||||
mLocationHistory.add(0, new TimestampedGeoPosition(event.getLocation(), event.getTimeStart()));
|
||||
|
||||
while(mLocationHistory.size() > MAX_LOCATION_HISTORY)
|
||||
{
|
||||
mLocationHistory.remove(mLocationHistory.size() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
251
src/main/java/io/github/dsheirer/map/PlottableEntityModel.java
Normal file
251
src/main/java/io/github/dsheirer/map/PlottableEntityModel.java
Normal file
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.map;
|
||||
|
||||
import io.github.dsheirer.alias.Alias;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier;
|
||||
import io.github.dsheirer.module.decode.event.PlottableDecodeEvent;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import java.awt.EventQueue;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
|
||||
/**
|
||||
* Table model for plottable entity history elements.
|
||||
*/
|
||||
public class PlottableEntityModel extends AbstractTableModel implements Listener<PlottableDecodeEvent>
|
||||
{
|
||||
private final static Logger LOGGER = LoggerFactory.getLogger(PlottableEntityModel.class);
|
||||
private static final int COLUMN_ID = 0;
|
||||
private static final int COLUMN_ALIAS = 1;
|
||||
private static final int COLUMN_ALIAS_LIST = 2;
|
||||
private static final String[] COLUMN_NAMES = {"ID", "Alias", "List"};
|
||||
private static final String KEY_NO_ALIAS_LIST = "(no alias list)";
|
||||
private Map<String,PlottableEntityHistory> mEntityHistoryMap = new HashMap();
|
||||
private List<PlottableEntityHistory> mEntityHistories = new ArrayList<>();
|
||||
private List<IPlottableUpdateListener> mPlottableUpdateListeners = new ArrayList<>();
|
||||
private AliasModel mAliasModel;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param aliasModel to lookup aliases
|
||||
*/
|
||||
public PlottableEntityModel(AliasModel aliasModel)
|
||||
{
|
||||
mAliasModel = aliasModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all tracks and histories.
|
||||
*/
|
||||
public void deleteAllTracks()
|
||||
{
|
||||
EventQueue.invokeLater(() -> {
|
||||
if(mEntityHistories.size() > 0)
|
||||
{
|
||||
int firstRow = 0;
|
||||
int lastRow = mEntityHistories.size() - 1;
|
||||
mEntityHistoryMap.clear();
|
||||
mEntityHistories.clear();
|
||||
fireTableRowsDeleted(firstRow, lastRow);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the tracks from both the map and entity histories and fires a table model changed event.
|
||||
* @param tracksToDelete to delete
|
||||
*/
|
||||
public void delete(List<PlottableEntityHistory> tracksToDelete)
|
||||
{
|
||||
EventQueue.invokeLater(() ->
|
||||
{
|
||||
for(PlottableEntityHistory track : tracksToDelete)
|
||||
{
|
||||
int index = mEntityHistories.indexOf(track);
|
||||
mEntityHistories.remove(track);
|
||||
mEntityHistoryMap.entrySet().removeIf(entry -> entry.getValue() == track);
|
||||
fireTableRowsDeleted(index, index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(PlottableDecodeEvent plottableDecodeEvent)
|
||||
{
|
||||
//Add or update the event on the swing event thread
|
||||
EventQueue.invokeLater(() -> {
|
||||
Identifier from = plottableDecodeEvent.getIdentifierCollection().getFromIdentifier();
|
||||
|
||||
if(from != null)
|
||||
{
|
||||
AliasListConfigurationIdentifier aliasList = plottableDecodeEvent.getIdentifierCollection().getAliasListConfiguration();
|
||||
String key = (aliasList != null ? aliasList.toString() : KEY_NO_ALIAS_LIST) + from;
|
||||
|
||||
PlottableEntityHistory entityHistory = mEntityHistoryMap.get(key);
|
||||
|
||||
if(entityHistory == null)
|
||||
{
|
||||
entityHistory = new PlottableEntityHistory(from, plottableDecodeEvent);
|
||||
mEntityHistories.add(entityHistory);
|
||||
mEntityHistoryMap.put(key, entityHistory);
|
||||
int index = mEntityHistories.indexOf(entityHistory);
|
||||
fireTableRowsInserted(index, index);
|
||||
}
|
||||
else
|
||||
{
|
||||
entityHistory.add(plottableDecodeEvent);
|
||||
int index = mEntityHistories.indexOf(entityHistory);
|
||||
fireTableRowsUpdated(index, index);
|
||||
}
|
||||
|
||||
for(IPlottableUpdateListener listener : mPlottableUpdateListeners)
|
||||
{
|
||||
listener.addPlottableEntity(entityHistory);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGGER.warn("Received plottable decode event that does not contain a FROM identifier - cannot plot");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName(int column)
|
||||
{
|
||||
if(0 <= column && column < COLUMN_NAMES.length)
|
||||
{
|
||||
return COLUMN_NAMES[column];
|
||||
}
|
||||
|
||||
return "error!";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCount()
|
||||
{
|
||||
return mEntityHistories.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCount()
|
||||
{
|
||||
return COLUMN_NAMES.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the plottable entity history for the specified model index
|
||||
* @param index in the model
|
||||
* @return entity history or null.
|
||||
*/
|
||||
public PlottableEntityHistory get(int index)
|
||||
{
|
||||
if(index >= 0 && index < mEntityHistories.size())
|
||||
{
|
||||
return mEntityHistories.get(index);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all plottable entity histories.
|
||||
*/
|
||||
public List<PlottableEntityHistory> getAll()
|
||||
{
|
||||
return new ArrayList<>(mEntityHistories);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex)
|
||||
{
|
||||
PlottableEntityHistory history = mEntityHistories.get(rowIndex);
|
||||
|
||||
if(history != null)
|
||||
{
|
||||
switch(columnIndex)
|
||||
{
|
||||
case COLUMN_ID:
|
||||
Identifier identifier = history.getIdentifier();
|
||||
|
||||
if(identifier != null)
|
||||
{
|
||||
return identifier.toString();
|
||||
}
|
||||
else
|
||||
{
|
||||
return "(no ID)";
|
||||
}
|
||||
case COLUMN_ALIAS:
|
||||
Identifier aliasListConfig = history.getIdentifierCollection().getAliasListConfiguration();
|
||||
if(aliasListConfig != null)
|
||||
{
|
||||
AliasList al = mAliasModel.getAliasList(aliasListConfig.toString());
|
||||
|
||||
if(al != null)
|
||||
{
|
||||
List<Alias> aliases = al.getAliases(history.getIdentifier());
|
||||
|
||||
if(!aliases.isEmpty())
|
||||
{
|
||||
return aliases.get(0).getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case COLUMN_ALIAS_LIST:
|
||||
Identifier aliasList = history.getIdentifierCollection().getAliasListConfiguration();
|
||||
if(aliasList != null)
|
||||
{
|
||||
return aliasList.toString();
|
||||
}
|
||||
else
|
||||
{
|
||||
return "(no alias list)";
|
||||
}
|
||||
default:
|
||||
throw new IllegalArgumentException("Unexpected column index");
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void addListener(IPlottableUpdateListener listener)
|
||||
{
|
||||
mPlottableUpdateListeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeListener(IPlottableUpdateListener listener)
|
||||
{
|
||||
mPlottableUpdateListeners.remove(listener);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -16,26 +15,33 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.map;
|
||||
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.icon.IconModel;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Rectangle;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.jdesktop.swingx.JXMapViewer;
|
||||
import org.jdesktop.swingx.painter.AbstractPainter;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Rectangle;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Paints plottable entities to the map.
|
||||
*/
|
||||
public class PlottableEntityPainter extends AbstractPainter<JXMapViewer>
|
||||
{
|
||||
private PlottableEntityRenderer mRenderer;
|
||||
private Set<PlottableEntityHistory> mEntities = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param aliasModel to lookup alias for entities
|
||||
* @param iconModel to lookup icon from alias.
|
||||
*/
|
||||
public PlottableEntityPainter(AliasModel aliasModel, IconModel iconModel)
|
||||
{
|
||||
mRenderer = new PlottableEntityRenderer(aliasModel, iconModel);
|
||||
@@ -43,24 +49,77 @@ public class PlottableEntityPainter extends AbstractPainter<JXMapViewer>
|
||||
setCacheable(false);
|
||||
}
|
||||
|
||||
public void addEntity(PlottableEntityHistory entity)
|
||||
/**
|
||||
* Sets the length of the plotted history trails.
|
||||
* @param length of history trails
|
||||
*/
|
||||
public void setTrackHistoryLength(int length)
|
||||
{
|
||||
mEntities.add(entity);
|
||||
mRenderer.setTrackHistoryLength(length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Current size of the history trail length.
|
||||
*/
|
||||
public int getTrackHistoryLength()
|
||||
{
|
||||
return mRenderer.getTrackHistoryLength();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an entity to the map
|
||||
* @param entity to add
|
||||
*/
|
||||
public boolean addEntity(PlottableEntityHistory entity)
|
||||
{
|
||||
if(entity != null && !mEntities.contains(entity))
|
||||
{
|
||||
mEntities.add(entity);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all entities to this painter
|
||||
* @param entities to add.
|
||||
*/
|
||||
public boolean addAll(List<PlottableEntityHistory> entities)
|
||||
{
|
||||
boolean added = false;
|
||||
for(PlottableEntityHistory entity : entities)
|
||||
{
|
||||
added |= addEntity(entity);
|
||||
}
|
||||
|
||||
return added;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an entity from the map
|
||||
* @param entity to remove
|
||||
*/
|
||||
public void removeEntity(PlottableEntityHistory entity)
|
||||
{
|
||||
mEntities.remove(entity);
|
||||
}
|
||||
|
||||
public void clearEntities()
|
||||
/**
|
||||
* Clears all entities from the map.
|
||||
*/
|
||||
public void clearAllEntities()
|
||||
{
|
||||
mEntities.clear();
|
||||
}
|
||||
|
||||
private Set<PlottableEntityHistory> getEntities()
|
||||
/**
|
||||
* Clears teh specified entities from the map
|
||||
* @param toDelete to delete
|
||||
*/
|
||||
public void clearEntities(List<PlottableEntityHistory> toDelete)
|
||||
{
|
||||
return Collections.unmodifiableSet(mEntities);
|
||||
mEntities.removeAll(toDelete);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -70,9 +129,7 @@ public class PlottableEntityPainter extends AbstractPainter<JXMapViewer>
|
||||
|
||||
g.translate(-viewportBounds.getX(), -viewportBounds.getY());
|
||||
|
||||
Set<PlottableEntityHistory> entities = getEntities();
|
||||
|
||||
for(PlottableEntityHistory entity : entities)
|
||||
for(PlottableEntityHistory entity : mEntities)
|
||||
{
|
||||
mRenderer.paintPlottableEntity(g, map, entity, true);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -15,7 +14,7 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.map;
|
||||
|
||||
@@ -23,10 +22,6 @@ import io.github.dsheirer.alias.Alias;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.icon.IconModel;
|
||||
import org.jdesktop.swingx.JXMapViewer;
|
||||
import org.jdesktop.swingx.mapviewer.GeoPosition;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
@@ -34,21 +29,58 @@ import java.awt.RenderingHints;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.jdesktop.swingx.JXMapViewer;
|
||||
import org.jdesktop.swingx.mapviewer.GeoPosition;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
|
||||
/**
|
||||
* Paints a single plottable entity to the map.
|
||||
*/
|
||||
public class PlottableEntityRenderer
|
||||
{
|
||||
private AliasModel mAliasModel;
|
||||
private IconModel mIconModel;
|
||||
private int mTrackHistoryLength = 3;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param aliasModel for alias lookup for the entity to determine plot color and icon
|
||||
* @param iconModel to retrieve icon for plotting.
|
||||
*/
|
||||
public PlottableEntityRenderer(AliasModel aliasModel, IconModel iconModel)
|
||||
{
|
||||
mAliasModel = aliasModel;
|
||||
mIconModel = iconModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the length of the plotted history trails.
|
||||
* @param length of history trails
|
||||
*/
|
||||
public void setTrackHistoryLength(int length)
|
||||
{
|
||||
mTrackHistoryLength = length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current size of the history trail length.
|
||||
*/
|
||||
public int getTrackHistoryLength()
|
||||
{
|
||||
return mTrackHistoryLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests the plottable entity be painted to the map viewer.
|
||||
* @param g graphics
|
||||
* @param viewer requesting the painting
|
||||
* @param entity to be painted
|
||||
* @param antiAliasing for rendering.
|
||||
*/
|
||||
public void paintPlottableEntity(Graphics2D g, JXMapViewer viewer, PlottableEntityHistory entity, boolean antiAliasing)
|
||||
{
|
||||
List<GeoPosition> locationHistory = entity.getLocationHistory();
|
||||
List<TimestampedGeoPosition> locationHistory = entity.getLocationHistory();
|
||||
|
||||
if(!locationHistory.isEmpty() && locationHistory.get(locationHistory.size() - 1).isValid())
|
||||
{
|
||||
@@ -66,7 +98,7 @@ public class PlottableEntityRenderer
|
||||
/**
|
||||
* Use the entity's preferred color for lines and labels
|
||||
*/
|
||||
Color color = (alias != null ? alias.getDisplayColor() : Color.YELLOW);
|
||||
Color color = (alias != null ? alias.getDisplayColor() : Color.BLUE);
|
||||
graphics.setColor(color);
|
||||
|
||||
/**
|
||||
@@ -79,7 +111,7 @@ public class PlottableEntityRenderer
|
||||
* Convert the lat/long geoposition to an x/y point on the viewer
|
||||
*/
|
||||
|
||||
Point2D point = viewer.getTileFactory().geoToPixel(locationHistory.get(locationHistory.size() - 1), viewer.getZoom());
|
||||
Point2D point = viewer.getTileFactory().geoToPixel(entity.getLatestPosition(), viewer.getZoom());
|
||||
|
||||
/**
|
||||
* Paint the icon at the current location
|
||||
@@ -139,7 +171,7 @@ public class PlottableEntityRenderer
|
||||
*/
|
||||
private void paintRoute(Graphics2D graphics, JXMapViewer viewer, PlottableEntityHistory entity, Color color)
|
||||
{
|
||||
List<GeoPosition> locations = entity.getLocationHistory();
|
||||
List<TimestampedGeoPosition> locations = entity.getLocationHistory();
|
||||
|
||||
if(!locations.isEmpty())
|
||||
{
|
||||
@@ -160,21 +192,26 @@ public class PlottableEntityRenderer
|
||||
/**
|
||||
* Draws a route from a list of plottables
|
||||
*/
|
||||
private void drawRoute(List<GeoPosition> locations, Graphics2D g, JXMapViewer viewer)
|
||||
private void drawRoute(List<TimestampedGeoPosition> locations, Graphics2D g, JXMapViewer viewer)
|
||||
{
|
||||
Point2D lastPoint = null;
|
||||
Point2D previousPoint = null;
|
||||
|
||||
for(GeoPosition location : locations)
|
||||
int length = Math.min(locations.size(), mTrackHistoryLength);
|
||||
|
||||
for(int x = 0; x < length; x++)
|
||||
{
|
||||
GeoPosition location = locations.get(x);
|
||||
|
||||
// convert geo-coordinate to world bitmap pixel
|
||||
Point2D currentPoint = viewer.getTileFactory().geoToPixel(location, viewer.getZoom());
|
||||
|
||||
if(lastPoint != null)
|
||||
if(previousPoint != null)
|
||||
{
|
||||
g.drawLine((int)lastPoint.getX(), (int)lastPoint.getY(), (int)currentPoint.getX(), (int)currentPoint.getY());
|
||||
g.drawLine((int)previousPoint.getX(), (int)previousPoint.getY(), (int)currentPoint.getX(), (int)currentPoint.getY());
|
||||
}
|
||||
|
||||
lastPoint = currentPoint;
|
||||
previousPoint = currentPoint;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package io.github.dsheirer.map;
|
||||
|
||||
import org.jdesktop.swingx.mapviewer.GeoPosition;
|
||||
|
||||
/**
|
||||
* Timestamped geo position
|
||||
*/
|
||||
public class TimestampedGeoPosition extends GeoPosition
|
||||
{
|
||||
private long mTimestamp;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param latitude for the position
|
||||
* @param longitude for the position
|
||||
* @param timestamp in milliseconds
|
||||
*/
|
||||
public TimestampedGeoPosition(GeoPosition position, long timestamp)
|
||||
{
|
||||
super(position.getLatitude(), position.getLongitude());
|
||||
mTimestamp = timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamp for the geo position
|
||||
* @return timestamp in milliseconds.
|
||||
*/
|
||||
public long getTimestamp()
|
||||
{
|
||||
return mTimestamp;
|
||||
}
|
||||
}
|
||||
174
src/main/java/io/github/dsheirer/map/TrackGenerator.java
Normal file
174
src/main/java/io/github/dsheirer/map/TrackGenerator.java
Normal file
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.map;
|
||||
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
||||
import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier;
|
||||
import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier;
|
||||
import io.github.dsheirer.identifier.configuration.SiteConfigurationIdentifier;
|
||||
import io.github.dsheirer.identifier.configuration.SystemConfigurationIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
||||
import io.github.dsheirer.module.decode.event.PlottableDecodeEvent;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.jdesktop.swingx.mapviewer.GeoPosition;
|
||||
|
||||
/**
|
||||
* Map test track data generator.
|
||||
*
|
||||
* Creates plottable events and publishes them to the map service. Issues periodic updates to the plottable events.
|
||||
*/
|
||||
public class TrackGenerator
|
||||
{
|
||||
private static GeoPosition DEFAULT_START_POSITION = new GeoPosition(43.048, -76.147);
|
||||
private static final String ALIAS_LIST_NAME = "DMR Test Alias List";
|
||||
private MapService mMapService;
|
||||
private List<TrackElementGenerator> mTrackElementGenerators = new ArrayList<>();
|
||||
private double mBaseSpeedKPH = 40.0;
|
||||
private int mTrackCount = 25;
|
||||
private ScheduledFuture<?> mGeneratorFuture;
|
||||
|
||||
/**
|
||||
* Constructs an instance to publish tracks to the specified map service
|
||||
* @param mapService to receive test tracks.
|
||||
*/
|
||||
public TrackGenerator(MapService mapService)
|
||||
{
|
||||
mMapService = mapService;
|
||||
|
||||
MutableIdentifierCollection base = new MutableIdentifierCollection();
|
||||
base.update(SystemConfigurationIdentifier.create("Test System"));
|
||||
base.update(SiteConfigurationIdentifier.create("Test Site"));
|
||||
base.update(FrequencyConfigurationIdentifier.create(155000000l));
|
||||
base.update(AliasListConfigurationIdentifier.create(ALIAS_LIST_NAME));
|
||||
|
||||
Random random = new Random();
|
||||
|
||||
for(int x = 0; x < mTrackCount; x++)
|
||||
{
|
||||
MutableIdentifierCollection mic = new MutableIdentifierCollection(base.getIdentifiers());
|
||||
mic.update(DMRRadio.createFrom(x + 1));
|
||||
double speedKPH = mBaseSpeedKPH + (random.nextDouble() * 15);
|
||||
mTrackElementGenerators.add(new TrackElementGenerator(speedKPH, mic));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the test generator
|
||||
*/
|
||||
public void start()
|
||||
{
|
||||
if(mGeneratorFuture == null)
|
||||
{
|
||||
mGeneratorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(() -> update(), 0, 1, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the test generator
|
||||
*/
|
||||
public void stop()
|
||||
{
|
||||
if(mGeneratorFuture != null)
|
||||
{
|
||||
mGeneratorFuture.cancel(true);
|
||||
mGeneratorFuture = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates each of the track generates causing them to dispatch a new decode event to the map service.
|
||||
*
|
||||
* Invoke this method once per second.
|
||||
*/
|
||||
private void update()
|
||||
{
|
||||
for(TrackElementGenerator tg: mTrackElementGenerators)
|
||||
{
|
||||
mMapService.receive(tg.update());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test track element generator
|
||||
*/
|
||||
public static class TrackElementGenerator
|
||||
{
|
||||
public static double EARTH_RADIUS_KM = 6378.137;
|
||||
public static double ONE_SECOND = 1.0 / 60.0 / 60.0; //1 hour divided by 60 minutes divided by 60 seconds.
|
||||
private IdentifierCollection mIdentifierCollection;
|
||||
private double mSpeedKPH;
|
||||
private GeoPosition mPosition = new GeoPosition(DEFAULT_START_POSITION.getLatitude(), DEFAULT_START_POSITION.getLongitude());
|
||||
private Random mRandom = new Random();
|
||||
private double mHeading = 360.0 * mRandom.nextDouble();
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param trackId for the track
|
||||
* @param speedKPH speed in KPH
|
||||
*/
|
||||
public TrackElementGenerator(double speedKPH, IdentifierCollection identifierCollection)
|
||||
{
|
||||
mSpeedKPH = speedKPH;
|
||||
mIdentifierCollection = identifierCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param heading 0-360 degrees
|
||||
*/
|
||||
public PlottableDecodeEvent update()
|
||||
{
|
||||
mHeading = mHeading + (15.0 - (mRandom.nextDouble() * 30.0));
|
||||
mHeading = Math.max(mHeading, 0);
|
||||
mHeading = Math.min(mHeading, 360.0);
|
||||
double distanceKM = mSpeedKPH * ONE_SECOND;
|
||||
double headingRadians = Math.toRadians(mHeading);
|
||||
|
||||
double angularDistance = distanceKM / EARTH_RADIUS_KM;
|
||||
double latRadians = Math.toRadians(mPosition.getLatitude());
|
||||
double lonRadians = Math.toRadians(mPosition.getLongitude());
|
||||
double latitude = Math.asin((Math.sin(latRadians) * Math.cos(angularDistance)) +
|
||||
(Math.cos(latRadians) * Math.sin(angularDistance) * Math.cos(headingRadians)));
|
||||
double longitude = lonRadians + Math.atan2(Math.sin(headingRadians) * Math.sin(angularDistance) *
|
||||
Math.cos(latRadians), Math.cos(angularDistance) - Math.sin(latRadians) * Math.sin(latitude));
|
||||
|
||||
latitude = Math.toDegrees(latitude);
|
||||
longitude = Math.toDegrees(longitude);
|
||||
mPosition = new GeoPosition(latitude, longitude);
|
||||
PlottableDecodeEvent event =
|
||||
new PlottableDecodeEvent.PlottableDecodeEventBuilder(DecodeEventType.GPS, System.currentTimeMillis())
|
||||
.heading(mHeading)
|
||||
.speed(mSpeedKPH)
|
||||
.location(mPosition)
|
||||
.protocol(Protocol.DMR)
|
||||
.identifiers(mIdentifierCollection)
|
||||
.build();
|
||||
return event;
|
||||
}
|
||||
}
|
||||
}
|
||||
126
src/main/java/io/github/dsheirer/map/TrackHistoryModel.java
Normal file
126
src/main/java/io/github/dsheirer/map/TrackHistoryModel.java
Normal file
@@ -0,0 +1,126 @@
|
||||
package io.github.dsheirer.map;
|
||||
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.function.ToLongFunction;
|
||||
|
||||
/**
|
||||
* Table model for track history.
|
||||
*/
|
||||
public class TrackHistoryModel extends AbstractTableModel
|
||||
{
|
||||
private static final String[] COLUMNS = new String[]{"Time", "Latitude", "Longitude"};
|
||||
private SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat("HH:mm:ss");
|
||||
private DecimalFormat mDegreeFormat = new DecimalFormat("0.00000");
|
||||
private List<TimestampedGeoPosition> mTimestampedGeoPositions = new ArrayList<>();
|
||||
private PlottableEntityHistory mPlottableEntityHistory;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public TrackHistoryModel()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the geo position at the specified index
|
||||
* @param index to retrieve
|
||||
* @return geo or null.
|
||||
*/
|
||||
public TimestampedGeoPosition get(int index)
|
||||
{
|
||||
if(index < mTimestampedGeoPositions.size())
|
||||
{
|
||||
return mTimestampedGeoPositions.get(index);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the plottable entity history into this model
|
||||
* @param plottableEntityHistory to load
|
||||
*/
|
||||
public void load(PlottableEntityHistory plottableEntityHistory)
|
||||
{
|
||||
mPlottableEntityHistory = plottableEntityHistory;
|
||||
|
||||
if(mTimestampedGeoPositions.size() > 0)
|
||||
{
|
||||
int lastRow = mTimestampedGeoPositions.size() - 1;
|
||||
mTimestampedGeoPositions.clear();
|
||||
fireTableRowsDeleted(0, lastRow);
|
||||
}
|
||||
|
||||
if(mPlottableEntityHistory != null)
|
||||
{
|
||||
mTimestampedGeoPositions.addAll(mPlottableEntityHistory.getLocationHistory());
|
||||
|
||||
if(mTimestampedGeoPositions.size() > 0)
|
||||
{
|
||||
fireTableRowsInserted(0, mTimestampedGeoPositions.size() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates or refreshes the track history from the current plottable entity.
|
||||
*/
|
||||
public void update()
|
||||
{
|
||||
if(mPlottableEntityHistory != null)
|
||||
{
|
||||
List<TimestampedGeoPosition> geos = mPlottableEntityHistory.getLocationHistory();
|
||||
Collections.reverse(geos);
|
||||
|
||||
for(TimestampedGeoPosition geo: geos)
|
||||
{
|
||||
if(!mTimestampedGeoPositions.contains(geo))
|
||||
{
|
||||
mTimestampedGeoPositions.add(0, geo);
|
||||
fireTableRowsInserted(0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCount()
|
||||
{
|
||||
return mTimestampedGeoPositions.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCount()
|
||||
{
|
||||
return COLUMNS.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName(int column)
|
||||
{
|
||||
return COLUMNS[column];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex)
|
||||
{
|
||||
TimestampedGeoPosition geoPosition = mTimestampedGeoPositions.get(rowIndex);
|
||||
switch (columnIndex)
|
||||
{
|
||||
case 0:
|
||||
return mSimpleDateFormat.format(geoPosition.getTimestamp());
|
||||
case 1:
|
||||
return mDegreeFormat.format(geoPosition.getLatitude());
|
||||
case 2:
|
||||
return mDegreeFormat.format(geoPosition.getLongitude());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.p25.phase2.timeslot;
|
||||
@@ -63,9 +60,6 @@ public class LinearFeedbackShiftRegister
|
||||
mSystem = system;
|
||||
mNac = nac;
|
||||
|
||||
int temp = 0xFFFFF & 1;
|
||||
long tempShift = temp << 24;
|
||||
|
||||
mRegisters = (long)(0xFFFFF & wacn) << 24;
|
||||
mRegisters += (0xFFF & system) << 12;
|
||||
mRegisters += (0xFFF & nac);
|
||||
@@ -78,6 +72,16 @@ public class LinearFeedbackShiftRegister
|
||||
mCurrentOutput = getTap(TAP_43);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the seed value directly into the registers.
|
||||
* @param seed for the lfsr
|
||||
*/
|
||||
public void updateSeed(long seed)
|
||||
{
|
||||
mRegisters = seed;
|
||||
mCurrentOutput = getTap(TAP_43);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the shift register is currently configured for the argument values (and doesn't need updating).
|
||||
*
|
||||
@@ -120,6 +124,32 @@ public class LinearFeedbackShiftRegister
|
||||
return sequence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a (de)scrambling sequence for the specified seed and length
|
||||
* @param seed value to use
|
||||
* @param length of the generated sequence
|
||||
* @return scrambling sequence in a binary message
|
||||
*/
|
||||
public BinaryMessage generateScramblingSequence(long seed, int length)
|
||||
{
|
||||
updateSeed(seed);
|
||||
BinaryMessage sequence = new BinaryMessage(length);
|
||||
|
||||
try
|
||||
{
|
||||
for(int x = 0; x < length; x++)
|
||||
{
|
||||
sequence.add(next());
|
||||
}
|
||||
}
|
||||
catch(BitSetFullException e)
|
||||
{
|
||||
//This shouldn't happen
|
||||
}
|
||||
|
||||
return sequence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the next output bit from the LFSR
|
||||
*/
|
||||
@@ -152,4 +182,39 @@ public class LinearFeedbackShiftRegister
|
||||
{
|
||||
return (mRegisters & tap) == tap;
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
long seed = 0;
|
||||
int wacn = 0xBEE00;
|
||||
int system = 0x1C7;
|
||||
int nac = 0x1C1;
|
||||
|
||||
seed = (long)(0xFFFFF & wacn) << 24;
|
||||
seed += (0xFFF & system) << 12;
|
||||
seed += (0xFFF & nac);
|
||||
|
||||
seed = 0xBEE001C7013l;
|
||||
|
||||
System.out.println("Seed: " + Long.toHexString(seed).toUpperCase());
|
||||
|
||||
LinearFeedbackShiftRegister lfsr = new LinearFeedbackShiftRegister();
|
||||
// BinaryMessage scramble = lfsr.generateScramblingSequence(0xBEE07, 0x40F, 0x04E);
|
||||
BinaryMessage scramble = lfsr.generateScramblingSequence(seed, 400);
|
||||
System.out.println("SCRAM: " + scramble.toHexString());
|
||||
// scramble.rotateRight(64, 0, 401);
|
||||
System.out.println("SCRAM: " + scramble.toHexString());
|
||||
|
||||
BinaryMessage raw1 = BinaryMessage.loadHex("BEE001C70139CB7D5F2D4823695F7ED499EA998F8748E6DAB167FAC15EC2C6222E");
|
||||
// BinaryMessage raw1 = BinaryMessage.loadHex("BEE0740F04E0172D21681A1B52FFBFBFEE53D2A5ADB9561CADF4D955EBF1CB0000");
|
||||
// BinaryMessage raw2 = BinaryMessage.loadHex("BEE0740F04E0DD2D21681A1B52FFBFBFEE53FE86BEF78FD5AB910B2376F9D80000");
|
||||
System.out.println(" RAW: " + raw1.toHexString());
|
||||
System.out.println(" xxx: BEE001C70139CB");
|
||||
int length = raw1.length();
|
||||
|
||||
|
||||
raw1.xor(scramble);
|
||||
BinaryMessage descrambled = raw1.get(0, length);
|
||||
System.out.println("DESCR: " + descrambled.toHexString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -31,6 +28,20 @@
|
||||
|
||||
package org.jdesktop.swingx;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.Insets;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.beans.DesignMode;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.util.Set;
|
||||
import jiconfont.icons.font_awesome.FontAwesome;
|
||||
import jiconfont.swing.IconFontSwing;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
@@ -46,20 +57,6 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.Insets;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.beans.DesignMode;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A tile oriented map component that can easily be used with tile sources
|
||||
@@ -87,8 +84,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
||||
{
|
||||
private static final long serialVersionUID = -3530746298586937321L;
|
||||
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( JXMapViewer.class );
|
||||
private final static Logger mLog = LoggerFactory.getLogger(JXMapViewer.class);
|
||||
|
||||
private final boolean isNegativeYAllowed = true; // maybe rename to isNorthBounded and isSouthBounded?
|
||||
|
||||
@@ -96,25 +92,25 @@ public class JXMapViewer extends JPanel implements DesignMode
|
||||
* The zoom level. Generally a value between 1 and 15 (TODO Is this true for all the mapping worlds? What does this
|
||||
* mean if some mapping system doesn't support the zoom level?
|
||||
*/
|
||||
private int zoomLevel = 1;
|
||||
private int mZoomLevel = 1;
|
||||
|
||||
/**
|
||||
* The position, in <I>map coordinates</I> of the center point. This is defined as the distance from the top and
|
||||
* left edges of the map in pixels. Dragging the map component will change the center position. Zooming in/out will
|
||||
* cause the center to be recalculated so as to remain in the center of the new "map".
|
||||
*/
|
||||
private Point2D center = new Point2D.Double(0, 0);
|
||||
private Point2D mCenter = new Point2D.Double(0, 0);
|
||||
|
||||
/**
|
||||
* Indicates whether or not to draw the borders between tiles. Defaults to false. TODO Generally not very nice
|
||||
* looking, very much a product of testing Consider whether this should really be a property or not.
|
||||
*/
|
||||
private boolean drawTileBorders = false;
|
||||
private boolean mDrawTileBorders = false;
|
||||
|
||||
/**
|
||||
* Factory used by this component to grab the tiles necessary for painting the map.
|
||||
*/
|
||||
private TileFactory factory;
|
||||
private TileFactory mTileFactory;
|
||||
|
||||
/**
|
||||
* The position in latitude/longitude of the "address" being mapped. This is a special coordinate that, when moved,
|
||||
@@ -122,27 +118,27 @@ public class JXMapViewer extends JPanel implements DesignMode
|
||||
* (in pixels) of the viewport whereas this will not change when panning or zooming. Whenever the addressLocation is
|
||||
* changed, however, the map will be repositioned.
|
||||
*/
|
||||
private GeoPosition addressLocation;
|
||||
private GeoPosition mAddressLocation;
|
||||
|
||||
/**
|
||||
* The overlay to delegate to for painting the "foreground" of the map component. This would include painting
|
||||
* waypoints, day/night, etc. Also receives mouse events.
|
||||
*/
|
||||
private Painter<? super JXMapViewer> overlay;
|
||||
private Painter<? super JXMapViewer> mOverlay;
|
||||
|
||||
private boolean designTime;
|
||||
private boolean mDesignTime;
|
||||
|
||||
private Image loadingImage;
|
||||
private Image mLoadingImage;
|
||||
|
||||
private boolean restrictOutsidePanning = true;
|
||||
private boolean horizontalWrapped = true;
|
||||
private boolean mRestrictOutsidePanning = true;
|
||||
private boolean mHorizontalWrapped = true;
|
||||
|
||||
/**
|
||||
* Create a new JXMapViewer. By default it will use the EmptyTileFactory
|
||||
*/
|
||||
public JXMapViewer()
|
||||
{
|
||||
factory = new EmptyTileFactory();
|
||||
mTileFactory = new EmptyTileFactory();
|
||||
// setTileFactory(new GoogleTileFactory());
|
||||
|
||||
// make a dummy loading image
|
||||
@@ -153,9 +149,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
||||
}
|
||||
catch (Throwable ex)
|
||||
{
|
||||
|
||||
mLog.error( "JXMapViewer could not load default 'loading.png'" );
|
||||
|
||||
BufferedImage img = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2 = img.createGraphics();
|
||||
g2.setColor(Color.black);
|
||||
@@ -203,7 +197,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
||||
@Override
|
||||
public void setDesignTime(boolean b)
|
||||
{
|
||||
this.designTime = b;
|
||||
this.mDesignTime = b;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -213,7 +207,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
||||
@Override
|
||||
public boolean isDesignTime()
|
||||
{
|
||||
return designTime;
|
||||
return mDesignTime;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -313,9 +307,9 @@ public class JXMapViewer extends JPanel implements DesignMode
|
||||
@SuppressWarnings("unused")
|
||||
private void drawOverlays(final int zoom, final Graphics g, final Rectangle viewportBounds)
|
||||
{
|
||||
if (overlay != null)
|
||||
if (mOverlay != null)
|
||||
{
|
||||
overlay.paint((Graphics2D) g, this, getWidth(), getHeight());
|
||||
mOverlay.paint((Graphics2D) g, this, getWidth(), getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,7 +327,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
||||
public void setOverlayPainter(Painter<? super JXMapViewer> overlay)
|
||||
{
|
||||
Painter<? super JXMapViewer> old = getOverlayPainter();
|
||||
this.overlay = overlay;
|
||||
this.mOverlay = overlay;
|
||||
|
||||
PropertyChangeListener listener = new PropertyChangeListener()
|
||||
{
|
||||
@@ -369,7 +363,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
||||
*/
|
||||
public Painter<? super JXMapViewer> getOverlayPainter()
|
||||
{
|
||||
return overlay;
|
||||
return mOverlay;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -399,7 +393,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
||||
*/
|
||||
public void setZoom(int zoom)
|
||||
{
|
||||
if (zoom == this.zoomLevel)
|
||||
if (zoom == this.mZoomLevel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -412,10 +406,10 @@ public class JXMapViewer extends JPanel implements DesignMode
|
||||
}
|
||||
|
||||
// if(zoom >= 0 && zoom <= 15 && zoom != this.zoom) {
|
||||
int oldzoom = this.zoomLevel;
|
||||
int oldzoom = this.mZoomLevel;
|
||||
Point2D oldCenter = getCenter();
|
||||
Dimension oldMapSize = getTileFactory().getMapSize(oldzoom);
|
||||
this.zoomLevel = zoom;
|
||||
this.mZoomLevel = zoom;
|
||||
this.firePropertyChange("zoom", oldzoom, zoom);
|
||||
|
||||
Dimension mapSize = getTileFactory().getMapSize(zoom);
|
||||
@@ -432,7 +426,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
||||
*/
|
||||
public int getZoom()
|
||||
{
|
||||
return this.zoomLevel;
|
||||
return this.mZoomLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -442,7 +436,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
||||
*/
|
||||
public GeoPosition getAddressLocation()
|
||||
{
|
||||
return addressLocation;
|
||||
return mAddressLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -452,7 +446,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
||||
public void setAddressLocation(GeoPosition addressLocation)
|
||||
{
|
||||
GeoPosition old = getAddressLocation();
|
||||
this.addressLocation = addressLocation;
|
||||
this.mAddressLocation = addressLocation;
|
||||
setCenter(getTileFactory().geoToPixel(addressLocation, getZoom()));
|
||||
|
||||
firePropertyChange("addressLocation", old, getAddressLocation());
|
||||
@@ -475,7 +469,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
||||
*/
|
||||
public boolean isDrawTileBorders()
|
||||
{
|
||||
return drawTileBorders;
|
||||
return mDrawTileBorders;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -485,7 +479,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
||||
public void setDrawTileBorders(boolean drawTileBorders)
|
||||
{
|
||||
boolean old = isDrawTileBorders();
|
||||
this.drawTileBorders = drawTileBorders;
|
||||
this.mDrawTileBorders = drawTileBorders;
|
||||
firePropertyChange("drawTileBorders", old, isDrawTileBorders());
|
||||
repaint();
|
||||
}
|
||||
@@ -497,7 +491,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
||||
public void setCenterPosition(GeoPosition geoPosition)
|
||||
{
|
||||
GeoPosition oldVal = getCenterPosition();
|
||||
setCenter(getTileFactory().geoToPixel(geoPosition, zoomLevel));
|
||||
setCenter(getTileFactory().geoToPixel(geoPosition, mZoomLevel));
|
||||
repaint();
|
||||
GeoPosition newVal = getCenterPosition();
|
||||
firePropertyChange("centerPosition", oldVal, newVal);
|
||||
@@ -509,7 +503,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
||||
*/
|
||||
public GeoPosition getCenterPosition()
|
||||
{
|
||||
return getTileFactory().pixelToGeo(getCenter(), zoomLevel);
|
||||
return getTileFactory().pixelToGeo(getCenter(), mZoomLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -518,7 +512,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
||||
*/
|
||||
public TileFactory getTileFactory()
|
||||
{
|
||||
return factory;
|
||||
return mTileFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -530,10 +524,10 @@ public class JXMapViewer extends JPanel implements DesignMode
|
||||
if (factory == null)
|
||||
throw new NullPointerException("factory must not be null");
|
||||
|
||||
this.factory.removeTileListener(tileLoadListener);
|
||||
this.factory.dispose();
|
||||
this.mTileFactory.removeTileListener(tileLoadListener);
|
||||
this.mTileFactory.dispose();
|
||||
|
||||
this.factory = factory;
|
||||
this.mTileFactory = factory;
|
||||
this.setZoom(factory.getInfo().getDefaultZoomLevel());
|
||||
|
||||
factory.addTileListener(tileLoadListener);
|
||||
@@ -547,7 +541,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
||||
*/
|
||||
public Image getLoadingImage()
|
||||
{
|
||||
return loadingImage;
|
||||
return mLoadingImage;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -556,7 +550,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
||||
*/
|
||||
public void setLoadingImage(Image loadingImage)
|
||||
{
|
||||
this.loadingImage = loadingImage;
|
||||
this.mLoadingImage = loadingImage;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -565,7 +559,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
||||
*/
|
||||
public Point2D getCenter()
|
||||
{
|
||||
return center;
|
||||
return mCenter;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -641,8 +635,8 @@ public class JXMapViewer extends JPanel implements DesignMode
|
||||
}
|
||||
|
||||
GeoPosition oldGP = this.getCenterPosition();
|
||||
this.center = new Point2D.Double(centerX, centerY);
|
||||
firePropertyChange("center", old, this.center);
|
||||
this.mCenter = new Point2D.Double(centerX, centerY);
|
||||
firePropertyChange("center", old, this.mCenter);
|
||||
firePropertyChange("centerPosition", oldGP, this.getCenterPosition());
|
||||
repaint();
|
||||
}
|
||||
@@ -739,7 +733,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
||||
*/
|
||||
public boolean isRestrictOutsidePanning()
|
||||
{
|
||||
return restrictOutsidePanning;
|
||||
return mRestrictOutsidePanning;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -747,7 +741,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
||||
*/
|
||||
public void setRestrictOutsidePanning(boolean restrictOutsidePanning)
|
||||
{
|
||||
this.restrictOutsidePanning = restrictOutsidePanning;
|
||||
this.mRestrictOutsidePanning = restrictOutsidePanning;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -755,7 +749,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
||||
*/
|
||||
public boolean isHorizontalWrapped()
|
||||
{
|
||||
return horizontalWrapped;
|
||||
return mHorizontalWrapped;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -763,7 +757,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
||||
*/
|
||||
public void setHorizontalWrapped(boolean horizontalWrapped)
|
||||
{
|
||||
this.horizontalWrapped = horizontalWrapped;
|
||||
this.mHorizontalWrapped = horizontalWrapped;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
******************************************************************************/
|
||||
package org.jdesktop.swingx.input;
|
||||
|
||||
import io.github.dsheirer.map.MapPanel;
|
||||
import org.jdesktop.swingx.JXMapViewer;
|
||||
|
||||
import java.awt.*;
|
||||
@@ -31,32 +32,34 @@ import java.awt.geom.Point2D;
|
||||
*/
|
||||
public class ZoomMouseWheelListenerCursor implements MouseWheelListener
|
||||
{
|
||||
private JXMapViewer viewer;
|
||||
|
||||
private MapPanel mMapPanel;
|
||||
private JXMapViewer mViewer;
|
||||
|
||||
/**
|
||||
* @param viewer the jxmapviewer
|
||||
*/
|
||||
public ZoomMouseWheelListenerCursor(JXMapViewer viewer)
|
||||
public ZoomMouseWheelListenerCursor(MapPanel mapPanel)
|
||||
{
|
||||
this.viewer = viewer;
|
||||
mMapPanel = mapPanel;
|
||||
mViewer = mMapPanel.getMapViewer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseWheelMoved(MouseWheelEvent evt)
|
||||
{
|
||||
Point current = evt.getPoint();
|
||||
Rectangle bound = viewer.getViewportBounds();
|
||||
Rectangle bound = mViewer.getViewportBounds();
|
||||
|
||||
double dx = current.x - bound.width / 2;
|
||||
double dy = current.y - bound.height / 2;
|
||||
|
||||
Dimension oldMapSize = viewer.getTileFactory().getMapSize(viewer.getZoom());
|
||||
Dimension oldMapSize = mViewer.getTileFactory().getMapSize(mViewer.getZoom());
|
||||
|
||||
viewer.setZoom(viewer.getZoom() + evt.getWheelRotation());
|
||||
|
||||
Dimension mapSize = viewer.getTileFactory().getMapSize(viewer.getZoom());
|
||||
mMapPanel.adjustZoom(evt.getWheelRotation());
|
||||
|
||||
Point2D center = viewer.getCenter();
|
||||
Dimension mapSize = mViewer.getTileFactory().getMapSize(mViewer.getZoom());
|
||||
|
||||
Point2D center = mViewer.getCenter();
|
||||
|
||||
double dzw = (mapSize.getWidth() / oldMapSize.getWidth());
|
||||
double dzh = (mapSize.getHeight() / oldMapSize.getHeight());
|
||||
@@ -64,6 +67,6 @@ public class ZoomMouseWheelListenerCursor implements MouseWheelListener
|
||||
double x = center.getX() + dx * (dzw - 1);
|
||||
double y = center.getY() + dy * (dzh - 1);
|
||||
|
||||
viewer.setCenter(new Point2D.Double(x, y));
|
||||
mViewer.setCenter(new Point2D.Double(x, y));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
/*
|
||||
* GeoPosition.java
|
||||
*
|
||||
@@ -33,7 +52,6 @@ public class GeoPosition
|
||||
this.latitude = latitude;
|
||||
this.longitude = longitude;
|
||||
}
|
||||
// must be an array of length two containing lat then long in that order.
|
||||
|
||||
/**
|
||||
* Creates a new instance of GeoPosition from the specified
|
||||
@@ -89,6 +107,36 @@ public class GeoPosition
|
||||
return longitude;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the position as Degrees Minutes Seconds (DMS)
|
||||
*/
|
||||
public String toDMS()
|
||||
{
|
||||
int latDegrees = (int)Math.floor(Math.abs(latitude));
|
||||
double latMinutes = (Math.abs(latitude) - latDegrees) * 60.0;
|
||||
int latMinutesInt = (int)Math.floor(latMinutes);
|
||||
double latSeconds = (latMinutes - latMinutesInt) * 60.0;
|
||||
int latSecondsInt = (int)latSeconds;
|
||||
|
||||
int lonDegrees = (int)Math.floor(Math.abs(longitude));
|
||||
double lonMinutes = (Math.abs(longitude) - lonDegrees) * 60.0;
|
||||
int lonMinutesInt = (int)Math.floor(lonMinutes);
|
||||
double lonSeconds = (lonMinutes - lonMinutesInt) * 60.0;
|
||||
int lonSecondsInt = (int)lonSeconds;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(latDegrees).append("°");
|
||||
sb.append(latMinutesInt).append("'");
|
||||
sb.append(latSecondsInt).append("");
|
||||
sb.append(latitude >= 0 ? "N" : "S");
|
||||
sb.append(", ");
|
||||
sb.append(lonDegrees).append("°");
|
||||
sb.append(lonMinutesInt).append("'");
|
||||
sb.append(lonSecondsInt).append("");
|
||||
sb.append(longitude >= 0 ? "E" : "W");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user