mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-14 17:47:32 -05:00
#2340 Update map plotting to address glitchy behavior when selecting entities and histories. (#2341)
Co-authored-by: denny <dennis.sheirer@gmail.com>
This commit is contained in:
@@ -51,7 +51,6 @@ import javax.swing.JToggleButton;
|
|||||||
import javax.swing.SpinnerNumberModel;
|
import javax.swing.SpinnerNumberModel;
|
||||||
import javax.swing.event.ChangeEvent;
|
import javax.swing.event.ChangeEvent;
|
||||||
import javax.swing.event.ChangeListener;
|
import javax.swing.event.ChangeListener;
|
||||||
import javax.swing.event.TableModelEvent;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Swing map panel.
|
* Swing map panel.
|
||||||
@@ -85,9 +84,9 @@ public class MapPanel extends JPanel implements IPlottableUpdateListener
|
|||||||
private JComboBox<Integer> mTrackHistoryLengthComboBox;
|
private JComboBox<Integer> mTrackHistoryLengthComboBox;
|
||||||
private JSpinner mMapZoomSpinner;
|
private JSpinner mMapZoomSpinner;
|
||||||
private SpinnerNumberModel mMapZoomSpinnerModel;
|
private SpinnerNumberModel mMapZoomSpinnerModel;
|
||||||
private TrackHistoryModel mTrackHistoryModel = new TrackHistoryModel();
|
|
||||||
private JTable mTrackHistoryTable;
|
private JTable mTrackHistoryTable;
|
||||||
private JLabel mSelectedTrackSystemLabel;
|
private JLabel mSelectedTrackSystemLabel;
|
||||||
|
private final TrackHistoryModel EMPTY_HISTORY = new TrackHistoryModel();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
@@ -138,6 +137,7 @@ public class MapPanel extends JPanel implements IPlottableUpdateListener
|
|||||||
|
|
||||||
cp.add(new JScrollPane(getTrackHistoryTable()), "span 3,wrap");
|
cp.add(new JScrollPane(getTrackHistoryTable()), "span 3,wrap");
|
||||||
|
|
||||||
|
//Uncomment this line to add a debug track generator button to the display
|
||||||
// cp.add(getTrackGeneratorToggle(), "span 3,wrap");
|
// cp.add(getTrackGeneratorToggle(), "span 3,wrap");
|
||||||
|
|
||||||
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
|
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
|
||||||
@@ -149,10 +149,10 @@ public class MapPanel extends JPanel implements IPlottableUpdateListener
|
|||||||
|
|
||||||
private void setSelected(PlottableEntityHistory selected)
|
private void setSelected(PlottableEntityHistory selected)
|
||||||
{
|
{
|
||||||
mTrackHistoryModel.load(selected);
|
|
||||||
|
|
||||||
if(selected != null)
|
if(selected != null)
|
||||||
{
|
{
|
||||||
|
getTrackHistoryTable().setModel(selected.getTrackHistoryModel());
|
||||||
|
|
||||||
Identifier system = selected.getIdentifierCollection().getIdentifier(IdentifierClass.CONFIGURATION, Form.SYSTEM, Role.ANY);
|
Identifier system = selected.getIdentifierCollection().getIdentifier(IdentifierClass.CONFIGURATION, Form.SYSTEM, Role.ANY);
|
||||||
|
|
||||||
if(system != null)
|
if(system != null)
|
||||||
@@ -171,6 +171,7 @@ public class MapPanel extends JPanel implements IPlottableUpdateListener
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
getTrackHistoryTable().setModel(EMPTY_HISTORY);
|
||||||
getSelectedTrackSystemLabel().setText(SELECT_A_TRACK);
|
getSelectedTrackSystemLabel().setText(SELECT_A_TRACK);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,13 +218,13 @@ public class MapPanel extends JPanel implements IPlottableUpdateListener
|
|||||||
{
|
{
|
||||||
if(mTrackHistoryTable == null)
|
if(mTrackHistoryTable == null)
|
||||||
{
|
{
|
||||||
mTrackHistoryTable = new JTable(mTrackHistoryModel);
|
mTrackHistoryTable = new JTable(EMPTY_HISTORY);
|
||||||
mTrackHistoryTable.getSelectionModel().addListSelectionListener(e ->
|
mTrackHistoryTable.getSelectionModel().addListSelectionListener(e ->
|
||||||
{
|
{
|
||||||
if(getCenterOnSelectedCheckBox().isSelected())
|
if(getCenterOnSelectedCheckBox().isSelected())
|
||||||
{
|
{
|
||||||
int modelIndex = getTrackHistoryTable().convertRowIndexToModel(getTrackHistoryTable().getSelectedRow());
|
int modelIndex = getTrackHistoryTable().convertRowIndexToModel(getTrackHistoryTable().getSelectedRow());
|
||||||
TimestampedGeoPosition geo = mTrackHistoryModel.get(modelIndex);
|
TimestampedGeoPosition geo = ((TrackHistoryModel)(getTrackHistoryTable().getModel())).get(modelIndex);
|
||||||
|
|
||||||
if(geo != null)
|
if(geo != null)
|
||||||
{
|
{
|
||||||
@@ -469,64 +470,6 @@ public class MapPanel extends JPanel implements IPlottableUpdateListener
|
|||||||
{
|
{
|
||||||
mPlottedTracksTable = new JTable(mMapService.getPlottableEntityModel());
|
mPlottedTracksTable = new JTable(mMapService.getPlottableEntityModel());
|
||||||
mPlottedTracksTable.setAutoCreateRowSorter(true);
|
mPlottedTracksTable.setAutoCreateRowSorter(true);
|
||||||
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
|
//Register selection listener to update button/label states
|
||||||
mPlottedTracksTable.getSelectionModel().addListSelectionListener(e ->
|
mPlottedTracksTable.getSelectionModel().addListSelectionListener(e ->
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
* Copyright (C) 2014-2025 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
|
||||||
@@ -22,19 +22,15 @@ package io.github.dsheirer.map;
|
|||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||||
import io.github.dsheirer.module.decode.event.PlottableDecodeEvent;
|
import io.github.dsheirer.module.decode.event.PlottableDecodeEvent;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import org.jdesktop.swingx.mapviewer.GeoPosition;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plottable entity history with location history.
|
* Plottable entity history with location history.
|
||||||
*/
|
*/
|
||||||
public class PlottableEntityHistory
|
public class PlottableEntityHistory
|
||||||
{
|
{
|
||||||
public static final int MAX_LOCATION_HISTORY = 10;
|
private final TrackHistoryModel mTrackHistoryModel = new TrackHistoryModel();
|
||||||
private List<TimestampedGeoPosition> mLocationHistory = new ArrayList<>();
|
|
||||||
private PlottableDecodeEvent mCurrentEvent;
|
private PlottableDecodeEvent mCurrentEvent;
|
||||||
private Identifier mIdentifier;
|
private final Identifier mIdentifier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a plottable entity history
|
* Constructs a plottable entity history
|
||||||
@@ -42,28 +38,24 @@ public class PlottableEntityHistory
|
|||||||
public PlottableEntityHistory(Identifier identifier, PlottableDecodeEvent event)
|
public PlottableEntityHistory(Identifier identifier, PlottableDecodeEvent event)
|
||||||
{
|
{
|
||||||
mIdentifier = identifier;
|
mIdentifier = identifier;
|
||||||
|
mCurrentEvent = event;
|
||||||
add(event);
|
add(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Location history for this entity
|
* Location history for this entity
|
||||||
*/
|
*/
|
||||||
public List<TimestampedGeoPosition> getLocationHistory()
|
public TrackHistoryModel getTrackHistoryModel()
|
||||||
{
|
{
|
||||||
return new ArrayList<>(mLocationHistory);
|
return mTrackHistoryModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Latest position for this entity.
|
* Latest position for this history
|
||||||
*/
|
*/
|
||||||
public TimestampedGeoPosition getLatestPosition()
|
public TimestampedGeoPosition getLatestPosition()
|
||||||
{
|
{
|
||||||
if(mLocationHistory.size() > 0)
|
return mTrackHistoryModel.get(0);
|
||||||
{
|
|
||||||
return mLocationHistory.get(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,43 +81,9 @@ public class PlottableEntityHistory
|
|||||||
{
|
{
|
||||||
if(event.isValidLocation())
|
if(event.isValidLocation())
|
||||||
{
|
{
|
||||||
TimestampedGeoPosition mostRecentPosition = getLatestPosition();
|
mTrackHistoryModel.add(new TimestampedGeoPosition(event.getLocation(), event.getTimeStart()));
|
||||||
TimestampedGeoPosition latest = new TimestampedGeoPosition(event.getLocation(), event.getTimeStart());
|
|
||||||
|
|
||||||
if(isUnique(latest, mostRecentPosition))
|
|
||||||
{
|
|
||||||
mCurrentEvent = event;
|
|
||||||
mLocationHistory.add(0, latest);
|
|
||||||
|
|
||||||
while(mLocationHistory.size() > MAX_LOCATION_HISTORY)
|
|
||||||
{
|
|
||||||
mLocationHistory.remove(mLocationHistory.size() - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates if the latest time and position is at least 30 seconds newer than the previous position and either the
|
|
||||||
* latitude or longitude differs by at least 0.00001 degrees.
|
|
||||||
* @param latest location
|
|
||||||
* @param previous location
|
|
||||||
* @return indication of uniqueness.
|
|
||||||
*/
|
|
||||||
private boolean isUnique(TimestampedGeoPosition latest, TimestampedGeoPosition previous)
|
|
||||||
{
|
|
||||||
if(latest != null && previous == null)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(latest != null && previous != null)
|
mCurrentEvent = event;
|
||||||
{
|
|
||||||
return latest.getTimestamp() > (previous.getTimestamp() + 30_000) ||
|
|
||||||
Math.abs(latest.getLatitude() - previous.getLatitude()) > 0.00001 ||
|
|
||||||
Math.abs(latest.getLongitude() - previous.getLongitude()) > 0.00001;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
* Copyright (C) 2014-2025 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
|
||||||
@@ -80,9 +80,9 @@ public class PlottableEntityRenderer
|
|||||||
*/
|
*/
|
||||||
public void paintPlottableEntity(Graphics2D g, JXMapViewer viewer, PlottableEntityHistory entity, boolean antiAliasing)
|
public void paintPlottableEntity(Graphics2D g, JXMapViewer viewer, PlottableEntityHistory entity, boolean antiAliasing)
|
||||||
{
|
{
|
||||||
List<TimestampedGeoPosition> locationHistory = entity.getLocationHistory();
|
List<TimestampedGeoPosition> locationHistory = entity.getTrackHistoryModel().getTrackHistory();
|
||||||
|
|
||||||
if(!locationHistory.isEmpty() && locationHistory.get(locationHistory.size() - 1).isValid())
|
if(!locationHistory.isEmpty() && locationHistory.getLast().isValid())
|
||||||
{
|
{
|
||||||
Graphics2D graphics = (Graphics2D)g.create();
|
Graphics2D graphics = (Graphics2D)g.create();
|
||||||
|
|
||||||
@@ -171,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<TimestampedGeoPosition> locations = entity.getLocationHistory();
|
List<TimestampedGeoPosition> locations = entity.getTrackHistoryModel().getTrackHistory();
|
||||||
|
|
||||||
if(!locations.isEmpty())
|
if(!locations.isEmpty())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
* Copyright (C) 2014-2025 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
|
||||||
@@ -28,6 +28,7 @@ import io.github.dsheirer.identifier.configuration.SystemConfigurationIdentifier
|
|||||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||||
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
||||||
import io.github.dsheirer.module.decode.event.PlottableDecodeEvent;
|
import io.github.dsheirer.module.decode.event.PlottableDecodeEvent;
|
||||||
|
import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier;
|
||||||
import io.github.dsheirer.protocol.Protocol;
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -44,13 +45,9 @@ import org.jdesktop.swingx.mapviewer.GeoPosition;
|
|||||||
*/
|
*/
|
||||||
public class TrackGenerator
|
public class TrackGenerator
|
||||||
{
|
{
|
||||||
private static GeoPosition DEFAULT_START_POSITION = new GeoPosition(43.048, -76.147);
|
private static final GeoPosition DEFAULT_START_POSITION = new GeoPosition(43.048, -76.147);
|
||||||
private static final String ALIAS_LIST_NAME = "DMR Test Alias List";
|
private final MapService mMapService;
|
||||||
private MapService mMapService;
|
private final List<TrackElementGenerator> mTrackElementGenerators = new ArrayList<>();
|
||||||
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
|
* Constructs an instance to publish tracks to the specified map service
|
||||||
@@ -60,19 +57,27 @@ public class TrackGenerator
|
|||||||
{
|
{
|
||||||
mMapService = 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();
|
Random random = new Random();
|
||||||
|
|
||||||
for(int x = 0; x < mTrackCount; x++)
|
int trackCount = 25;
|
||||||
|
for(int x = 0; x < trackCount; x++)
|
||||||
{
|
{
|
||||||
MutableIdentifierCollection mic = new MutableIdentifierCollection(base.getIdentifiers());
|
MutableIdentifierCollection mic = new MutableIdentifierCollection();
|
||||||
mic.update(DMRRadio.createFrom(x + 1));
|
mic.update(SystemConfigurationIdentifier.create("Test System"));
|
||||||
double speedKPH = mBaseSpeedKPH + (random.nextDouble() * 15);
|
mic.update(SiteConfigurationIdentifier.create("Test Site"));
|
||||||
|
mic.update(FrequencyConfigurationIdentifier.create(155000000l));
|
||||||
|
if(x % 2 == 1)
|
||||||
|
{
|
||||||
|
mic.update(AliasListConfigurationIdentifier.create("DMR " + x + " LIST"));
|
||||||
|
mic.update(DMRRadio.createFrom(x + 1));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mic.update(AliasListConfigurationIdentifier.create("P25 " + x + " LIST"));
|
||||||
|
mic.update(APCO25RadioIdentifier.createFrom(x + 1));
|
||||||
|
}
|
||||||
|
double baseSpeedKPH = 20.0;
|
||||||
|
double speedKPH = baseSpeedKPH + random.nextDouble(15);
|
||||||
mTrackElementGenerators.add(new TrackElementGenerator(speedKPH, mic));
|
mTrackElementGenerators.add(new TrackElementGenerator(speedKPH, mic));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,9 +87,9 @@ public class TrackGenerator
|
|||||||
*/
|
*/
|
||||||
public void start()
|
public void start()
|
||||||
{
|
{
|
||||||
if(mGeneratorFuture == null)
|
for(TrackElementGenerator generator: mTrackElementGenerators)
|
||||||
{
|
{
|
||||||
mGeneratorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(() -> update(), 0, 1, TimeUnit.SECONDS);
|
generator.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,59 +98,81 @@ public class TrackGenerator
|
|||||||
*/
|
*/
|
||||||
public void stop()
|
public void stop()
|
||||||
{
|
{
|
||||||
if(mGeneratorFuture != null)
|
for(TrackElementGenerator generator: mTrackElementGenerators)
|
||||||
{
|
{
|
||||||
mGeneratorFuture.cancel(true);
|
generator.stop();
|
||||||
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
|
* Test track element generator
|
||||||
*/
|
*/
|
||||||
public static class TrackElementGenerator
|
public class TrackElementGenerator
|
||||||
{
|
{
|
||||||
public static double EARTH_RADIUS_KM = 6378.137;
|
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.
|
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 IdentifierCollection mIdentifierCollection;
|
||||||
private double mSpeedKPH;
|
private double mSpeedKPH;
|
||||||
private GeoPosition mPosition = new GeoPosition(DEFAULT_START_POSITION.getLatitude(), DEFAULT_START_POSITION.getLongitude());
|
private GeoPosition mPosition;
|
||||||
private Random mRandom = new Random();
|
private Random mRandom = new Random();
|
||||||
private double mHeading = 360.0 * mRandom.nextDouble();
|
private double mHeading = 360.0 * mRandom.nextDouble();
|
||||||
|
private ScheduledFuture<?> mGeneratorFuture;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
* @param trackId for the track
|
|
||||||
* @param speedKPH speed in KPH
|
* @param speedKPH speed in KPH
|
||||||
|
* @param identifierCollection for the track
|
||||||
*/
|
*/
|
||||||
public TrackElementGenerator(double speedKPH, IdentifierCollection identifierCollection)
|
public TrackElementGenerator(double speedKPH, IdentifierCollection identifierCollection)
|
||||||
{
|
{
|
||||||
mSpeedKPH = speedKPH;
|
mSpeedKPH = speedKPH;
|
||||||
mIdentifierCollection = identifierCollection;
|
mIdentifierCollection = identifierCollection;
|
||||||
|
double latOffset = (1.0 - (mRandom.nextFloat() * 2)) / 800;
|
||||||
|
double lonOffset = (1.0 - (mRandom.nextFloat() * 2)) / 800;
|
||||||
|
mPosition = new GeoPosition(DEFAULT_START_POSITION.getLatitude() + latOffset,
|
||||||
|
DEFAULT_START_POSITION.getLongitude() + lonOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Starts this track generator
|
||||||
* @param heading 0-360 degrees
|
|
||||||
*/
|
*/
|
||||||
public PlottableDecodeEvent update()
|
public void start()
|
||||||
{
|
{
|
||||||
mHeading = mHeading + (15.0 - (mRandom.nextDouble() * 30.0));
|
if(mGeneratorFuture == null)
|
||||||
mHeading = Math.max(mHeading, 0);
|
{
|
||||||
mHeading = Math.min(mHeading, 360.0);
|
long intervalMS = 500 + (mRandom.nextLong(1000));
|
||||||
|
mSpeedKPH /= (intervalMS / 1000.0);
|
||||||
|
System.out.println("Speed: " + mSpeedKPH);
|
||||||
|
mGeneratorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(this::update, 0, intervalMS, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops this track generator
|
||||||
|
*/
|
||||||
|
public void stop()
|
||||||
|
{
|
||||||
|
if(mGeneratorFuture != null)
|
||||||
|
{
|
||||||
|
mGeneratorFuture.cancel(true);
|
||||||
|
mGeneratorFuture = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pushes a new update to the map service.
|
||||||
|
*/
|
||||||
|
public void update()
|
||||||
|
{
|
||||||
|
mHeading = mHeading + (30.0 - (mRandom.nextDouble(60)));
|
||||||
|
if(mHeading < 0)
|
||||||
|
{
|
||||||
|
mHeading += 360;
|
||||||
|
}
|
||||||
|
else if(mHeading >= 360)
|
||||||
|
{
|
||||||
|
mHeading -= 360;
|
||||||
|
}
|
||||||
double distanceKM = mSpeedKPH * ONE_SECOND;
|
double distanceKM = mSpeedKPH * ONE_SECOND;
|
||||||
double headingRadians = Math.toRadians(mHeading);
|
double headingRadians = Math.toRadians(mHeading);
|
||||||
|
|
||||||
@@ -168,7 +195,7 @@ public class TrackGenerator
|
|||||||
.protocol(Protocol.DMR)
|
.protocol(Protocol.DMR)
|
||||||
.identifiers(mIdentifierCollection)
|
.identifiers(mIdentifierCollection)
|
||||||
.build();
|
.build();
|
||||||
return event;
|
mMapService.receive(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
* Copyright (C) 2014-2025 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
|
||||||
@@ -28,15 +28,15 @@ import java.util.List;
|
|||||||
import javax.swing.table.AbstractTableModel;
|
import javax.swing.table.AbstractTableModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Table model for track history.
|
* Track history table model.
|
||||||
*/
|
*/
|
||||||
public class TrackHistoryModel extends AbstractTableModel
|
public class TrackHistoryModel extends AbstractTableModel
|
||||||
{
|
{
|
||||||
|
public static final int MAX_LOCATION_HISTORY = 10;
|
||||||
private static final String[] COLUMNS = new String[]{"Time", "Latitude", "Longitude"};
|
private static final String[] COLUMNS = new String[]{"Time", "Latitude", "Longitude"};
|
||||||
private SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
private final SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
private DecimalFormat mDegreeFormat = new DecimalFormat("0.00000");
|
private final DecimalFormat mDegreeFormat = new DecimalFormat("0.00000");
|
||||||
private List<TimestampedGeoPosition> mTimestampedGeoPositions = new ArrayList<>();
|
private final List<TimestampedGeoPosition> mTimestampedGeoPositions = new ArrayList<>();
|
||||||
private PlottableEntityHistory mPlottableEntityHistory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
@@ -45,6 +45,65 @@ public class TrackHistoryModel extends AbstractTableModel
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Complete history for this entity.
|
||||||
|
*/
|
||||||
|
public List<TimestampedGeoPosition> getTrackHistory()
|
||||||
|
{
|
||||||
|
return Collections.unmodifiableList(mTimestampedGeoPositions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the position to the history if it is unique and trims the history to MAX_LOCATION_HISTORY size.
|
||||||
|
* @param latest position to add.
|
||||||
|
*/
|
||||||
|
public void add(TimestampedGeoPosition latest)
|
||||||
|
{
|
||||||
|
TimestampedGeoPosition mostRecentPosition = null;
|
||||||
|
|
||||||
|
if(!mTimestampedGeoPositions.isEmpty())
|
||||||
|
{
|
||||||
|
mostRecentPosition = mTimestampedGeoPositions.getFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isUnique(latest, mostRecentPosition))
|
||||||
|
{
|
||||||
|
mTimestampedGeoPositions.addFirst(latest);
|
||||||
|
fireTableRowsInserted(0, 0);
|
||||||
|
|
||||||
|
while(mTimestampedGeoPositions.size() > MAX_LOCATION_HISTORY)
|
||||||
|
{
|
||||||
|
int index = mTimestampedGeoPositions.size() - 1;
|
||||||
|
mTimestampedGeoPositions.removeLast();
|
||||||
|
fireTableRowsDeleted(index, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the latest time and position is at least 30 seconds newer than the previous position and either the
|
||||||
|
* latitude or longitude differs by at least 0.00001 degrees.
|
||||||
|
* @param latest location
|
||||||
|
* @param previous location
|
||||||
|
* @return indication of uniqueness.
|
||||||
|
*/
|
||||||
|
private boolean isUnique(TimestampedGeoPosition latest, TimestampedGeoPosition previous)
|
||||||
|
{
|
||||||
|
if(latest != null && previous == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(latest != null)
|
||||||
|
{
|
||||||
|
return latest.getTimestamp() > (previous.getTimestamp() + 2_000) ||
|
||||||
|
Math.abs(latest.getLatitude() - previous.getLatitude()) > 0.00001 ||
|
||||||
|
Math.abs(latest.getLongitude() - previous.getLongitude()) > 0.00001;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the geo position at the specified index
|
* Get the geo position at the specified index
|
||||||
* @param index to retrieve
|
* @param index to retrieve
|
||||||
@@ -52,7 +111,7 @@ public class TrackHistoryModel extends AbstractTableModel
|
|||||||
*/
|
*/
|
||||||
public TimestampedGeoPosition get(int index)
|
public TimestampedGeoPosition get(int index)
|
||||||
{
|
{
|
||||||
if(index < mTimestampedGeoPositions.size())
|
if(index >= 0 && index < mTimestampedGeoPositions.size())
|
||||||
{
|
{
|
||||||
return mTimestampedGeoPositions.get(index);
|
return mTimestampedGeoPositions.get(index);
|
||||||
}
|
}
|
||||||
@@ -60,53 +119,6 @@ public class TrackHistoryModel extends AbstractTableModel
|
|||||||
return null;
|
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
|
@Override
|
||||||
public int getRowCount()
|
public int getRowCount()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
|
|
||||||
package io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola;
|
package io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola;
|
||||||
|
|
||||||
import io.github.dsheirer.bits.BinaryMessage;
|
|
||||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
import io.github.dsheirer.bits.IntField;
|
import io.github.dsheirer.bits.IntField;
|
||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
@@ -123,16 +122,4 @@ public class LCMotorolaUnitGPS extends LinkControlWord
|
|||||||
|
|
||||||
return mIdentifiers;
|
return mIdentifiers;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void main()
|
|
||||||
{
|
|
||||||
BinaryMessage mick = BinaryMessage.load("000001101001000000000000101100011010110101010011011000101001000001000010");
|
|
||||||
BinaryMessage newj = BinaryMessage.load("000001101001000000000000001110010000011010111110110010110011100011000010");
|
|
||||||
|
|
||||||
LCMotorolaUnitGPS motoMick = new LCMotorolaUnitGPS(new CorrectedBinaryMessage(mick));
|
|
||||||
LCMotorolaUnitGPS motoNewJ = new LCMotorolaUnitGPS(new CorrectedBinaryMessage(newj));
|
|
||||||
|
|
||||||
System.out.println("MICK: " + motoMick);
|
|
||||||
System.out.println("NEWJ: " + motoNewJ);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user