mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-11 16: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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user