mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-14 09:37:36 -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(mAudioRecordingManager);
|
||||||
mPlaylistManager.getChannelProcessingManager().addAudioSegmentListener(mAudioStreamingManager);
|
mPlaylistManager.getChannelProcessingManager().addAudioSegmentListener(mAudioStreamingManager);
|
||||||
|
|
||||||
MapService mapService = new MapService(mIconModel);
|
MapService mapService = new MapService(aliasModel, mIconModel);
|
||||||
mPlaylistManager.getChannelProcessingManager().addDecodeEventListener(mapService);
|
mPlaylistManager.getChannelProcessingManager().addDecodeEventListener(mapService);
|
||||||
|
|
||||||
mNowPlayingDetailsVisible = mPreferences.getBoolean(PREFERENCE_NOW_PLAYING_DETAILS_VISIBLE, true);
|
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
|
* 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
|
* 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
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
*
|
* ****************************************************************************
|
||||||
******************************************************************************/
|
*/
|
||||||
package io.github.dsheirer.map;
|
package io.github.dsheirer.map;
|
||||||
|
|
||||||
import io.github.dsheirer.alias.AliasModel;
|
import io.github.dsheirer.alias.AliasModel;
|
||||||
import io.github.dsheirer.icon.IconModel;
|
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.MapViewSetting;
|
||||||
import io.github.dsheirer.settings.SettingsManager;
|
import io.github.dsheirer.settings.SettingsManager;
|
||||||
import net.miginfocom.swing.MigLayout;
|
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.GeoPosition;
|
||||||
import org.jdesktop.swingx.mapviewer.TileFactoryInfo;
|
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.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.awt.EventQueue;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Swing map panel.
|
||||||
|
*/
|
||||||
public class MapPanel extends JPanel implements IPlottableUpdateListener
|
public class MapPanel extends JPanel implements IPlottableUpdateListener
|
||||||
{
|
{
|
||||||
private static final long serialVersionUID = 1L;
|
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 SettingsManager mSettingsManager;
|
||||||
private MapService mMapService;
|
private MapService mMapService;
|
||||||
private JXMapViewer mMapViewer = new JXMapViewer();
|
private JXMapViewer mMapViewer;
|
||||||
private PlottableEntityPainter mMapPainter;
|
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)
|
public MapPanel(MapService mapService, AliasModel aliasModel, IconModel iconModel, SettingsManager settingsManager)
|
||||||
{
|
{
|
||||||
mSettingsManager = settingsManager;
|
mSettingsManager = settingsManager;
|
||||||
@@ -55,62 +108,548 @@ public class MapPanel extends JPanel implements IPlottableUpdateListener
|
|||||||
private void init()
|
private void init()
|
||||||
{
|
{
|
||||||
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
|
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);
|
mMapService.addListener(this);
|
||||||
|
|
||||||
/**
|
JPanel cp = new JPanel();
|
||||||
* Map image source
|
cp.setLayout(new MigLayout("insets 5", "[grow,fill][grow,fill][grow,fill]",
|
||||||
*/
|
"[grow,fill][][][][][][grow,fill][]"));
|
||||||
TileFactoryInfo info = new OSMTileFactoryInfo();
|
cp.add(new JScrollPane(getPlottedTracksTable()), "span 3,wrap");
|
||||||
DefaultTileFactory tileFactory = new DefaultTileFactory(info);
|
|
||||||
mMapViewer.setTileFactory(tileFactory);
|
|
||||||
|
|
||||||
/**
|
cp.add(getMapZoomSpinner());
|
||||||
* Defines how many threads will be used to fetch the background map
|
JPanel zoomCenterPanel = new JPanel();
|
||||||
* tiles (graphics)
|
zoomCenterPanel.setLayout(new MigLayout("insets 0", "[]5[grow,fill]", ""));
|
||||||
*/
|
zoomCenterPanel.add(new JLabel("Zoom"));
|
||||||
tileFactory.setThreadPoolSize(8);
|
zoomCenterPanel.add(getCenterOnSelectedCheckBox());
|
||||||
|
cp.add(zoomCenterPanel, "span 2, wrap");
|
||||||
|
|
||||||
/**
|
cp.add(getDeleteTrackButton());
|
||||||
* Set initial location and zoom for the map upon display
|
cp.add(getDeleteAllTracksButton());
|
||||||
*/
|
cp.add(getClearMapButton(), "wrap");
|
||||||
GeoPosition syracuse = new GeoPosition(43.048, -76.147);
|
|
||||||
int zoom = 7;
|
|
||||||
|
|
||||||
MapViewSetting view = mSettingsManager.getMapViewSetting("Default", syracuse, zoom);
|
cp.add(getReplotAllTracksButton());
|
||||||
|
cp.add(getFollowButton());
|
||||||
|
cp.add(getFollowedEntityLabel(), "wrap");
|
||||||
|
|
||||||
mMapViewer.setAddressLocation(view.getGeoPosition());
|
cp.add(getTrackHistoryLengthComboBox());
|
||||||
mMapViewer.setZoom(view.getZoom());
|
cp.add(new JLabel("Track History Length"), "span 2, wrap");
|
||||||
|
|
||||||
/**
|
cp.add(new JLabel("Selected System:"), "align right");
|
||||||
* Add a mouse adapter for panning and scrolling
|
cp.add(getSelectedTrackSystemLabel(), "span 2, wrap");
|
||||||
*/
|
|
||||||
MapMouseListener listener = new MapMouseListener(mMapViewer, mSettingsManager);
|
|
||||||
mMapViewer.addMouseListener(listener);
|
|
||||||
mMapViewer.addMouseMotionListener(listener);
|
|
||||||
|
|
||||||
/* Map zoom listener */
|
cp.add(new JScrollPane(getTrackHistoryTable()), "span 3,wrap");
|
||||||
mMapViewer.addMouseWheelListener(new ZoomMouseWheelListenerCursor(mMapViewer));
|
|
||||||
|
|
||||||
/* Keyboard panning listener */
|
// cp.add(getTrackGeneratorToggle(), "span 3,wrap");
|
||||||
mMapViewer.addKeyListener(new PanKeyListener(mMapViewer));
|
|
||||||
|
|
||||||
/**
|
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
|
||||||
* Add a selection listener
|
splitPane.setDividerLocation(300);
|
||||||
*/
|
splitPane.add(cp);
|
||||||
SelectionAdapter sa = new SelectionAdapter(mMapViewer);
|
splitPane.add(getMapViewer());
|
||||||
mMapViewer.addMouseListener(sa);
|
add(splitPane, "span");
|
||||||
mMapViewer.addMouseMotionListener(sa);
|
}
|
||||||
|
|
||||||
/**
|
private void setSelected(PlottableEntityHistory selected)
|
||||||
* Map component
|
{
|
||||||
*/
|
mTrackHistoryModel.load(selected);
|
||||||
add(mMapViewer, "span");
|
|
||||||
|
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
|
@Override
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* 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
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* *****************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
package io.github.dsheirer.map;
|
package io.github.dsheirer.map;
|
||||||
|
|
||||||
|
import io.github.dsheirer.alias.AliasModel;
|
||||||
import io.github.dsheirer.icon.IconModel;
|
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.IDecodeEvent;
|
||||||
import io.github.dsheirer.module.decode.event.PlottableDecodeEvent;
|
import io.github.dsheirer.module.decode.event.PlottableDecodeEvent;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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>
|
public class MapService implements Listener<IDecodeEvent>
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(MapService.class);
|
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 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
|
@Override
|
||||||
public void receive(IDecodeEvent decodeEvent)
|
public void receive(IDecodeEvent decodeEvent)
|
||||||
{
|
{
|
||||||
if(decodeEvent instanceof PlottableDecodeEvent)
|
if(decodeEvent instanceof PlottableDecodeEvent plottableDecodeEvent)
|
||||||
{
|
{
|
||||||
PlottableDecodeEvent plottableDecodeEvent = (PlottableDecodeEvent)decodeEvent;
|
mPlottableEntityModel.receive(plottableDecodeEvent);
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addListener(IPlottableUpdateListener listener)
|
public void addListener(IPlottableUpdateListener listener)
|
||||||
{
|
{
|
||||||
mListeners.add(listener);
|
mPlottableEntityModel.addListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeListener(IPlottableUpdateListener 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
|
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 PlottableDecodeEvent mCurrentEvent;
|
||||||
private Identifier mIdentifier;
|
private Identifier mIdentifier;
|
||||||
|
|
||||||
@@ -47,11 +48,24 @@ public class PlottableEntityHistory
|
|||||||
/**
|
/**
|
||||||
* Location history for this entity
|
* Location history for this entity
|
||||||
*/
|
*/
|
||||||
public List<GeoPosition> getLocationHistory()
|
public List<TimestampedGeoPosition> getLocationHistory()
|
||||||
{
|
{
|
||||||
return new ArrayList<>(mLocationHistory);
|
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
|
* Identifier for this plottable
|
||||||
*/
|
*/
|
||||||
@@ -74,6 +88,11 @@ public class PlottableEntityHistory
|
|||||||
public void add(PlottableDecodeEvent event)
|
public void add(PlottableDecodeEvent event)
|
||||||
{
|
{
|
||||||
mCurrentEvent = 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-2024 Dennis Sheirer
|
||||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* 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
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* *****************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
package io.github.dsheirer.map;
|
package io.github.dsheirer.map;
|
||||||
|
|
||||||
import io.github.dsheirer.alias.AliasModel;
|
import io.github.dsheirer.alias.AliasModel;
|
||||||
import io.github.dsheirer.icon.IconModel;
|
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.JXMapViewer;
|
||||||
import org.jdesktop.swingx.painter.AbstractPainter;
|
import org.jdesktop.swingx.painter.AbstractPainter;
|
||||||
|
|
||||||
import java.awt.Graphics2D;
|
/**
|
||||||
import java.awt.Rectangle;
|
* Paints plottable entities to the map.
|
||||||
import java.util.Collections;
|
*/
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public class PlottableEntityPainter extends AbstractPainter<JXMapViewer>
|
public class PlottableEntityPainter extends AbstractPainter<JXMapViewer>
|
||||||
{
|
{
|
||||||
private PlottableEntityRenderer mRenderer;
|
private PlottableEntityRenderer mRenderer;
|
||||||
private Set<PlottableEntityHistory> mEntities = new HashSet<>();
|
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)
|
public PlottableEntityPainter(AliasModel aliasModel, IconModel iconModel)
|
||||||
{
|
{
|
||||||
mRenderer = new PlottableEntityRenderer(aliasModel, iconModel);
|
mRenderer = new PlottableEntityRenderer(aliasModel, iconModel);
|
||||||
@@ -43,24 +49,77 @@ public class PlottableEntityPainter extends AbstractPainter<JXMapViewer>
|
|||||||
setCacheable(false);
|
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)
|
public void removeEntity(PlottableEntityHistory entity)
|
||||||
{
|
{
|
||||||
mEntities.remove(entity);
|
mEntities.remove(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearEntities()
|
/**
|
||||||
|
* Clears all entities from the map.
|
||||||
|
*/
|
||||||
|
public void clearAllEntities()
|
||||||
{
|
{
|
||||||
mEntities.clear();
|
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
|
@Override
|
||||||
@@ -70,9 +129,7 @@ public class PlottableEntityPainter extends AbstractPainter<JXMapViewer>
|
|||||||
|
|
||||||
g.translate(-viewportBounds.getX(), -viewportBounds.getY());
|
g.translate(-viewportBounds.getX(), -viewportBounds.getY());
|
||||||
|
|
||||||
Set<PlottableEntityHistory> entities = getEntities();
|
for(PlottableEntityHistory entity : mEntities)
|
||||||
|
|
||||||
for(PlottableEntityHistory entity : entities)
|
|
||||||
{
|
{
|
||||||
mRenderer.paintPlottableEntity(g, map, entity, true);
|
mRenderer.paintPlottableEntity(g, map, entity, true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* 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
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* *****************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
package io.github.dsheirer.map;
|
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.AliasList;
|
||||||
import io.github.dsheirer.alias.AliasModel;
|
import io.github.dsheirer.alias.AliasModel;
|
||||||
import io.github.dsheirer.icon.IconModel;
|
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.BasicStroke;
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
@@ -34,21 +29,58 @@ import java.awt.RenderingHints;
|
|||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
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
|
public class PlottableEntityRenderer
|
||||||
{
|
{
|
||||||
private AliasModel mAliasModel;
|
private AliasModel mAliasModel;
|
||||||
private IconModel mIconModel;
|
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)
|
public PlottableEntityRenderer(AliasModel aliasModel, IconModel iconModel)
|
||||||
{
|
{
|
||||||
mAliasModel = aliasModel;
|
mAliasModel = aliasModel;
|
||||||
mIconModel = iconModel;
|
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)
|
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())
|
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
|
* 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);
|
graphics.setColor(color);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -79,7 +111,7 @@ public class PlottableEntityRenderer
|
|||||||
* Convert the lat/long geoposition to an x/y point on the viewer
|
* 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
|
* 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)
|
private void paintRoute(Graphics2D graphics, JXMapViewer viewer, PlottableEntityHistory entity, Color color)
|
||||||
{
|
{
|
||||||
List<GeoPosition> locations = entity.getLocationHistory();
|
List<TimestampedGeoPosition> locations = entity.getLocationHistory();
|
||||||
|
|
||||||
if(!locations.isEmpty())
|
if(!locations.isEmpty())
|
||||||
{
|
{
|
||||||
@@ -160,21 +192,26 @@ public class PlottableEntityRenderer
|
|||||||
/**
|
/**
|
||||||
* Draws a route from a list of plottables
|
* 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
|
// convert geo-coordinate to world bitmap pixel
|
||||||
Point2D currentPoint = viewer.getTileFactory().geoToPixel(location, viewer.getZoom());
|
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
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * 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 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;
|
package io.github.dsheirer.module.decode.p25.phase2.timeslot;
|
||||||
@@ -63,9 +60,6 @@ public class LinearFeedbackShiftRegister
|
|||||||
mSystem = system;
|
mSystem = system;
|
||||||
mNac = nac;
|
mNac = nac;
|
||||||
|
|
||||||
int temp = 0xFFFFF & 1;
|
|
||||||
long tempShift = temp << 24;
|
|
||||||
|
|
||||||
mRegisters = (long)(0xFFFFF & wacn) << 24;
|
mRegisters = (long)(0xFFFFF & wacn) << 24;
|
||||||
mRegisters += (0xFFF & system) << 12;
|
mRegisters += (0xFFF & system) << 12;
|
||||||
mRegisters += (0xFFF & nac);
|
mRegisters += (0xFFF & nac);
|
||||||
@@ -78,6 +72,16 @@ public class LinearFeedbackShiftRegister
|
|||||||
mCurrentOutput = getTap(TAP_43);
|
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).
|
* 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;
|
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
|
* Provides the next output bit from the LFSR
|
||||||
*/
|
*/
|
||||||
@@ -152,4 +182,39 @@ public class LinearFeedbackShiftRegister
|
|||||||
{
|
{
|
||||||
return (mRegisters & tap) == tap;
|
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
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * 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 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;
|
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.icons.font_awesome.FontAwesome;
|
||||||
import jiconfont.swing.IconFontSwing;
|
import jiconfont.swing.IconFontSwing;
|
||||||
import org.apache.commons.math3.util.FastMath;
|
import org.apache.commons.math3.util.FastMath;
|
||||||
@@ -46,20 +57,6 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.swing.JPanel;
|
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
|
* 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 static final long serialVersionUID = -3530746298586937321L;
|
||||||
|
|
||||||
private final static Logger mLog =
|
private final static Logger mLog = LoggerFactory.getLogger(JXMapViewer.class);
|
||||||
LoggerFactory.getLogger( JXMapViewer.class );
|
|
||||||
|
|
||||||
private final boolean isNegativeYAllowed = true; // maybe rename to isNorthBounded and isSouthBounded?
|
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
|
* 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?
|
* 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
|
* 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
|
* 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".
|
* 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
|
* 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.
|
* 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.
|
* 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,
|
* 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
|
* (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.
|
* 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
|
* 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.
|
* 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 mRestrictOutsidePanning = true;
|
||||||
private boolean horizontalWrapped = true;
|
private boolean mHorizontalWrapped = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new JXMapViewer. By default it will use the EmptyTileFactory
|
* Create a new JXMapViewer. By default it will use the EmptyTileFactory
|
||||||
*/
|
*/
|
||||||
public JXMapViewer()
|
public JXMapViewer()
|
||||||
{
|
{
|
||||||
factory = new EmptyTileFactory();
|
mTileFactory = new EmptyTileFactory();
|
||||||
// setTileFactory(new GoogleTileFactory());
|
// setTileFactory(new GoogleTileFactory());
|
||||||
|
|
||||||
// make a dummy loading image
|
// make a dummy loading image
|
||||||
@@ -153,9 +149,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
|||||||
}
|
}
|
||||||
catch (Throwable ex)
|
catch (Throwable ex)
|
||||||
{
|
{
|
||||||
|
|
||||||
mLog.error( "JXMapViewer could not load default 'loading.png'" );
|
mLog.error( "JXMapViewer could not load default 'loading.png'" );
|
||||||
|
|
||||||
BufferedImage img = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
|
BufferedImage img = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
|
||||||
Graphics2D g2 = img.createGraphics();
|
Graphics2D g2 = img.createGraphics();
|
||||||
g2.setColor(Color.black);
|
g2.setColor(Color.black);
|
||||||
@@ -203,7 +197,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
|||||||
@Override
|
@Override
|
||||||
public void setDesignTime(boolean b)
|
public void setDesignTime(boolean b)
|
||||||
{
|
{
|
||||||
this.designTime = b;
|
this.mDesignTime = b;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -213,7 +207,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
|||||||
@Override
|
@Override
|
||||||
public boolean isDesignTime()
|
public boolean isDesignTime()
|
||||||
{
|
{
|
||||||
return designTime;
|
return mDesignTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -313,9 +307,9 @@ public class JXMapViewer extends JPanel implements DesignMode
|
|||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private void drawOverlays(final int zoom, final Graphics g, final Rectangle viewportBounds)
|
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)
|
public void setOverlayPainter(Painter<? super JXMapViewer> overlay)
|
||||||
{
|
{
|
||||||
Painter<? super JXMapViewer> old = getOverlayPainter();
|
Painter<? super JXMapViewer> old = getOverlayPainter();
|
||||||
this.overlay = overlay;
|
this.mOverlay = overlay;
|
||||||
|
|
||||||
PropertyChangeListener listener = new PropertyChangeListener()
|
PropertyChangeListener listener = new PropertyChangeListener()
|
||||||
{
|
{
|
||||||
@@ -369,7 +363,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
|||||||
*/
|
*/
|
||||||
public Painter<? super JXMapViewer> getOverlayPainter()
|
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)
|
public void setZoom(int zoom)
|
||||||
{
|
{
|
||||||
if (zoom == this.zoomLevel)
|
if (zoom == this.mZoomLevel)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -412,10 +406,10 @@ public class JXMapViewer extends JPanel implements DesignMode
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if(zoom >= 0 && zoom <= 15 && zoom != this.zoom) {
|
// if(zoom >= 0 && zoom <= 15 && zoom != this.zoom) {
|
||||||
int oldzoom = this.zoomLevel;
|
int oldzoom = this.mZoomLevel;
|
||||||
Point2D oldCenter = getCenter();
|
Point2D oldCenter = getCenter();
|
||||||
Dimension oldMapSize = getTileFactory().getMapSize(oldzoom);
|
Dimension oldMapSize = getTileFactory().getMapSize(oldzoom);
|
||||||
this.zoomLevel = zoom;
|
this.mZoomLevel = zoom;
|
||||||
this.firePropertyChange("zoom", oldzoom, zoom);
|
this.firePropertyChange("zoom", oldzoom, zoom);
|
||||||
|
|
||||||
Dimension mapSize = getTileFactory().getMapSize(zoom);
|
Dimension mapSize = getTileFactory().getMapSize(zoom);
|
||||||
@@ -432,7 +426,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
|||||||
*/
|
*/
|
||||||
public int getZoom()
|
public int getZoom()
|
||||||
{
|
{
|
||||||
return this.zoomLevel;
|
return this.mZoomLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -442,7 +436,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
|||||||
*/
|
*/
|
||||||
public GeoPosition getAddressLocation()
|
public GeoPosition getAddressLocation()
|
||||||
{
|
{
|
||||||
return addressLocation;
|
return mAddressLocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -452,7 +446,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
|||||||
public void setAddressLocation(GeoPosition addressLocation)
|
public void setAddressLocation(GeoPosition addressLocation)
|
||||||
{
|
{
|
||||||
GeoPosition old = getAddressLocation();
|
GeoPosition old = getAddressLocation();
|
||||||
this.addressLocation = addressLocation;
|
this.mAddressLocation = addressLocation;
|
||||||
setCenter(getTileFactory().geoToPixel(addressLocation, getZoom()));
|
setCenter(getTileFactory().geoToPixel(addressLocation, getZoom()));
|
||||||
|
|
||||||
firePropertyChange("addressLocation", old, getAddressLocation());
|
firePropertyChange("addressLocation", old, getAddressLocation());
|
||||||
@@ -475,7 +469,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
|||||||
*/
|
*/
|
||||||
public boolean isDrawTileBorders()
|
public boolean isDrawTileBorders()
|
||||||
{
|
{
|
||||||
return drawTileBorders;
|
return mDrawTileBorders;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -485,7 +479,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
|||||||
public void setDrawTileBorders(boolean drawTileBorders)
|
public void setDrawTileBorders(boolean drawTileBorders)
|
||||||
{
|
{
|
||||||
boolean old = isDrawTileBorders();
|
boolean old = isDrawTileBorders();
|
||||||
this.drawTileBorders = drawTileBorders;
|
this.mDrawTileBorders = drawTileBorders;
|
||||||
firePropertyChange("drawTileBorders", old, isDrawTileBorders());
|
firePropertyChange("drawTileBorders", old, isDrawTileBorders());
|
||||||
repaint();
|
repaint();
|
||||||
}
|
}
|
||||||
@@ -497,7 +491,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
|||||||
public void setCenterPosition(GeoPosition geoPosition)
|
public void setCenterPosition(GeoPosition geoPosition)
|
||||||
{
|
{
|
||||||
GeoPosition oldVal = getCenterPosition();
|
GeoPosition oldVal = getCenterPosition();
|
||||||
setCenter(getTileFactory().geoToPixel(geoPosition, zoomLevel));
|
setCenter(getTileFactory().geoToPixel(geoPosition, mZoomLevel));
|
||||||
repaint();
|
repaint();
|
||||||
GeoPosition newVal = getCenterPosition();
|
GeoPosition newVal = getCenterPosition();
|
||||||
firePropertyChange("centerPosition", oldVal, newVal);
|
firePropertyChange("centerPosition", oldVal, newVal);
|
||||||
@@ -509,7 +503,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
|||||||
*/
|
*/
|
||||||
public GeoPosition getCenterPosition()
|
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()
|
public TileFactory getTileFactory()
|
||||||
{
|
{
|
||||||
return factory;
|
return mTileFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -530,10 +524,10 @@ public class JXMapViewer extends JPanel implements DesignMode
|
|||||||
if (factory == null)
|
if (factory == null)
|
||||||
throw new NullPointerException("factory must not be null");
|
throw new NullPointerException("factory must not be null");
|
||||||
|
|
||||||
this.factory.removeTileListener(tileLoadListener);
|
this.mTileFactory.removeTileListener(tileLoadListener);
|
||||||
this.factory.dispose();
|
this.mTileFactory.dispose();
|
||||||
|
|
||||||
this.factory = factory;
|
this.mTileFactory = factory;
|
||||||
this.setZoom(factory.getInfo().getDefaultZoomLevel());
|
this.setZoom(factory.getInfo().getDefaultZoomLevel());
|
||||||
|
|
||||||
factory.addTileListener(tileLoadListener);
|
factory.addTileListener(tileLoadListener);
|
||||||
@@ -547,7 +541,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
|||||||
*/
|
*/
|
||||||
public Image getLoadingImage()
|
public Image getLoadingImage()
|
||||||
{
|
{
|
||||||
return loadingImage;
|
return mLoadingImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -556,7 +550,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
|||||||
*/
|
*/
|
||||||
public void setLoadingImage(Image loadingImage)
|
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()
|
public Point2D getCenter()
|
||||||
{
|
{
|
||||||
return center;
|
return mCenter;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -641,8 +635,8 @@ public class JXMapViewer extends JPanel implements DesignMode
|
|||||||
}
|
}
|
||||||
|
|
||||||
GeoPosition oldGP = this.getCenterPosition();
|
GeoPosition oldGP = this.getCenterPosition();
|
||||||
this.center = new Point2D.Double(centerX, centerY);
|
this.mCenter = new Point2D.Double(centerX, centerY);
|
||||||
firePropertyChange("center", old, this.center);
|
firePropertyChange("center", old, this.mCenter);
|
||||||
firePropertyChange("centerPosition", oldGP, this.getCenterPosition());
|
firePropertyChange("centerPosition", oldGP, this.getCenterPosition());
|
||||||
repaint();
|
repaint();
|
||||||
}
|
}
|
||||||
@@ -739,7 +733,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
|||||||
*/
|
*/
|
||||||
public boolean isRestrictOutsidePanning()
|
public boolean isRestrictOutsidePanning()
|
||||||
{
|
{
|
||||||
return restrictOutsidePanning;
|
return mRestrictOutsidePanning;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -747,7 +741,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
|||||||
*/
|
*/
|
||||||
public void setRestrictOutsidePanning(boolean restrictOutsidePanning)
|
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()
|
public boolean isHorizontalWrapped()
|
||||||
{
|
{
|
||||||
return horizontalWrapped;
|
return mHorizontalWrapped;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -763,7 +757,7 @@ public class JXMapViewer extends JPanel implements DesignMode
|
|||||||
*/
|
*/
|
||||||
public void setHorizontalWrapped(boolean horizontalWrapped)
|
public void setHorizontalWrapped(boolean horizontalWrapped)
|
||||||
{
|
{
|
||||||
this.horizontalWrapped = horizontalWrapped;
|
this.mHorizontalWrapped = horizontalWrapped;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package org.jdesktop.swingx.input;
|
package org.jdesktop.swingx.input;
|
||||||
|
|
||||||
|
import io.github.dsheirer.map.MapPanel;
|
||||||
import org.jdesktop.swingx.JXMapViewer;
|
import org.jdesktop.swingx.JXMapViewer;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
@@ -31,32 +32,34 @@ import java.awt.geom.Point2D;
|
|||||||
*/
|
*/
|
||||||
public class ZoomMouseWheelListenerCursor implements MouseWheelListener
|
public class ZoomMouseWheelListenerCursor implements MouseWheelListener
|
||||||
{
|
{
|
||||||
private JXMapViewer viewer;
|
private MapPanel mMapPanel;
|
||||||
|
private JXMapViewer mViewer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param viewer the jxmapviewer
|
* @param viewer the jxmapviewer
|
||||||
*/
|
*/
|
||||||
public ZoomMouseWheelListenerCursor(JXMapViewer viewer)
|
public ZoomMouseWheelListenerCursor(MapPanel mapPanel)
|
||||||
{
|
{
|
||||||
this.viewer = viewer;
|
mMapPanel = mapPanel;
|
||||||
|
mViewer = mMapPanel.getMapViewer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mouseWheelMoved(MouseWheelEvent evt)
|
public void mouseWheelMoved(MouseWheelEvent evt)
|
||||||
{
|
{
|
||||||
Point current = evt.getPoint();
|
Point current = evt.getPoint();
|
||||||
Rectangle bound = viewer.getViewportBounds();
|
Rectangle bound = mViewer.getViewportBounds();
|
||||||
|
|
||||||
double dx = current.x - bound.width / 2;
|
double dx = current.x - bound.width / 2;
|
||||||
double dy = current.y - bound.height / 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());
|
mMapPanel.adjustZoom(evt.getWheelRotation());
|
||||||
|
|
||||||
Dimension mapSize = viewer.getTileFactory().getMapSize(viewer.getZoom());
|
Dimension mapSize = mViewer.getTileFactory().getMapSize(mViewer.getZoom());
|
||||||
|
|
||||||
Point2D center = viewer.getCenter();
|
Point2D center = mViewer.getCenter();
|
||||||
|
|
||||||
double dzw = (mapSize.getWidth() / oldMapSize.getWidth());
|
double dzw = (mapSize.getWidth() / oldMapSize.getWidth());
|
||||||
double dzh = (mapSize.getHeight() / oldMapSize.getHeight());
|
double dzh = (mapSize.getHeight() / oldMapSize.getHeight());
|
||||||
@@ -64,6 +67,6 @@ public class ZoomMouseWheelListenerCursor implements MouseWheelListener
|
|||||||
double x = center.getX() + dx * (dzw - 1);
|
double x = center.getX() + dx * (dzw - 1);
|
||||||
double y = center.getY() + dy * (dzh - 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
|
* GeoPosition.java
|
||||||
*
|
*
|
||||||
@@ -33,7 +52,6 @@ public class GeoPosition
|
|||||||
this.latitude = latitude;
|
this.latitude = latitude;
|
||||||
this.longitude = longitude;
|
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
|
* Creates a new instance of GeoPosition from the specified
|
||||||
@@ -89,6 +107,36 @@ public class GeoPosition
|
|||||||
return longitude;
|
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
|
@Override
|
||||||
public int hashCode()
|
public int hashCode()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user