diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/utils/IconsUtil.java b/Core/src/org/sleuthkit/autopsy/datamodel/utils/IconsUtil.java index 5a22888a2f..33291f3224 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/utils/IconsUtil.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/utils/IconsUtil.java @@ -121,6 +121,8 @@ public final class IconsUtil { imageFile = "web-account-type.png"; //NON-NLS } else if (typeID == ARTIFACT_TYPE.TSK_WEB_FORM_ADDRESS.getTypeID()) { imageFile = "web-form-address.png"; //NON-NLS + } else if (typeID == ARTIFACT_TYPE.TSK_GPS_AREA.getTypeID()) { + imageFile = "gps-area.png"; //NON-NLS } else { imageFile = "artifact-icon.png"; //NON-NLS } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/AbstractWaypointFetcher.java b/Core/src/org/sleuthkit/autopsy/geolocation/AbstractWaypointFetcher.java index fccdfacd29..613ffc3c85 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/AbstractWaypointFetcher.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/AbstractWaypointFetcher.java @@ -22,11 +22,11 @@ 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; import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationParseResult; +import org.sleuthkit.autopsy.geolocation.datamodel.Area; import org.sleuthkit.autopsy.geolocation.datamodel.Track; import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint; import org.sleuthkit.autopsy.geolocation.datamodel.WaypointBuilder; @@ -81,7 +81,7 @@ abstract class AbstractWaypointFetcher implements WaypointBuilder.WaypointFilter * @param wasEntirelySuccessful True if no errors occurred while processing. */ abstract void handleFilteredWaypointSet(Set mapWaypoints, List> tracks, - boolean wasEntirelySuccessful); + List> areas, boolean wasEntirelySuccessful); @Override public void process(GeoLocationParseResult waypointResults) { @@ -93,36 +93,54 @@ abstract class AbstractWaypointFetcher implements WaypointBuilder.WaypointFilter logger.log(Level.WARNING, "Exception thrown while retrieving list of Tracks", ex); } } - - Pair, List>> waypointsAndTracks = createWaypointList( + + GeoLocationParseResult areaResults = null; + if (filters.getArtifactTypes().contains(ARTIFACT_TYPE.TSK_GPS_AREA)) { + try { + areaResults = Area.getAreas(Case.getCurrentCase().getSleuthkitCase(), filters.getDataSources()); + } catch (GeoLocationDataException ex) { + logger.log(Level.WARNING, "Exception thrown while retrieving list of Areas", ex); + } + } + + GeoDataSet geoDataSet = createWaypointList( waypointResults.getItems(), - (trackResults == null) ? new ArrayList() : trackResults.getItems()); + (trackResults == null) ? new ArrayList<>() : trackResults.getItems(), + (areaResults == null) ? new ArrayList<>() : areaResults.getItems()); - final Set pointSet = MapWaypoint.getWaypoints(waypointsAndTracks.getKey()); + final Set pointSet = MapWaypoint.getWaypoints(geoDataSet.getWaypoints()); final List> trackSets = new ArrayList<>(); - for (List t : waypointsAndTracks.getValue()) { + for (List t : geoDataSet.getTracks()) { trackSets.add(MapWaypoint.getWaypoints(t)); } + final List> areaSets = new ArrayList<>(); + for (List t : geoDataSet.getAreas()) { + areaSets.add(MapWaypoint.getWaypoints(t)); + } handleFilteredWaypointSet( - pointSet, trackSets, - (trackResults == null || trackResults.isSuccessfullyParsed()) && waypointResults.isSuccessfullyParsed()); + pointSet, trackSets, areaSets, + (trackResults == null || trackResults.isSuccessfullyParsed()) + && (areaResults == null || areaResults.isSuccessfullyParsed()) + && waypointResults.isSuccessfullyParsed()); } /** - * Returns a complete list of waypoints including the tracks. Takes into + * Returns a complete list of waypoints including the tracks and areas. Takes into * account the current filters and includes waypoints as approprate. * * @param waypoints List of waypoints * @param tracks List of tracks + * @param areas List of areas * - * @return A list of waypoints including the tracks based on the current - * filters. + * @return A GeoDataSet object containing a list of waypoints including the tracks and areas based on the current + filters. */ - private Pair, List>> createWaypointList(List waypoints, List tracks) { + private GeoDataSet createWaypointList(List waypoints, List tracks, List areas) { final List completeList = new ArrayList<>(); List> filteredTracks = new ArrayList<>(); + List> filteredAreas = new ArrayList<>(); if (tracks != null) { Long timeRangeEnd; @@ -149,7 +167,14 @@ abstract class AbstractWaypointFetcher implements WaypointBuilder.WaypointFilter } else { completeList.addAll(waypoints); } - return new Pair<>(completeList, filteredTracks); + + // Areas don't have timestamps so add all of them + for (Area area : areas) { + completeList.addAll(area.getPath()); + filteredAreas.add(area.getPath()); + } + + return new GeoDataSet(completeList, filteredTracks, filteredAreas); } /** @@ -270,4 +295,31 @@ abstract class AbstractWaypointFetcher implements WaypointBuilder.WaypointFilter return -1L; } + + /** + * Utility class to collect filtered GPS objects. + */ + static class GeoDataSet { + private final List waypoints; + private final List> tracks; + private final List> areas; + + GeoDataSet(List waypoints, List> tracks, List> areas) { + this.waypoints = waypoints; + this.tracks = tracks; + this.areas = areas; + } + + List getWaypoints() { + return waypoints; + } + + List> getTracks() { + return tracks; + } + + List> getAreas() { + return areas; + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java index df91b2963e..8fe7a70542 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java @@ -71,7 +71,8 @@ class GeoFilterPanel extends javax.swing.JPanel { ARTIFACT_TYPE.TSK_GPS_SEARCH, ARTIFACT_TYPE.TSK_GPS_TRACK, ARTIFACT_TYPE.TSK_GPS_TRACKPOINT, - ARTIFACT_TYPE.TSK_METADATA_EXIF + ARTIFACT_TYPE.TSK_METADATA_EXIF, + ARTIFACT_TYPE.TSK_GPS_AREA }; /** @@ -523,6 +524,7 @@ class GeoFilterPanel extends javax.swing.JPanel { + " or attrs.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID() + " or attrs.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_TRACKPOINTS.getTypeID() + " or attrs.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_WAYPOINTS.getTypeID() + + " or attrs.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_AREAPOINTS.getTypeID() + " )" + " ) as innerTable"; try (SleuthkitCase.CaseDbQuery queryResult = sleuthkitCase.executeQuery(queryStr); diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java index 31ef953fdc..57ea19391c 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java @@ -115,7 +115,8 @@ public final class GeolocationTopComponent extends TopComponent { || eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE.getTypeID() || eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF.getTypeID() || eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK.getTypeID() - || eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACK.getTypeID())) { + || eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACK.getTypeID() + || eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_AREA.getTypeID())) { showRefreshPanel(true); } @@ -330,7 +331,7 @@ public final class GeolocationTopComponent extends TopComponent { * * @param waypointList */ - void addWaypointsToMap(Set waypointList, List> tracks) { + void addWaypointsToMap(Set waypointList, List> tracks, List> areas) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { @@ -348,6 +349,7 @@ public final class GeolocationTopComponent extends TopComponent { mapPanel.clearWaypoints(); mapPanel.setWaypoints(waypointList); mapPanel.setTracks(tracks); + mapPanel.setAreas(areas); mapPanel.initializePainter(); setWaypointLoading(false); geoFilterPanel.setEnabled(true); @@ -505,8 +507,9 @@ public final class GeolocationTopComponent extends TopComponent { } @Override - void handleFilteredWaypointSet(Set mapWaypoints, List> tracks, boolean wasEntirelySuccessful) { - addWaypointsToMap(mapWaypoints, tracks); + void handleFilteredWaypointSet(Set mapWaypoints, List> tracks, + List> areas, boolean wasEntirelySuccessful) { + addWaypointsToMap(mapWaypoints, tracks, areas); // if there is an error, present to the user. if (!wasEntirelySuccessful) { diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java index 0304891afb..b9a40afad0 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java @@ -23,6 +23,7 @@ import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; +import java.awt.Paint; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; @@ -30,6 +31,7 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; +import java.awt.geom.GeneralPath; import java.awt.geom.Point2D; import java.awt.image.BufferedImage; import java.beans.PropertyChangeEvent; @@ -91,11 +93,14 @@ final public class MapPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; private static final Set DOT_WAYPOINT_TYPES = new HashSet<>(); private static final int DOT_SIZE = 12; + private static final Set VERY_SMALL_DOT_WAYPOINT_TYPES = new HashSet<>(); + private static final int VERY_SMALL_DOT_SIZE = 6; private boolean zoomChanging; private KdTree waypointTree; private Set waypointSet; private List> tracks = new ArrayList<>(); + private List> areas = new ArrayList<>(); private Popup currentPopup; private final PopupFactory popupFactory; @@ -108,12 +113,13 @@ final public class MapPanel extends javax.swing.JPanel { private BufferedImage transparentWaypointImage; private MapWaypoint currentlySelectedWaypoint; - private Set currentlySelectedTrack; + private Set currentlySelectedSet; static { DOT_WAYPOINT_TYPES.add(ARTIFACT_TYPE.TSK_GPS_TRACKPOINT.getTypeID()); DOT_WAYPOINT_TYPES.add(ARTIFACT_TYPE.TSK_GPS_TRACK.getTypeID()); DOT_WAYPOINT_TYPES.add(ARTIFACT_TYPE.TSK_GPS_ROUTE.getTypeID()); + VERY_SMALL_DOT_WAYPOINT_TYPES.add(ARTIFACT_TYPE.TSK_GPS_AREA.getTypeID()); } /** @@ -241,6 +247,7 @@ final public class MapPanel extends javax.swing.JPanel { waypointPainter.setRenderer(new MapWaypointRenderer()); ArrayList> painters = new ArrayList<>(); + painters.add(new MapAreaRenderer(areas)); painters.add(new MapTrackRenderer(tracks)); painters.add(waypointPainter); @@ -342,6 +349,15 @@ final public class MapPanel extends javax.swing.JPanel { void setTracks(List> tracks) { this.tracks = tracks; } + + /** + * Stores the given list of areas from which to draw paths later + * + * @param areas + */ + void setAreas(List> areas) { + this.areas = areas; + } /** * Set the current zoom level. @@ -361,7 +377,7 @@ final public class MapPanel extends javax.swing.JPanel { void clearWaypoints() { waypointTree = null; currentlySelectedWaypoint = null; - currentlySelectedTrack = null; + currentlySelectedSet = null; if (currentPopup != null) { currentPopup.hide(); } @@ -514,6 +530,13 @@ final public class MapPanel extends javax.swing.JPanel { DOT_SIZE, DOT_SIZE ); + } else if (VERY_SMALL_DOT_WAYPOINT_TYPES.contains(nextWaypoint.getArtifactTypeID())) { + rect = new Rectangle( + pointX - (VERY_SMALL_DOT_SIZE / 2), + pointY - (VERY_SMALL_DOT_SIZE / 2), + VERY_SMALL_DOT_SIZE, + VERY_SMALL_DOT_SIZE + ); } else { rect = new Rectangle( pointX - (whiteWaypointImage.getWidth() / 2), @@ -717,16 +740,24 @@ final public class MapPanel extends javax.swing.JPanel { if (waypoints.size() > 0) { MapWaypoint selection = waypoints.get(0); currentlySelectedWaypoint = selection; - currentlySelectedTrack = null; + currentlySelectedSet = null; for (Set track : tracks) { if (track.contains(selection)) { - currentlySelectedTrack = track; + currentlySelectedSet = track; break; } } + if (currentlySelectedSet == null) { + for (Set area : areas) { + if (area.contains(selection)) { + currentlySelectedSet = area; + break; + } + } + } } else { currentlySelectedWaypoint = null; - currentlySelectedTrack = null; + currentlySelectedSet = null; } showDetailsPopup(); } @@ -755,6 +786,7 @@ final public class MapPanel extends javax.swing.JPanel { private class MapWaypointRenderer implements WaypointRenderer { private final Map dotImageCache = new HashMap<>(); + private final Map verySmallDotImageCache = new HashMap<>(); private final Map waypointImageCache = new HashMap<>(); /** @@ -766,7 +798,7 @@ final public class MapPanel extends javax.swing.JPanel { private Color getColor(MapWaypoint waypoint) { Color baseColor = waypoint.getColor(); if (waypoint.equals(currentlySelectedWaypoint) - || (currentlySelectedTrack != null && currentlySelectedTrack.contains(waypoint))) { + || (currentlySelectedSet != null && currentlySelectedSet.contains(waypoint))) { // Highlight this waypoint since it is selected return Color.YELLOW; } else { @@ -778,11 +810,11 @@ final public class MapPanel extends javax.swing.JPanel { * Creates a dot image with the specified color * * @param color the color of the new image + * @param s the size of the dot * * @return the new dot image */ - private BufferedImage createTrackDotImage(Color color) { - int s = DOT_SIZE; + private BufferedImage createTrackDotImage(Color color, int s) { BufferedImage ret = new BufferedImage(s, s, BufferedImage.TYPE_INT_ARGB); Graphics2D g = ret.createGraphics(); @@ -831,7 +863,13 @@ final public class MapPanel extends javax.swing.JPanel { if (DOT_WAYPOINT_TYPES.contains(waypoint.getArtifactTypeID())) { image = dotImageCache.computeIfAbsent(color, k -> { - return createTrackDotImage(color); + return createTrackDotImage(color, DOT_SIZE); + }); + // Center the dot on the GPS coordinate + y -= image.getHeight() / 2; + } else if (VERY_SMALL_DOT_WAYPOINT_TYPES.contains(waypoint.getArtifactTypeID())) { + image = verySmallDotImageCache.computeIfAbsent(color, k -> { + return createTrackDotImage(color, VERY_SMALL_DOT_SIZE); }); // Center the dot on the GPS coordinate y -= image.getHeight() / 2; @@ -903,4 +941,74 @@ final public class MapPanel extends javax.swing.JPanel { g2d.dispose(); } } + + /** + * Renderer for map areas + */ + private class MapAreaRenderer implements Painter { + + private final List> areas; + + MapAreaRenderer(List> areas) { + this.areas = areas; + } + + /** + * Shade in the area on the map. + * + * @param area The waypoints defining the outline of the area. + * @param g Graphics2D + * @param map JXMapViewer + */ + private void drawArea(Set area, Graphics2D g, JXMapViewer map) { + if (area.isEmpty()) { + return; + } + boolean first = true; + + GeneralPath polygon = new GeneralPath(GeneralPath.WIND_EVEN_ODD, area.size()); + + for (MapWaypoint wp : area) { + Point2D p = map.getTileFactory().geoToPixel(wp.getPosition(), map.getZoom()); + int thisX = (int) p.getX(); + int thisY = (int) p.getY(); + + if (first) { + polygon.moveTo(thisX, thisY); + first = false; + } else { + polygon.lineTo(thisX, thisY); + } + } + polygon.closePath(); + + Color areaColor = area.iterator().next().getColor(); + final double maxColorValue = 255.0; + g.setPaint(new Color((float)(areaColor.getRed() / maxColorValue), + (float)(areaColor.getGreen() / maxColorValue), + (float)(areaColor.getBlue() / maxColorValue), + .2f)); + g.fill(polygon); + g.draw(polygon); + } + + @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 area : areas) { + drawArea(area, g2d, map); + } + + g2d.dispose(); + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java index 21799e19fa..8d51dd6a36 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java @@ -79,6 +79,7 @@ final class MapWaypoint extends KdTree.XYZPoint implements org.jxmapviewer.viewe artifactTypesToColors.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACK.getTypeID(), Color.ORANGE); artifactTypesToColors.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT.getTypeID(), Color.ORANGE); artifactTypesToColors.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF.getTypeID(), Color.MAGENTA); + artifactTypesToColors.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_AREA.getTypeID(), new Color(0x8a2be2)); // Blue violet } private final Waypoint dataModelWaypoint; diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Area.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Area.java new file mode 100644 index 0000000000..65b2f84362 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Area.java @@ -0,0 +1,171 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 Basis Technology Corp. + * contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.geolocation.datamodel; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.blackboardutils.attributes.BlackboardJsonAttrUtil; +import org.sleuthkit.datamodel.blackboardutils.attributes.BlackboardJsonAttrUtil.InvalidJsonException; +import org.sleuthkit.datamodel.blackboardutils.attributes.GeoAreaPoints; + +/** + * A GPS track with which wraps the TSK_GPS_AREA artifact. + */ +public final class Area extends GeoPath { + /** + * Construct a new Area for the given artifact. + * + * @param artifact + * + * @throws GeoLocationDataException + */ + public Area(BlackboardArtifact artifact) throws GeoLocationDataException { + this(artifact, Waypoint.getAttributesFromArtifactAsMap(artifact)); + } + + /** + * Construct an Area for the given artifact and attributeMap. + * + * @param artifact TSK_GPD_TRACK artifact + * @param attributeMap Map of the artifact attributes + * + * @throws GeoLocationDataException + */ + private Area(BlackboardArtifact artifact, Map attributeMap) throws GeoLocationDataException { + super(artifact, getAreaName(attributeMap)); + + GeoAreaPoints points = getPointsList(attributeMap); + buildPath(points, artifact); + } + + /** + * Return the name of the area from the attributeMap. Track name is stored + * in the attribute TSK_NAME + * + * @param attributeMap + * + * @return Area name or empty string if none was available. + */ + private static String getAreaName(Map attributeMap) { + BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); + + return attribute != null ? attribute.getValueString() : ""; + } + + /** + * Create the list of AreaWaypoints from the GeoTrackPoint list. + * + * @param points GeoAreaPoints object. + * @param artifact The artifact to which these points belong + * + * @throws GeoLocationDataException + */ + @Messages({ + "# {0} - area name", + "GEOArea_point_label_header=Area outline point for area: {0}" + }) + private void buildPath(GeoAreaPoints points, BlackboardArtifact artifact) throws GeoLocationDataException { + for (GeoAreaPoints.AreaPoint point : points) { + addToPath(new AreaWaypoint(artifact, Bundle.GEOArea_point_label_header(getLabel()), point)); + } + } + + /** + * Returns the list of GeoAreaPoints from the attributeMap. Creates the + * GeoAreaPoint list from the TSK_GEO_AREAPOINTS attribute. + * + * @param attributeMap Map of artifact attributes. + * + * @return GeoTrackPoint list empty list if the attribute was not found. + * + * @throws GeoLocationDataException + */ + private GeoAreaPoints getPointsList(Map attributeMap) throws GeoLocationDataException { + BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_AREAPOINTS); + if (attribute == null) { + throw new GeoLocationDataException("No TSK_GEO_AREAPOINTS attribute present in attribute map to parse."); + } + + try { + return BlackboardJsonAttrUtil.fromAttribute(attribute, GeoAreaPoints.class); + } catch (InvalidJsonException ex) { + throw new GeoLocationDataException("Unable to parse area points in TSK_GEO_AREAPOINTS attribute", ex); + } + } + + /** + * A Waypoint subclass for the points of an area outline. + */ + final class AreaWaypoint extends Waypoint { + + private final List propertyList; + + /** + * Construct a AreaWaypoint. + * + * @param artifact the artifact to which this waypoint belongs + * + * @param pointLabel the label for the waypoint + * + * @param point GeoAreaPoint + * + * @throws GeoLocationDataException + */ + AreaWaypoint(BlackboardArtifact artifact, String pointLabel, GeoAreaPoints.AreaPoint point) throws GeoLocationDataException { + super(artifact, pointLabel, + null, + point.getLatitude(), + point.getLongitude(), + null, + null, + null, + Area.this); + + propertyList = createPropertyList(point); + } + + /** + * Overloaded to return a property list that is generated from the + * GeoTrackPoint instead of an artifact. + * + * @return unmodifiable list of Waypoint.Property + */ + @Override + public List getOtherProperties() { + return Collections.unmodifiableList(propertyList); + } + + /** + * Create a propertyList specific to GeoAreaPoints. + * + * @param point GeoAreaPoint to get values from. + * + * @return A list of Waypoint.properies. + */ + private List createPropertyList(GeoAreaPoints.AreaPoint point) { + List list = new ArrayList<>(); + return list; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Bundle.properties-MERGED index d603e85b7b..bf54a777e9 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Bundle.properties-MERGED @@ -1,3 +1,5 @@ +# {0} - area name +GEOArea_point_label_header=Area outline point for area: {0} # {0} - track name GEOTrack_point_label_header=Trackpoint for track: {0} LastKnownWaypoint_Label=Last Known Location diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeoPath.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeoPath.java index a1d369808a..4afbd922d0 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeoPath.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeoPath.java @@ -22,6 +22,8 @@ package org.sleuthkit.autopsy.geolocation.datamodel; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.logging.Level; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.SleuthkitCase; @@ -31,6 +33,8 @@ import org.sleuthkit.datamodel.TskCoreException; * Class representing a series of waypoints that form a path. */ public class GeoPath { + private static final Logger LOGGER = Logger.getLogger(GeoPath.class.getName()); + private final List waypointList; private final String pathName; private final BlackboardArtifact artifact; @@ -62,13 +66,13 @@ public class GeoPath { } /** - * Gets the list of Routes from the TSK_GPS_TRACK artifacts. + * Gets the list of Tracks from the TSK_GPS_TRACK artifacts. * * @param skCase Currently open SleuthkitCase * @param sourceList List of source to return tracks from, maybe null to * return tracks from all sources * - * @return List of Route objects, empty list will be returned if no Routes + * @return List of Track objects, empty list will be returned if no tracks * were found * * @throws GeoLocationDataException @@ -85,6 +89,7 @@ public class GeoPath { tracks.add(new Track(artifact)); } catch (GeoLocationDataException e) { + LOGGER.log(Level.WARNING, "Error loading track from artifact with ID " + artifact.getArtifactID(), e); allParsedSuccessfully = false; } } @@ -94,6 +99,41 @@ public class GeoPath { } return new GeoLocationParseResult(tracks, allParsedSuccessfully); } + + /** + * Gets the list of Areas from the TSK_GPS_AREA artifacts. + * + * @param skCase Currently open SleuthkitCase + * @param sourceList List of source to return areas from, may be null to + * return areas from all sources + * + * @return List of Area objects, empty list will be returned if no areas + * were found + * + * @throws GeoLocationDataException + */ + public static GeoLocationParseResult getAreas(SleuthkitCase skCase, List sourceList) throws GeoLocationDataException { + List artifacts; + boolean allParsedSuccessfully = true; + List areas = new ArrayList<>(); + try { + artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_AREA); + for (BlackboardArtifact artifact : artifacts) { + if (sourceList == null || sourceList.contains(artifact.getDataSource())) { + try { + areas.add(new Area(artifact)); + + } catch (GeoLocationDataException e) { + LOGGER.log(Level.WARNING, "Error loading track from artifact with ID " + artifact.getArtifactID(), e); + allParsedSuccessfully = false; + } + } + } + } catch (TskCoreException ex) { + throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_BOOKMARK", ex); + } + return new GeoLocationParseResult<>(areas, allParsedSuccessfully); + } /** * Path constructor. diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Track.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Track.java index af19623100..24432ee62d 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Track.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Track.java @@ -22,20 +22,17 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.logging.Level; import org.openide.util.NbBundle.Messages; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.blackboardutils.attributes.BlackboardJsonAttrUtil; import org.sleuthkit.datamodel.blackboardutils.attributes.BlackboardJsonAttrUtil.InvalidJsonException; import org.sleuthkit.datamodel.blackboardutils.attributes.GeoTrackPoints; -import org.sleuthkit.autopsy.coreutils.Logger; /** * A GPS track with which wraps the TSK_GPS_TRACK artifact. */ public final class Track extends GeoPath { - private static final Logger LOGGER = Logger.getLogger(Track.class.getName()); private final Long startTimestamp; private final Long endTimeStamp; @@ -134,14 +131,12 @@ public final class Track extends GeoPath { private GeoTrackPoints getPointsList(Map attributeMap) throws GeoLocationDataException { BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_TRACKPOINTS); if (attribute == null) { - LOGGER.log(Level.SEVERE, "No TSK_GEO_TRACKPOINTS attribute was present on the artifact."); throw new GeoLocationDataException("No TSK_GEO_TRACKPOINTS attribute present in attribute map to parse."); } try { return BlackboardJsonAttrUtil.fromAttribute(attribute, GeoTrackPoints.class); } catch (InvalidJsonException ex) { - LOGGER.log(Level.SEVERE, "TSK_GEO_TRACKPOINTS could not be properly parsed from TSK_GEO_TRACKPOINTS attribute."); throw new GeoLocationDataException("Unable to parse track points in TSK_GEO_TRACKPOINTS attribute", ex); } } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java index 076ac8b8f9..917c1572cd 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java @@ -178,6 +178,28 @@ public final class WaypointBuilder { return trackList; } + + /** + * Returns a list of areas from the given list of waypoints. + * + * @param waypoints A list of waypoints + * + * @return A list of areas or an empty list if none were found. + */ + public static List getAreas(List waypoints) { + List areaList = new ArrayList<>(); + for (Waypoint point : waypoints) { + GeoPath path = point.getParentGeoPath(); + if (path instanceof Area) { + Area area = (Area) path; + if (!areaList.contains(area)) { + areaList.add(area); + } + } + } + + return areaList; + } /** * Gets a list of Waypoints for TSK_GPS_TRACKPOINT artifacts. diff --git a/Core/src/org/sleuthkit/autopsy/images/gps-area.png b/Core/src/org/sleuthkit/autopsy/images/gps-area.png new file mode 100644 index 0000000000..32422261ad Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/images/gps-area.png differ diff --git a/Core/src/org/sleuthkit/autopsy/report/infrastructure/TableReportGenerator.java b/Core/src/org/sleuthkit/autopsy/report/infrastructure/TableReportGenerator.java index 6b8e4a4957..0e2d9dc976 100644 --- a/Core/src/org/sleuthkit/autopsy/report/infrastructure/TableReportGenerator.java +++ b/Core/src/org/sleuthkit/autopsy/report/infrastructure/TableReportGenerator.java @@ -1793,6 +1793,7 @@ class TableReportGenerator { || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK.getTypeID() || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION.getTypeID() || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH.getTypeID() + || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_AREA.getTypeID() || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_SERVICE_ACCOUNT.getTypeID() || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED.getTypeID() || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED.getTypeID() diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/html/HTMLReport.java b/Core/src/org/sleuthkit/autopsy/report/modules/html/HTMLReport.java index 32cdd0a0b0..d431d5a65c 100644 --- a/Core/src/org/sleuthkit/autopsy/report/modules/html/HTMLReport.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/html/HTMLReport.java @@ -384,6 +384,9 @@ public class HTMLReport implements TableReportModule { case TSK_WEB_FORM_ADDRESS: in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/web-form-address.png"); //NON-NLS break; + case TSK_GPS_AREA: + in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/gps-area.png"); //NON-NLS + break; default: logger.log(Level.WARNING, "useDataTypeIcon: unhandled artifact type = {0}", dataType); //NON-NLS in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/star.png"); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/kml/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/report/modules/kml/Bundle.properties-MERGED index c1fe7e6ac1..93bb51b8db 100755 --- a/Core/src/org/sleuthkit/autopsy/report/modules/kml/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/report/modules/kml/Bundle.properties-MERGED @@ -27,6 +27,7 @@ ReportKML.genReport.reportName=KML Report ReportKML.latLongStartPoint={0};{1};;{2} (Start)\n ReportKML.latLongEndPoint={0};{1};;{2} (End)\n Route_Details_Header=GPS Route +Waypoint_Area_Point_Display_String=GPS Area Outline Point Waypoint_Bookmark_Display_String=GPS Bookmark Waypoint_EXIF_Display_String=EXIF Metadata With Location Waypoint_Last_Known_Display_String=GPS Last Known Location diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java b/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java index 607b8879fe..7ca8749e15 100644 --- a/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java @@ -50,6 +50,7 @@ import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationParseResult; import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint; import org.sleuthkit.autopsy.geolocation.datamodel.Route; import org.sleuthkit.autopsy.geolocation.datamodel.Track; +import org.sleuthkit.autopsy.geolocation.datamodel.Area; import org.sleuthkit.autopsy.geolocation.datamodel.WaypointBuilder; import org.sleuthkit.autopsy.report.GeneralReportSettings; import org.sleuthkit.autopsy.report.ReportBranding; @@ -84,6 +85,7 @@ public final class KMLReport implements GeneralReportModule { private Element gpsSearchesFolder; private Element gpsTrackpointsFolder; private Element gpsTracksFolder; + private Element gpsAreasFolder; private GeneralReportSettings settings; @@ -154,7 +156,8 @@ public final class KMLReport implements GeneralReportModule { "Waypoint_Track_Display_String=GPS Track", "Route_Details_Header=GPS Route", "ReportBodyFile.ingestWarning.text=Ingest Warning message", - "Waypoint_Track_Point_Display_String=GPS Individual Track Point" + "Waypoint_Track_Point_Display_String=GPS Individual Track Point", + "Waypoint_Area_Point_Display_String=GPS Area Outline Point", }) public void generateReport(String baseReportDir, ReportProgressPanel progressPanel, List waypointList) { @@ -216,6 +219,11 @@ public final class KMLReport implements GeneralReportModule { result = ReportProgressPanel.ReportStatus.ERROR; errorMessage = Bundle.KMLReport_partialFailure(); } + entirelySuccessful = makeAreas(skCase); + if (!entirelySuccessful) { + result = ReportProgressPanel.ReportStatus.ERROR; + errorMessage = Bundle.KMLReport_partialFailure(); + } addLocationsToReport(skCase, baseReportDir); } catch (GeoLocationDataException | IOException | TskCoreException ex) { @@ -326,6 +334,11 @@ public final class KMLReport implements GeneralReportModule { CDATA cdataTrack = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-trackpoint.png"); //NON-NLS Element hrefTrack = new Element("href", ns).addContent(cdataTrack); //NON-NLS gpsTracksFolder.addContent(new Element("Icon", ns).addContent(hrefTrack)); //NON-NLS + + gpsAreasFolder = new Element("Folder", ns); //NON-NLS + CDATA cdataArea = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-area.png"); //NON-NLS + Element hrefArea = new Element("href", ns).addContent(cdataArea); //NON-NLS + gpsAreasFolder.addContent(new Element("Icon", ns).addContent(hrefArea)); //NON-NLS gpsExifMetadataFolder.addContent(new Element("name", ns).addContent("EXIF Metadata")); //NON-NLS gpsBookmarksFolder.addContent(new Element("name", ns).addContent("GPS Bookmarks")); //NON-NLS @@ -334,6 +347,7 @@ public final class KMLReport implements GeneralReportModule { gpsSearchesFolder.addContent(new Element("name", ns).addContent("GPS Searches")); //NON-NLS gpsTrackpointsFolder.addContent(new Element("name", ns).addContent("GPS Trackpoints")); //NON-NLS gpsTracksFolder.addContent(new Element("name", ns).addContent("GPS Tracks")); //NON-NLS + gpsAreasFolder.addContent(new Element("name", ns).addContent("GPS Areas")); //NON-NLS document.addContent(gpsExifMetadataFolder); document.addContent(gpsBookmarksFolder); @@ -342,6 +356,7 @@ public final class KMLReport implements GeneralReportModule { document.addContent(gpsSearchesFolder); document.addContent(gpsTrackpointsFolder); document.addContent(gpsTracksFolder); + document.addContent(gpsAreasFolder); return kmlDocument; } @@ -570,6 +585,62 @@ public final class KMLReport implements GeneralReportModule { point.getTimestamp(), element, formattedCoordinates(point.getLatitude(), point.getLongitude()))); //NON-NLS } } + + /** + * Add the area to the area folder in the document. + * + * @param skCase Currently open case. + * @return The operation was entirely successful. + * + * @throws TskCoreException + */ + boolean makeAreas(SleuthkitCase skCase) throws GeoLocationDataException, TskCoreException { + List areas; + boolean successful = true; + + if (waypointList == null) { + GeoLocationParseResult result = Area.getAreas(skCase, null); + areas = result.getItems(); + successful = result.isSuccessfullyParsed(); + } else { + areas = WaypointBuilder.getAreas(waypointList); + } + + for (Area area : areas) { + if(shouldFilterFromReport(area.getArtifact())) { + continue; + } + addAreaToReport(area); + } + + return successful; + } + + /** + * Add a area to the KML report. + * + * @param area + */ + private void addAreaToReport(Area area) { + List areaPoints = area.getPath(); + + if (areaPoints.isEmpty()) { + return; + } + + // Adding a folder with the area name so that all of the + // area border points will be grouped together. + Element areaFolder = new Element("Folder", ns); //NON-NLS + areaFolder.addContent(new Element("name", ns).addContent(area.getLabel())); //NON-NLS + gpsAreasFolder.addContent(areaFolder); + + // Create a polygon using the waypoints + Element element = makePolygon(areaPoints); + Waypoint firstWp = areaPoints.get(0); + areaFolder.addContent(makePlacemark("", + FeatureColor.GREEN, getFormattedDetails(firstWp, Bundle.Waypoint_Area_Point_Display_String()), + firstWp.getTimestamp(), element, formattedCoordinates(firstWp.getLatitude(), firstWp.getLongitude()))); //NON-NLS + } /** * Format a point time stamp (in seconds) to the report format. @@ -628,7 +699,7 @@ public final class KMLReport implements GeneralReportModule { point.addContent(coordinates); return point; - } + } /** * Create a LineString for use in a Placemark. Note in this method, start @@ -662,6 +733,35 @@ public final class KMLReport implements GeneralReportModule { return lineString; } + /** + * Create a Polygon for use in a Placemark. + * + * @param waypoints The waypoints making up the outline. + * + * @return the Polygon as an Element + */ + private Element makePolygon(List waypoints) { + + Element polygon = new Element("Polygon", ns); //NON-NLS + + Element altitudeMode = new Element("altitudeMode", ns).addContent("clampToGround"); //NON-NLS + polygon.addContent(altitudeMode); + + // KML uses lon, lat. Deliberately reversed. + Element coordinates = new Element("coordinates", ns); + for (Waypoint wp : waypoints) { + coordinates.addContent(wp.getLongitude() + "," + wp.getLatitude() + ",0 "); //NON-NLS + } + // Add the first one again + coordinates.addContent(waypoints.get(0).getLongitude() + "," + waypoints.get(0).getLatitude() + ",0 "); //NON-NLS + + Element linearRing = new Element("LinearRing", ns).addContent(coordinates); + Element outerBoundary = new Element("outerBoundaryIs", ns).addContent(linearRing); + polygon.addContent(outerBoundary); + + return polygon; + } + /** * Make a Placemark for use in displaying features. Takes a * coordinate-bearing feature (Point, LineString, etc) and places it in the