diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/AbstractWaypointFetcher.java b/Core/src/org/sleuthkit/autopsy/geolocation/AbstractWaypointFetcher.java index bc56968f9926f1f742f53fb36dd0dc8618bb8674..e923d6dbd67b71036c1e76455c699440d76000cc 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/AbstractWaypointFetcher.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/AbstractWaypointFetcher.java @@ -22,6 +22,7 @@ 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 @@ void getWaypoints() throws GeoLocationDataException { * * @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 @@ public void process(List<Waypoint> waypoints) { 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 @@ public void process(List<Waypoint> waypoints) { * @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 @@ private List<Waypoint> createWaypointList(List<Waypoint> waypoints, List<Track> 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 @@ private List<Waypoint> getWaypointsInRange(Long timeRangeStart, Long timeRangeEn } /** - * 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; } /** diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java index da8e14a65baa81da975a3b44bcac2e1b8c7fcd51..ae387c7847b03d9d6be6369281cb5b0b58da68fc 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java @@ -330,7 +330,7 @@ public void run() { * * @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 void run() { } mapPanel.clearWaypoints(); mapPanel.setWaypoints(waypointList); + mapPanel.setTracks(tracks); + mapPanel.initializePainter(); setWaypointLoading(false); geoFilterPanel.setEnabled(true); } @@ -499,8 +501,8 @@ final private class WaypointFetcher extends AbstractWaypointFetcher { } @Override - void handleFilteredWaypointSet(Set<MapWaypoint> mapWaypoints) { - addWaypointsToMap(mapWaypoints); + void handleFilteredWaypointSet(Set<MapWaypoint> mapWaypoints, List<Set<MapWaypoint>> tracks) { + addWaypointsToMap(mapWaypoints, tracks); } } } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java index 46600f2c86ccef1be0b6e7b442b0e58d761b2cfe..44de21ff81725a2fef97e0adda9f9a445cd03505 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java @@ -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.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 @@ public void propertyChange(PropertyChangeEvent evt) { 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 @@ public Set<MapWaypoint> getWaypoints() { }; 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 @@ void setWaypoints(Set<MapWaypoint> waypoints) { 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 @@ void setZoom(int zoom) { void clearWaypoints() { waypointTree = null; currentlySelectedWaypoint = null; + currentlySelectedTrack = null; if (currentPopup != null) { currentPopup.hide(); } @@ -661,9 +687,17 @@ private void mapViewerMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:e 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 @@ private void zoomOutBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FI */ 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 @@ private Color getColor(MapWaypoint waypoint, MapWaypoint currentlySelectedWaypoi } } + /** + * 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 @@ private BufferedImage createWaypointImage(Color color) { 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 @@ private BufferedImage createWaypointImage(Color color) { } @Override - public void paintWaypoint(Graphics2D gd, JXMapViewer jxmv, MapWaypoint waypoint) { - Color color = getColor(waypoint, currentlySelectedWaypoint); + 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(); - // Store computed images in cache for later use - BufferedImage image = imageCache.computeIfAbsent(color, k -> { - return createWaypointImage(color); - }); + 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; - Point2D point = jxmv.getTileFactory().geoToPixel(waypoint.getPosition(), jxmv.getZoom()); + Graphics2D g2d = (Graphics2D) g.create(); + g2d.drawImage(image, x, y, null); + g2d.dispose(); + } + } - int x = (int) point.getX(); - int y = (int) point.getY(); + /** + * 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); + } - gd = (Graphics2D) gd.create(); - gd.drawImage(image, x - image.getWidth() / 2, y - image.getHeight(), null); - gd.dispose(); + g2d.dispose(); } } } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java index f9e4ed86ee5f10775da71524f1ef30e8c34f3632..f7673338b1e4ef8988669c9efc34b12d07eb35bc 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java @@ -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 @@ String getHTMLFormattedWaypointDetails() { 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. diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java index 1f8f25452e25ac690666724b4871cb6fbd816f60..11ff0107dd9fe5dd6e8925e90fd3fae310ac694d 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java @@ -98,7 +98,7 @@ public interface WaypointFilterQueryCallBack { * * @param wwaypoints This of waypoints. */ - void process(List<Waypoint> wwaypoints); + void process(List<Waypoint> waypoints); } /**