mirror of
https://github.com/elisspace/autopsy.git
synced 2026-10-01 23:09:56 +00:00
Merge branch 'develop' of github.com:sleuthkit/autopsy into CloudSolrClient
This commit is contained in:
@@ -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.
|
||||
|
||||
+28
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user