Merge branch 'develop' of github.com:sleuthkit/autopsy into CloudSolrClient

This commit is contained in:
Eugene Livis
2020-04-20 14:12:44 -04:00
9 changed files with 508 additions and 176 deletions
@@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.casemodule;
import org.sleuthkit.autopsy.featureaccess.FeatureAccessUtils;
import com.google.common.annotations.Beta;
import com.google.common.eventbus.Subscribe;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
import java.awt.Frame;
import java.awt.event.ActionEvent;
@@ -125,6 +126,7 @@ import org.sleuthkit.datamodel.CaseDbConnectionInfo;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.FileSystem;
import org.sleuthkit.datamodel.Image;
import org.sleuthkit.datamodel.Report;
import org.sleuthkit.datamodel.SleuthkitCase;
@@ -156,6 +158,9 @@ public class Case {
private static final Logger logger = Logger.getLogger(Case.class.getName());
private static final AutopsyEventPublisher eventPublisher = new AutopsyEventPublisher();
private static final Object caseActionSerializationLock = new Object();
private static Future<?> backgroundOpenFileSystemsFuture = null;
private static final ExecutorService openFileSystemsExecutor
= Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("case-open-file-systems-%d").build());
private static volatile Frame mainFrame;
private static volatile Case currentCase;
private final CaseMetadata metadata;
@@ -1979,6 +1984,8 @@ public class Case {
openAppServiceCaseResources(progressIndicator);
checkForCancellation();
openCommunicationChannels(progressIndicator);
checkForCancellation();
openFileSystemsInBackground();
return null;
} catch (CaseActionException ex) {
@@ -1997,6 +2004,141 @@ public class Case {
}
}
/**
* Starts a background task that reads a sector from each file system of
* each image of a case to do an eager open of the filesystems in the case.
* If this method is called before another background file system read has
* finished the earlier one will be cancelled.
*
* @throws CaseActionCancelledException Exception thrown if task is
* cancelled.
*/
@Messages({
"# {0} - case", "Case.openFileSystems.retrievingImages=Retrieving images for case: {0}...",
"# {0} - image", "Case.openFileSystems.openingImage=Opening all filesystems for image: {0}..."
})
private void openFileSystemsInBackground() {
if (backgroundOpenFileSystemsFuture != null && !backgroundOpenFileSystemsFuture.isDone()) {
backgroundOpenFileSystemsFuture.cancel(true);
}
BackgroundOpenFileSystemsTask backgroundTask = new BackgroundOpenFileSystemsTask(this.caseDb, new LoggingProgressIndicator());
backgroundOpenFileSystemsFuture = openFileSystemsExecutor.submit(backgroundTask);
}
/**
* This task opens all the filesystems of all images in the case in the
* background. It also responds to cancellation events.
*/
private static class BackgroundOpenFileSystemsTask implements Runnable {
private final SleuthkitCase tskCase;
private final String caseName;
private final ProgressIndicator progressIndicator;
/**
* Main constructor for the BackgroundOpenFileSystemsTask.
*
* @param tskCase The case database to query for filesystems
* to open.
* @param progressIndicator The progress indicator for file systems
* opened.
*/
BackgroundOpenFileSystemsTask(SleuthkitCase tskCase, ProgressIndicator progressIndicator) {
this.tskCase = tskCase;
this.progressIndicator = progressIndicator;
caseName = (this.tskCase != null) ? this.tskCase.getDatabaseName() : "";
}
/**
* Checks if thread has been cancelled and throws an
* InterruptedException if it has.
*
* @throws InterruptedException The exception thrown if the operation
* has been cancelled.
*/
private void checkIfCancelled() throws InterruptedException {
if (Thread.interrupted()) {
throw new InterruptedException();
}
}
/**
* Retrieves all images present in the sleuthkit case.
*
* @return All images present in the sleuthkit case.
*/
private List<Image> getImages() {
progressIndicator.progress(Bundle.Case_openFileSystems_retrievingImages(caseName));
try {
return this.tskCase.getImages();
} catch (TskCoreException ex) {
logger.log(
Level.SEVERE,
String.format("Could not obtain images while opening case: %s.", caseName),
ex);
return null;
}
}
/**
* Opens all file systems in the list of images provided.
*
* @param images The images whose file systems will be opened.
*
* @throws CaseActionCancelledException The exception thrown in the
* event that the operation is
* cancelled prior to completion.
*/
private void openFileSystems(List<Image> images) throws InterruptedException {
byte[] tempBuff = new byte[512];
for (Image image : images) {
String imageStr = image.getName();
progressIndicator.progress(Bundle.Case_openFileSystems_openingImage(imageStr));
Collection<FileSystem> fileSystems = this.tskCase.getFileSystems(image);
checkIfCancelled();
for (FileSystem fileSystem : fileSystems) {
try {
fileSystem.read(tempBuff, 0, 512);
} catch (TskCoreException ex) {
String fileSysStr = fileSystem.getName();
logger.log(
Level.WARNING,
String.format("Could not open filesystem: %s in image: %s for case: %s.", fileSysStr, imageStr, caseName),
ex);
}
checkIfCancelled();
}
}
}
@Override
public void run() {
try {
checkIfCancelled();
List<Image> images = getImages();
if (images == null) {
return;
}
checkIfCancelled();
openFileSystems(images);
} catch (InterruptedException ex) {
logger.log(
Level.INFO,
String.format("Background operation opening all file systems in %s has been cancelled.", caseName));
}
}
}
/**
* A case action (interface CaseAction<T, V, R>) that opens a case, deletes
* a data source from the case, and closes the case.
@@ -21,7 +21,9 @@ package org.sleuthkit.autopsy.centralrepository.eventlisteners;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
@@ -64,6 +66,15 @@ final class CaseEventListener implements PropertyChangeListener {
private static final Logger LOGGER = Logger.getLogger(CaseEventListener.class.getName());
private final ExecutorService jobProcessingExecutor;
private static final String CASE_EVENT_THREAD_NAME = "Case-Event-Listener-%d";
private static final Set<Case.Events> CASE_EVENTS_OF_INTEREST = EnumSet.of(
Case.Events.CONTENT_TAG_ADDED, Case.Events.CONTENT_TAG_DELETED,
Case.Events.BLACKBOARD_ARTIFACT_TAG_DELETED, Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED,
Case.Events.CONTENT_TAG_ADDED, Case.Events.CONTENT_TAG_DELETED,
Case.Events.DATA_SOURCE_ADDED,
Case.Events.TAG_DEFINITION_CHANGED,
Case.Events.CURRENT_CASE,
Case.Events.DATA_SOURCE_NAME_CHANGED);
CaseEventListener() {
jobProcessingExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(CASE_EVENT_THREAD_NAME).build());
@@ -82,6 +93,9 @@ final class CaseEventListener implements PropertyChangeListener {
LOGGER.log(Level.SEVERE, "Failed to get instance of db manager.", ex);
return;
}
// If any changes are made to which event types are handled the change
// must also be made to CASE_EVENTS_OF_INTEREST.
switch (Case.Events.valueOf(evt.getPropertyName())) {
case CONTENT_TAG_ADDED:
case CONTENT_TAG_DELETED: {
@@ -113,6 +127,20 @@ final class CaseEventListener implements PropertyChangeListener {
break;
}
}
/*
* Add all of our Case Event Listeners to the case.
*/
void installListeners() {
Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, this);
}
/*
* Remove all of our Case Event Listeners from the case.
*/
void uninstallListeners() {
Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, this);
}
private final class ContentTagTask implements Runnable {
@@ -101,7 +101,7 @@ public class Installer extends ModuleInstall {
* central repository.
*/
private void addApplicationEventListeners() {
Case.addPropertyChangeListener(caseEventListener);
caseEventListener.installListeners();
ingestEventListener.installListeners();
}
@@ -226,7 +226,7 @@ public class Installer extends ModuleInstall {
*
* THIS CODE IS NEVER EXECUTED.
*/
Case.removePropertyChangeListener(caseEventListener);
caseEventListener.uninstallListeners();
caseEventListener.shutdown();
ingestEventListener.shutdown();
ingestEventListener.uninstallListeners();
@@ -22,6 +22,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import javafx.util.Pair;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException;
@@ -75,11 +76,11 @@ abstract class AbstractWaypointFetcher implements WaypointBuilder.WaypointFilter
*
* @param mapWaypoints List of filtered MapWaypoints.
*/
abstract void handleFilteredWaypointSet(Set<MapWaypoint> mapWaypoints);
abstract void handleFilteredWaypointSet(Set<MapWaypoint> mapWaypoints, List<Set<MapWaypoint>> tracks);
@Override
public void process(List<Waypoint> waypoints) {
List<Track> tracks = null;
List<Track> tracks = new ArrayList<>();
if (filters.getArtifactTypes().contains(ARTIFACT_TYPE.TSK_GPS_TRACK)) {
try {
tracks = Track.getTracks(Case.getCurrentCase().getSleuthkitCase(), filters.getDataSources());
@@ -87,11 +88,15 @@ abstract class AbstractWaypointFetcher implements WaypointBuilder.WaypointFilter
logger.log(Level.WARNING, "Exception thrown while retrieving list of Tracks", ex);
}
}
Pair<List<Waypoint>, List<List<Waypoint>>> waypointsAndTracks = createWaypointList(waypoints, tracks);
final Set<MapWaypoint> pointSet = MapWaypoint.getWaypoints(waypointsAndTracks.getKey());
final List<Set<MapWaypoint>> trackSets = new ArrayList<>();
for (List<Waypoint> t : waypointsAndTracks.getValue()) {
trackSets.add(MapWaypoint.getWaypoints(t));
}
List<Waypoint> completeList = createWaypointList(waypoints, tracks);
final Set<MapWaypoint> pointSet = MapWaypoint.getWaypoints(completeList);
handleFilteredWaypointSet(pointSet);
handleFilteredWaypointSet(pointSet, trackSets);
}
/**
@@ -104,8 +109,9 @@ abstract class AbstractWaypointFetcher implements WaypointBuilder.WaypointFilter
* @return A list of waypoints including the tracks based on the current
* filters.
*/
private List<Waypoint> createWaypointList(List<Waypoint> waypoints, List<Track> tracks) {
private Pair<List<Waypoint>, List<List<Waypoint>>> createWaypointList(List<Waypoint> waypoints, List<Track> tracks) {
final List<Waypoint> completeList = new ArrayList<>();
List<List<Waypoint>> filteredTracks = new ArrayList<>();
if (tracks != null) {
Long timeRangeEnd;
@@ -117,19 +123,22 @@ abstract class AbstractWaypointFetcher implements WaypointBuilder.WaypointFilter
timeRangeStart = timeRangeEnd - (86400 * filters.getMostRecentNumDays());
completeList.addAll(getWaypointsInRange(timeRangeStart, timeRangeEnd, waypoints));
completeList.addAll(getTracksInRange(timeRangeStart, timeRangeEnd, tracks));
filteredTracks = getTracksInRange(timeRangeStart, timeRangeEnd, tracks);
for (List<Waypoint> filteredTrack : filteredTracks) {
completeList.addAll(filteredTrack);
}
} else {
completeList.addAll(waypoints);
for (Track track : tracks) {
completeList.addAll(track.getPath());
filteredTracks.add(track.getPath());
}
}
} else {
completeList.addAll(waypoints);
}
return completeList;
return new Pair<>(completeList, filteredTracks);
}
/**
@@ -158,31 +167,30 @@ abstract class AbstractWaypointFetcher implements WaypointBuilder.WaypointFilter
}
/**
* Return a list of waypoints from the given tracks that fall into for
* tracks that fall into the given time range. The track start time will
* used for determining if the whole track falls into the range.
* Return a list of lists of waypoints from the given tracks that fall into
* the given time range. The track start time will used for determining if
* the whole track falls into the range.
*
* @param timeRangeStart start timestamp of range (seconds from java epoch)
* @param timeRangeEnd start timestamp of range (seconds from java epoch)
* @param tracks Track list.
*
* @return A list of waypoints that that belong to tracks that fall into the
* time range.
* @return A list of lists of waypoints corresponding to belong to tracks
* that exist within the time range.
*/
private List<Waypoint> getTracksInRange(Long timeRangeStart, Long timeRangeEnd, List<Track> tracks) {
List<Waypoint> completeList = new ArrayList<>();
private List<List<Waypoint>> getTracksInRange(Long timeRangeStart, Long timeRangeEnd, List<Track> tracks) {
List<List<Waypoint>> ret = new ArrayList<>();
if (tracks != null) {
for (Track track : tracks) {
Long trackTime = track.getStartTime();
if ((trackTime == null && filters.showWaypointsWithoutTimeStamp())
|| (trackTime != null && (trackTime >= timeRangeStart && trackTime <= timeRangeEnd))) {
completeList.addAll(track.getPath());
ret.add(track.getPath());
}
}
}
return completeList;
return ret;
}
/**
@@ -330,7 +330,7 @@ public final class GeolocationTopComponent extends TopComponent {
*
* @param waypointList
*/
void addWaypointsToMap(Set<MapWaypoint> waypointList) {
void addWaypointsToMap(Set<MapWaypoint> waypointList, List<Set<MapWaypoint>> tracks) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
@@ -347,6 +347,8 @@ public final class GeolocationTopComponent extends TopComponent {
}
mapPanel.clearWaypoints();
mapPanel.setWaypoints(waypointList);
mapPanel.setTracks(tracks);
mapPanel.initializePainter();
setWaypointLoading(false);
geoFilterPanel.setEnabled(true);
}
@@ -499,8 +501,8 @@ public final class GeolocationTopComponent extends TopComponent {
}
@Override
void handleFilteredWaypointSet(Set<MapWaypoint> mapWaypoints) {
addWaypointsToMap(mapWaypoints);
void handleFilteredWaypointSet(Set<MapWaypoint> mapWaypoints, List<Set<MapWaypoint>> tracks) {
addWaypointsToMap(mapWaypoints, tracks);
}
}
}
@@ -19,11 +19,13 @@
package org.sleuthkit.autopsy.geolocation;
import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
@@ -71,6 +73,9 @@ import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException;
import org.sleuthkit.datamodel.TskCoreException;
import javax.imageio.ImageIO;
import javax.swing.SwingUtilities;
import org.jxmapviewer.painter.CompoundPainter;
import org.jxmapviewer.painter.Painter;
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
/**
* The map panel. This panel contains the jxmapviewer MapViewer
@@ -84,6 +89,7 @@ final public class MapPanel extends javax.swing.JPanel {
private boolean zoomChanging;
private KdTree<MapWaypoint> waypointTree;
private Set<MapWaypoint> waypointSet;
private List<Set<MapWaypoint>> tracks = new ArrayList<>();
private Popup currentPopup;
private final PopupFactory popupFactory;
@@ -96,6 +102,7 @@ final public class MapPanel extends javax.swing.JPanel {
private BufferedImage transparentWaypointImage;
private MapWaypoint currentlySelectedWaypoint;
private Set<MapWaypoint> currentlySelectedTrack;
/**
* Creates new form MapPanel
@@ -204,6 +211,10 @@ final public class MapPanel extends javax.swing.JPanel {
mapViewer.setCenterPosition(new GeoPosition(0, 0));
initializePainter();
}
void initializePainter() {
// Basic painters for the way points.
WaypointPainter<MapWaypoint> waypointPainter = new WaypointPainter<MapWaypoint>() {
@Override
@@ -217,7 +228,12 @@ final public class MapPanel extends javax.swing.JPanel {
};
waypointPainter.setRenderer(new MapWaypointRenderer());
mapViewer.setOverlayPainter(waypointPainter);
ArrayList<Painter<JXMapViewer>> painters = new ArrayList<>();
painters.add(new MapTrackRenderer(tracks));
painters.add(waypointPainter);
CompoundPainter<JXMapViewer> compoundPainter = new CompoundPainter<>(painters);
mapViewer.setOverlayPainter(compoundPainter);
}
/**
@@ -306,6 +322,15 @@ final public class MapPanel extends javax.swing.JPanel {
mapViewer.repaint();
}
/**
* Stores the given List of tracks from which to draw paths later
*
* @param tracks
*/
void setTracks(List<Set<MapWaypoint>> tracks) {
this.tracks = tracks;
}
/**
* Set the current zoom level.
*
@@ -324,6 +349,7 @@ final public class MapPanel extends javax.swing.JPanel {
void clearWaypoints() {
waypointTree = null;
currentlySelectedWaypoint = null;
currentlySelectedTrack = null;
if (currentPopup != null) {
currentPopup.hide();
}
@@ -661,9 +687,17 @@ final public class MapPanel extends javax.swing.JPanel {
if (!evt.isPopupTrigger() && SwingUtilities.isLeftMouseButton(evt)) {
List<MapWaypoint> waypoints = findClosestWaypoint(evt.getPoint());
if (waypoints.size() > 0) {
currentlySelectedWaypoint = waypoints.get(0);
MapWaypoint selection = waypoints.get(0);
currentlySelectedWaypoint = selection;
for (Set<MapWaypoint> track : tracks) {
if (track.contains(selection)) {
currentlySelectedTrack = track;
break;
}
}
} else {
currentlySelectedWaypoint = null;
currentlySelectedTrack = null;
}
showDetailsPopup();
}
@@ -691,18 +725,18 @@ final public class MapPanel extends javax.swing.JPanel {
*/
private class MapWaypointRenderer implements WaypointRenderer<MapWaypoint> {
private final Map<Color, BufferedImage> imageCache = new HashMap<>();
private final Map<Color, BufferedImage> dotImageCache = new HashMap<>();
private final Map<Color, BufferedImage> waypointImageCache = new HashMap<>();
/**
*
* @param waypoint the waypoint for which to get the color
* @param currentlySelectedWaypoint the waypoint that is currently
* selected
* @param waypoint the waypoint for which to get the color selected
* @return the color that this waypoint should be rendered
*/
private Color getColor(MapWaypoint waypoint, MapWaypoint currentlySelectedWaypoint) {
private Color getColor(MapWaypoint waypoint) {
Color baseColor = waypoint.getColor();
if (waypoint.equals(currentlySelectedWaypoint)) {
if (waypoint.equals(currentlySelectedWaypoint)
|| (currentlySelectedTrack != null && currentlySelectedTrack.contains(waypoint))) {
// Highlight this waypoint since it is selected
return Color.YELLOW;
} else {
@@ -710,10 +744,32 @@ final public class MapPanel extends javax.swing.JPanel {
}
}
/**
* Creates a dot image with the specified color
*
* @param color the color of the new image
* @return the new dot image
*/
private BufferedImage createTrackDotImage(Color color) {
int w = 10;
int h = 10;
BufferedImage ret = new BufferedImage(w + 2, h + 2, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = ret.createGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(color);
g.fillOval(1, 1, w, h);
g.setColor(Color.BLACK);
g.setStroke(new BasicStroke(1));
g.drawOval(1, 1, w, h);
g.dispose();
return ret;
}
/**
* Creates a waypoint image with the specified color
*
* @param color the color of the new waypoint image
* @param color the color of the new image
* @return the new waypoint image
*/
private BufferedImage createWaypointImage(Color color) {
@@ -723,6 +779,7 @@ final public class MapPanel extends javax.swing.JPanel {
BufferedImage ret = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = ret.createGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.drawImage(whiteWaypointImage, 0, 0, null);
g.setComposite(AlphaComposite.SrcIn);
g.setColor(color);
@@ -734,22 +791,88 @@ final public class MapPanel extends javax.swing.JPanel {
}
@Override
public void paintWaypoint(Graphics2D gd, JXMapViewer jxmv, MapWaypoint waypoint) {
Color color = getColor(waypoint, currentlySelectedWaypoint);
// Store computed images in cache for later use
BufferedImage image = imageCache.computeIfAbsent(color, k -> {
return createWaypointImage(color);
});
Point2D point = jxmv.getTileFactory().geoToPixel(waypoint.getPosition(), jxmv.getZoom());
public void paintWaypoint(Graphics2D g, JXMapViewer map, MapWaypoint waypoint) {
Color color = getColor(waypoint);
BufferedImage image;
int artifactType = waypoint.getArtifactTypeID();
Point2D point = map.getTileFactory().geoToPixel(waypoint.getPosition(), map.getZoom());
int x = (int) point.getX();
int y = (int) point.getY();
gd = (Graphics2D) gd.create();
gd.drawImage(image, x - image.getWidth() / 2, y - image.getHeight(), null);
gd.dispose();
if (artifactType == ARTIFACT_TYPE.TSK_GPS_TRACKPOINT.getTypeID()
|| artifactType == ARTIFACT_TYPE.TSK_GPS_TRACK.getTypeID()
|| artifactType == ARTIFACT_TYPE.TSK_GPS_ROUTE.getTypeID()) {
image = dotImageCache.computeIfAbsent(color, k -> {
return createTrackDotImage(color);
});
// Center the dot on the GPS coordinate
y -= image.getHeight() / 2;
} else {
image = waypointImageCache.computeIfAbsent(color, k -> {
return createWaypointImage(color);
});
// Align the bottom of the pin with the GPS coordinate
y -= image.getHeight();
}
// Center image horizontally on image
x -= image.getWidth() / 2;
Graphics2D g2d = (Graphics2D) g.create();
g2d.drawImage(image, x, y, null);
g2d.dispose();
}
}
/**
* Renderer for map track routes
*/
private class MapTrackRenderer implements Painter<JXMapViewer> {
private final List<Set<MapWaypoint>> tracks;
MapTrackRenderer(List<Set<MapWaypoint>> tracks) {
this.tracks = tracks;
}
private void drawRoute(Set<MapWaypoint> track, Graphics2D g, JXMapViewer map) {
int lastX = 0;
int lastY = 0;
boolean first = true;
for (MapWaypoint wp : track) {
Point2D p = map.getTileFactory().geoToPixel(wp.getPosition(), map.getZoom());
int thisX = (int) p.getX();
int thisY = (int) p.getY();
if (first) {
first = false;
} else {
g.drawLine(lastX, lastY, thisX, thisY);
}
lastX = thisX;
lastY = thisY;
}
}
@Override
public void paint(Graphics2D g, JXMapViewer map, int w, int h) {
Graphics2D g2d = (Graphics2D) g.create();
Rectangle bounds = map.getViewportBounds();
g2d.translate(-bounds.x, -bounds.y);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.BLACK);
g2d.setStroke(new BasicStroke(2));
for (Set<MapWaypoint> track : tracks) {
drawRoute(track, g2d, map);
}
g2d.dispose();
}
}
}
@@ -84,7 +84,7 @@ final class MapWaypoint extends KdTree.XYZPoint implements org.jxmapviewer.viewe
private final GeoPosition position;
/**
* Returns a list of of MapWaypoint objects for the given list of
* Returns a list of MapWaypoint objects for the given list of
* datamodel.Waypoint objects.
*
* @param dmWaypoints
@@ -199,6 +199,13 @@ final class MapWaypoint extends KdTree.XYZPoint implements org.jxmapviewer.viewe
return getFormattedDetails(dataModelWaypoint);
}
/**
* Returns the artifact type for this waypoint's data source
*/
int getArtifactTypeID() {
return dataModelWaypoint.getArtifact().getArtifactTypeID();
}
/**
* Returns a list of JMenuItems for the waypoint. The list list may contain
* nulls which should be removed or replaced with JSeparators.
@@ -98,7 +98,7 @@ public final class WaypointBuilder {
*
* @param wwaypoints This of waypoints.
*/
void process(List<Waypoint> wwaypoints);
void process(List<Waypoint> waypoints);
}
/**