From d154ac98b69b52bee5b2aa44b16dca117225d6ef Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Thu, 14 Nov 2019 13:11:08 -0500 Subject: [PATCH] First pass of the waypoint filtering --- .../autopsy/geolocation/Bundle.properties | 10 + .../geolocation/Bundle.properties-MERGED | 12 + .../autopsy/geolocation/CheckBoxJList.java | 109 ++++ .../geolocation/CheckBoxListPanel.form | 71 +++ .../geolocation/CheckBoxListPanel.java | 234 +++++++++ .../autopsy/geolocation/GeoFilterPanel.form | 168 ++++++ .../autopsy/geolocation/GeoFilterPanel.java | 360 +++++++++++++ .../geolocation/GeoLocationUIException.java | 46 ++ .../geolocation/GeolocationTopComponent.form | 14 +- .../geolocation/GeolocationTopComponent.java | 77 ++- .../autopsy/geolocation/HidingPane.java | 120 +++++ .../autopsy/geolocation/MapPanel.java | 4 +- .../autopsy/geolocation/MapWaypoint.java | 25 +- .../autopsy/geolocation/VerticalLabelUI.java | 110 ++++ .../geolocation/datamodel/Waypoint.java | 180 ------- .../datamodel/WaypointBuilder.java | 484 ++++++++++++++++++ .../autopsy/geolocation/images/blueGeo16.png | Bin 0 -> 741 bytes .../autopsy/geolocation/images/blueGeo64.png | Bin 0 -> 3972 bytes .../autopsy/geolocation/images/funnel.png | Bin 0 -> 591 bytes .../autopsy/geolocation/images/tick.png | Bin 0 -> 582 bytes .../autopsy/report/modules/kml/KMLReport.java | 11 +- 21 files changed, 1844 insertions(+), 191 deletions(-) create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxJList.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.form create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.form create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/GeoLocationUIException.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/HidingPane.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/VerticalLabelUI.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/images/blueGeo16.png create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/images/blueGeo64.png create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/images/funnel.png create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/images/tick.png diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties index 8fa233ea33..4fe5148ba5 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties @@ -7,3 +7,13 @@ RefreshPanel.closeButton.text= MapPanel.cordLabel.text= WaypointDetailPanel.closeButton.text= WaypointDetailPanel.imageLabel.text= +GeoFilterPanel.waypointSettings.border.title= +GeoFilterPanel.allButton.text=Show All +GeoFilterPanel.mostRecentButton.text=Hide items older than +GeoFilterPanel.applyButton.text=Apply +GeoFilterPanel.showWaypointsWOTSCheckBox.text=Show waypoints without time stamp +GeoFilterPanel.daysLabel.text=days +CheckBoxListPanel.titleLabel.text=jLabel1 +CheckBoxListPanel.checkButton.text=Check All +CheckBoxListPanel.uncheckButton.text=Uncheck All +GeoFilterPanel.optionsLabel.text=Waypoints diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED index 95dd0d23f0..ed550cd8db 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED @@ -1,6 +1,8 @@ CTL_OpenGeolocation=Geolocation CTL_GeolocationTopComponentAction=GeolocationTopComponent CTL_GeolocationTopComponent=Geolocation +GeoFilterPanel_DataSource_List_Title=Data Sources +GeoFilterPanel_empty_dataSource=Data Source list is empty. GLTopComponent_initilzation_error=An error occurred during waypoint initilization. Geolocation data maybe incomplete. GLTopComponent_name=Geolocation MayWaypoint_ExternalViewer_label=Open in ExternalViewer @@ -12,4 +14,14 @@ RefreshPanel.closeButton.text= MapPanel.cordLabel.text= WaypointDetailPanel.closeButton.text= WaypointDetailPanel.imageLabel.text= +GeoFilterPanel.waypointSettings.border.title= +GeoFilterPanel.allButton.text=Show All +GeoFilterPanel.mostRecentButton.text=Hide items older than +GeoFilterPanel.applyButton.text=Apply +GeoFilterPanel.showWaypointsWOTSCheckBox.text=Show waypoints without time stamp +GeoFilterPanel.daysLabel.text=days +CheckBoxListPanel.titleLabel.text=jLabel1 +CheckBoxListPanel.checkButton.text=Check All +CheckBoxListPanel.uncheckButton.text=Uncheck All +GeoFilterPanel.optionsLabel.text=Waypoints WaypointExtractAction_label=Extract Files(s) diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxJList.java b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxJList.java new file mode 100755 index 0000000000..52276b6046 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxJList.java @@ -0,0 +1,109 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 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; + +import java.awt.Component; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import javax.swing.JCheckBox; +import javax.swing.JList; +import javax.swing.ListCellRenderer; +import javax.swing.ListSelectionModel; + +/** + * A JList that renders the list items as check boxes. + */ +final class CheckBoxJList extends JList { + + private static final long serialVersionUID = 1L; + + /** + * Simple interface that must be implement for an object to be displayed as + * a checkbox in CheckBoxJList. + * + */ + interface CheckboxListItem { + + /** + * Returns the checkbox state. + * + * @return True if the check box should be checked + */ + boolean isChecked(); + + /** + * Set the state of the check box. + * + * @param checked + */ + void setChecked(boolean checked); + + /** + * Returns String to display as the check box label + * + * @return + */ + String getDisplayName(); + } + + /** + * Construct a new JCheckBoxList. + */ + CheckBoxJList() { + initalize(); + } + + /** + * Do all of the UI initialization. + */ + private void initalize() { + setCellRenderer(new CellRenderer()); + addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + int index = locationToIndex(e.getPoint()); + if (index != -1) { + CheckBoxJList.CheckboxListItem element = getModel().getElementAt(index); + element.setChecked(!element.isChecked()); + repaint(); + } + } + }); + setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + } + + /** + * A ListCellRenderer that renders list elements as check boxes. + */ + class CellRenderer extends JCheckBox implements ListCellRenderer { + + private static final long serialVersionUID = 1L; + + @Override + public Component getListCellRendererComponent( + JList list, CheckBoxJList.CheckboxListItem value, int index, + boolean isSelected, boolean cellHasFocus) { + + setBackground(list.getBackground()); + setSelected(value.isChecked()); + setText(value.getDisplayName()); + return this; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.form b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.form new file mode 100755 index 0000000000..75fb1d4c62 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.form @@ -0,0 +1,71 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.java new file mode 100755 index 0000000000..8e465dd3a3 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.java @@ -0,0 +1,234 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 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; + +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import javax.swing.DefaultListModel; +import javax.swing.Icon; + +/** + * A panel for showing Content objects in a check box list. + */ +final class CheckBoxListPanel extends javax.swing.JPanel { + + private static final long serialVersionUID = 1L; + + private final DefaultListModel> model = new DefaultListModel<>(); + private final CheckBoxJList> checkboxList; + + /** + * Creates new CheckboxFilterPanel + */ + CheckBoxListPanel() { + initComponents(); + + checkboxList = new CheckBoxJList<>(); + checkboxList.setModel(model); + scrollPane.setViewportView(checkboxList); + } + + /** + * Add a new element to the check box list. + * + * @param displayName display name for the checkbox + * @param obj Object that the checkbox represents + */ + void addElement(String displayName, T obj) { + model.addElement(new ObjectCheckBox<>(displayName, true, obj)); + } + + /** + * Returns a list of all of the selected elements. + * + * @return List of selected elements. + */ + List getSelectedElements() { + List selectedElements = new ArrayList<>(); + Enumeration> elements = model.elements(); + + while (elements.hasMoreElements()) { + ObjectCheckBox element = elements.nextElement(); + if (element.isChecked()) { + selectedElements.add(element.getObject()); + } + } + + return selectedElements; + } + + /** + * Sets the selection state of the all the check boxes in the list. + * + * @param selected True to check the boxes, false to unchecked + */ + void setSetAllSelected(boolean selected) { + Enumeration> enumeration = model.elements(); + while (enumeration.hasMoreElements()) { + ObjectCheckBox element = enumeration.nextElement(); + element.setChecked(selected); + checkboxList.repaint(); + checkboxList.revalidate(); + + } + } + + /** + * Sets the panel title. + * + * @param title Panel title or null for no title. + */ + void setPanelTitle(String title) { + titleLabel.setText(title); + } + + /** + * Sets the panel title icon. + * + * @param icon Icon to set or null for no icon + */ + void setPanelTitleIcon(Icon icon) { + titleLabel.setIcon(icon); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + java.awt.GridBagConstraints gridBagConstraints; + + titleLabel = new javax.swing.JLabel(); + uncheckButton = new javax.swing.JButton(); + checkButton = new javax.swing.JButton(); + scrollPane = new javax.swing.JScrollPane(); + + setLayout(new java.awt.GridBagLayout()); + + org.openide.awt.Mnemonics.setLocalizedText(titleLabel, org.openide.util.NbBundle.getMessage(CheckBoxListPanel.class, "CheckBoxListPanel.titleLabel.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.gridwidth = 3; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + add(titleLabel, gridBagConstraints); + + org.openide.awt.Mnemonics.setLocalizedText(uncheckButton, org.openide.util.NbBundle.getMessage(CheckBoxListPanel.class, "CheckBoxListPanel.uncheckButton.text")); // NOI18N + uncheckButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + uncheckButtonActionPerformed(evt); + } + }); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 2; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new java.awt.Insets(0, 0, 0, 9); + add(uncheckButton, gridBagConstraints); + + org.openide.awt.Mnemonics.setLocalizedText(checkButton, org.openide.util.NbBundle.getMessage(CheckBoxListPanel.class, "CheckBoxListPanel.checkButton.text")); // NOI18N + checkButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + checkButtonActionPerformed(evt); + } + }); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 2; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST; + add(checkButton, gridBagConstraints); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + gridBagConstraints.gridwidth = 3; + gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.weighty = 1.0; + gridBagConstraints.insets = new java.awt.Insets(5, 0, 9, 0); + add(scrollPane, gridBagConstraints); + }// //GEN-END:initComponents + + private void uncheckButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_uncheckButtonActionPerformed + setSetAllSelected(false); + }//GEN-LAST:event_uncheckButtonActionPerformed + + private void checkButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_checkButtonActionPerformed + setSetAllSelected(true); + }//GEN-LAST:event_checkButtonActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton checkButton; + private javax.swing.JScrollPane scrollPane; + private javax.swing.JLabel titleLabel; + private javax.swing.JButton uncheckButton; + // End of variables declaration//GEN-END:variables + + /** + * Wrapper around T that implements CheckboxListItem + * + * @param + */ + final class ObjectCheckBox implements CheckBoxJList.CheckboxListItem { + + private static final long serialVersionUID = 1L; + + private final T object; + private final String displayName; + private boolean checked; + + /** + * Constructs a new ObjectCheckBox + * + * @param displayName String to show as the check box label + * @param initialState Sets the initial state of the check box + * @param object Object that the check box represents. + */ + ObjectCheckBox(String displayName, boolean initialState, T object) { + this.displayName = displayName; + this.object = object; + this.checked = initialState; + } + + T getObject() { + return object; + } + + @Override + public boolean isChecked() { + return checked; + } + + @Override + public void setChecked(boolean checked) { + this.checked = checked; + } + + @Override + public String getDisplayName() { + return displayName; + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.form b/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.form new file mode 100755 index 0000000000..897341c482 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.form @@ -0,0 +1,168 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java new file mode 100755 index 0000000000..bb3035b5d5 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java @@ -0,0 +1,360 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 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; + +import java.awt.GridBagConstraints; +import java.awt.event.ActionListener; +import java.util.Collections; +import java.util.List; +import java.util.logging.Level; +import javax.swing.ImageIcon; +import javax.swing.SpinnerNumberModel; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * + * Panel to display the filter options for geolocation waypoints. + */ +class GeoFilterPanel extends javax.swing.JPanel { + + private static final long serialVersionUID = 1L; + private static final Logger logger = Logger.getLogger(GeoFilterPanel.class.getName()); + + private final SpinnerNumberModel numberModel; + private final CheckBoxListPanel checkboxPanel; + + /** + * Creates new GeoFilterPanel + */ + @Messages({ + "GeoFilterPanel_DataSource_List_Title=Data Sources" + }) + GeoFilterPanel() { + // numberModel is used in initComponents + numberModel = new SpinnerNumberModel(10, 1, Integer.MAX_VALUE, 1); + + initComponents(); + + // The gui builder cannot handle using CheckBoxListPanel due to its + // use of generics so we will initalize it here. + checkboxPanel = new CheckBoxListPanel<>(); + checkboxPanel.setPanelTitle(Bundle.GeoFilterPanel_DataSource_List_Title()); + checkboxPanel.setPanelTitleIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/image.png"))); + checkboxPanel.setSetAllSelected(true); + + GridBagConstraints gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 3; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.weighty = 1.0; + gridBagConstraints.insets = new java.awt.Insets(0, 15, 0, 15); + add(checkboxPanel, gridBagConstraints); + + try { + initCheckboxList(); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Failed to initialize the CheckboxListPane", ex); //NON-NLS + } + } + + /** + * Adds an actionListener to listen for the filter apply action + * + * @param listener + */ + void addActionListener(ActionListener listener) { + applyButton.addActionListener(listener); + } + + /** + * Returns the selected filter values. + * + * @return A GeoFilter object with the user selected filter values + * + * @throws GeoLocationUIException + */ + @Messages({ + "GeoFilterPanel_empty_dataSource=Data Source list is empty." + }) + GeoFilter getFilterState() throws GeoLocationUIException { + boolean showAll = allButton.isSelected(); + boolean withTimeStamp = showWaypointsWOTSCheckBox.isSelected(); + int dayCnt = numberModel.getNumber().intValue(); + + List dataSources = checkboxPanel.getSelectedElements(); + + if (dataSources.isEmpty()) { + throw new GeoLocationUIException(Bundle.GeoFilterPanel_empty_dataSource()); + } + + return new GeoFilter(showAll, withTimeStamp, dayCnt, dataSources); + } + + /** + * Initialize the checkbox list panel + * + * @throws TskCoreException + */ + private void initCheckboxList() throws TskCoreException { + final SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase(); + + for (DataSource dataSource : sleuthkitCase.getDataSources()) { + String dsName = sleuthkitCase.getContentById(dataSource.getId()).getName(); + checkboxPanel.addElement(dsName, dataSource); + } + } + + /** + * Based on the state of mostRecent radio button Change the state of the cnt + * spinner and the time stamp checkbox. + */ + private void updateWaypointOptions() { + boolean selected = mostRecentButton.isSelected(); + showWaypointsWOTSCheckBox.setEnabled(selected); + daysSpinner.setEnabled(selected); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + java.awt.GridBagConstraints gridBagConstraints; + + buttonGroup = new javax.swing.ButtonGroup(); + waypointSettings = new javax.swing.JPanel(); + allButton = new javax.swing.JRadioButton(); + mostRecentButton = new javax.swing.JRadioButton(); + showWaypointsWOTSCheckBox = new javax.swing.JCheckBox(); + daysSpinner = new javax.swing.JSpinner(numberModel); + javax.swing.JLabel daysLabel = new javax.swing.JLabel(); + buttonPanel = new javax.swing.JPanel(); + applyButton = new javax.swing.JButton(); + javax.swing.JLabel optionsLabel = new javax.swing.JLabel(); + + setLayout(new java.awt.GridBagLayout()); + + waypointSettings.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(GeoFilterPanel.class, "GeoFilterPanel.waypointSettings.border.title"))); // NOI18N + waypointSettings.setLayout(new java.awt.GridBagLayout()); + + buttonGroup.add(allButton); + allButton.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(allButton, org.openide.util.NbBundle.getMessage(GeoFilterPanel.class, "GeoFilterPanel.allButton.text")); // NOI18N + allButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + allButtonActionPerformed(evt); + } + }); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.gridwidth = 4; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + waypointSettings.add(allButton, gridBagConstraints); + + buttonGroup.add(mostRecentButton); + org.openide.awt.Mnemonics.setLocalizedText(mostRecentButton, org.openide.util.NbBundle.getMessage(GeoFilterPanel.class, "GeoFilterPanel.mostRecentButton.text")); // NOI18N + mostRecentButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + mostRecentButtonActionPerformed(evt); + } + }); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + gridBagConstraints.gridwidth = 2; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(9, 0, 0, 0); + waypointSettings.add(mostRecentButton, gridBagConstraints); + + org.openide.awt.Mnemonics.setLocalizedText(showWaypointsWOTSCheckBox, org.openide.util.NbBundle.getMessage(GeoFilterPanel.class, "GeoFilterPanel.showWaypointsWOTSCheckBox.text")); // NOI18N + showWaypointsWOTSCheckBox.setEnabled(false); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 2; + gridBagConstraints.gridwidth = 3; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(0, 30, 0, 0); + waypointSettings.add(showWaypointsWOTSCheckBox, gridBagConstraints); + + daysSpinner.setEnabled(false); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 1; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(9, 0, 0, 0); + waypointSettings.add(daysSpinner, gridBagConstraints); + + org.openide.awt.Mnemonics.setLocalizedText(daysLabel, org.openide.util.NbBundle.getMessage(GeoFilterPanel.class, "GeoFilterPanel.daysLabel.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 3; + gridBagConstraints.gridy = 1; + gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new java.awt.Insets(9, 5, 0, 0); + waypointSettings.add(daysLabel, gridBagConstraints); + + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 2; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new java.awt.Insets(5, 15, 9, 15); + add(waypointSettings, gridBagConstraints); + + buttonPanel.setLayout(new java.awt.GridBagLayout()); + + applyButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/geolocation/images/tick.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(applyButton, org.openide.util.NbBundle.getMessage(GeoFilterPanel.class, "GeoFilterPanel.applyButton.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST; + gridBagConstraints.weightx = 1.0; + buttonPanel.add(applyButton, gridBagConstraints); + + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new java.awt.Insets(9, 15, 0, 15); + add(buttonPanel, gridBagConstraints); + + optionsLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/geolocation/images/blueGeo16.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(optionsLabel, org.openide.util.NbBundle.getMessage(GeoFilterPanel.class, "GeoFilterPanel.optionsLabel.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; + gridBagConstraints.insets = new java.awt.Insets(0, 15, 0, 0); + add(optionsLabel, gridBagConstraints); + }// //GEN-END:initComponents + + private void allButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_allButtonActionPerformed + updateWaypointOptions(); + }//GEN-LAST:event_allButtonActionPerformed + + private void mostRecentButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_mostRecentButtonActionPerformed + updateWaypointOptions(); + }//GEN-LAST:event_mostRecentButtonActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JRadioButton allButton; + private javax.swing.JButton applyButton; + private javax.swing.ButtonGroup buttonGroup; + private javax.swing.JPanel buttonPanel; + private javax.swing.JSpinner daysSpinner; + private javax.swing.JRadioButton mostRecentButton; + private javax.swing.JCheckBox showWaypointsWOTSCheckBox; + private javax.swing.JPanel waypointSettings; + // End of variables declaration//GEN-END:variables + + /** + * Class to store the values of the Geolocation user set filter parameters + */ + final class GeoFilter { + + private final boolean showAll; + private final boolean showWithoutTimeStamp; + private final int mostRecentNumDays; + private final List dataSources; + + /** + * Construct a Geolocation filter. showAll and mostRecentNumDays are + * exclusive filters, ie they cannot be used together. + * + * withoutTimeStamp is only applicable if mostRecentNumDays is true. + * + * When using the filters "most recent days" means to include waypoints + * for the numbers of days after the most recent waypoint, not the + * current date. + * + * @param showAll True if all waypoints should be shown + * @param withoutTimeStamp True to show waypoints without timeStamps, + * this filter is only applicable if + * mostRecentNumDays is true + * @param mostRecentNumDays Show Waypoint for the most recent given + * number of days. This parameter is ignored if + * showAll is true. + * @param dataSources A list of dataSources to filter waypoint + * for. + */ + GeoFilter(boolean showAll, boolean withoutTimeStamp, int mostRecentNumDays, List dataSources) { + this.showAll = showAll; + this.showWithoutTimeStamp = withoutTimeStamp; + this.mostRecentNumDays = mostRecentNumDays; + this.dataSources = dataSources; + } + + /** + * Returns whether or not to show all waypoints. + * + * @return True if all waypoints should be shown. + */ + boolean showAll() { + return showAll; + } + + /** + * Returns whether or not to include waypoints with time stamps. + * + * This filter is only applicable if "showAll" is true. + * + * @return True if waypoints with time stamps should be shown. + */ + boolean showWithoutTimeStamp() { + return showWithoutTimeStamp; + } + + /** + * Returns the number of most recent days to show waypoints for. This + * value should be ignored if showAll is true. + * + * @return The number of most recent days to show waypoints for + */ + int getMostRecentNumDays() { + return mostRecentNumDays; + } + + /** + * Returns a list of data sources to filter the waypoints by, or null if + * all datasources should be include. + * + * @return A list of dataSources or null if all dataSources should be + * included. + */ + List getDataSources() { + return Collections.unmodifiableList(dataSources); + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeoLocationUIException.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeoLocationUIException.java new file mode 100755 index 0000000000..7ce6837914 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeoLocationUIException.java @@ -0,0 +1,46 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 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; + +/** + * + * An exception call for Exceptions that occure in the geolocation dialog. + */ +public class GeoLocationUIException extends Exception{ + private static final long serialVersionUID = 1L; + + /** + * Create exception containing the error message + * + * @param msg the message + */ + public GeoLocationUIException(String msg) { + super(msg); + } + + /** + * Create exception containing the error message and cause exception + * + * @param msg the message + * @param ex cause exception + */ + public GeoLocationUIException(String msg, Exception ex) { + super(msg, ex); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.form b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.form index bddebcbc0c..16cd5368a6 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.form +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.form @@ -11,6 +11,7 @@ + @@ -23,6 +24,17 @@ + + + + + + + + + + + - + \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java index bf54ece196..f29bfca802 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.logging.Level; +import javax.swing.SwingUtilities; import javax.swing.SwingWorker; import org.openide.util.NbBundle.Messages; import org.openide.windows.RetainLocation; @@ -37,6 +38,11 @@ import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.ThreadConfined; +import org.sleuthkit.autopsy.geolocation.GeoFilterPanel.GeoFilter; +import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException; +import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint; +import org.sleuthkit.autopsy.geolocation.datamodel.WaypointBuilder; +import org.sleuthkit.autopsy.geolocation.datamodel.WaypointBuilder.WaypointFilterQueryCallBack; import org.sleuthkit.autopsy.ingest.IngestManager; import static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.DATA_ADDED; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; @@ -59,6 +65,7 @@ public final class GeolocationTopComponent extends TopComponent { private static final Set INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(DATA_ADDED); private final PropertyChangeListener ingestListener; + private final GeoFilterPanel geoFilterPanel; final RefreshPanel refreshPanel = new RefreshPanel(); @@ -109,6 +116,16 @@ public final class GeolocationTopComponent extends TopComponent { showRefreshPanel(false); } }); + + geoFilterPanel = new GeoFilterPanel(); + filterPane.setPanel(geoFilterPanel); + geoFilterPanel.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + filterWaypoints(); + } + }); + } @Override @@ -151,14 +168,12 @@ public final class GeolocationTopComponent extends TopComponent { /** * Use a SwingWorker thread to get a list of waypoints. - * */ private void initWaypoints() { SwingWorker, MapWaypoint> worker = new SwingWorker, MapWaypoint>() { @Override protected List doInBackground() throws Exception { Case currentCase = Case.getCurrentCaseThrows(); - return MapWaypoint.getWaypoints(currentCase.getSleuthkitCase()); } @@ -189,6 +204,59 @@ public final class GeolocationTopComponent extends TopComponent { worker.execute(); } + /** + * Filters the list of waypoints based on the user selections in the filter + * pane. + */ + @Messages({ + "GeoTopComponent_no_waypoints_returned_mgs=Applied filter failed to find waypoints that matched criteria.\nRevise filter options and try again.", + "GeoTopComponent_no_waypoints_returned_Title=No Waypoints Found", + "GeoTopComponent_filter_exception_msg=Exception occured during waypoint filtering.", + "GeoTopComponent_filter_exception_Title=Filter Failure", + "GeoTopComponent_filer_data_invalid_msg=Unable to run waypoint filter.\nPlease select one or more data sources.", + "GeoTopComponent_filer_data_invalid_Title=Filter Failure" + }) + private void filterWaypoints() { + GeoFilter filters; + + // Show a warning message if the user has not selected a data source + try { + filters = geoFilterPanel.getFilterState(); + } catch (GeoLocationUIException ex) { + MessageNotifyUtil.Notify.info( + Bundle.GeoTopComponent_filer_data_invalid_Title(), + Bundle.GeoTopComponent_filer_data_invalid_msg()); + return; + } + + SwingUtilities.invokeLater(new Runnable() { + public void run() { + Case currentCase = Case.getCurrentCase(); + try { + WaypointBuilder.getAllWaypoints(currentCase.getSleuthkitCase(), filters.getDataSources(), filters.showAll(), filters.getMostRecentNumDays(), filters.showWithoutTimeStamp(), new WaypointFilterQueryCallBack() { + @Override + public void process(List waypoints) { + // If the list is empty, tell the user and do not change + // the visible waypoints. + if (waypoints == null || waypoints.isEmpty()) { + MessageNotifyUtil.Notify.info( + Bundle.GeoTopComponent_no_waypoints_returned_Title(), + Bundle.GeoTopComponent_no_waypoints_returned()); + return; + } + mapPanel.setWaypoints(MapWaypoint.getWaypoints(waypoints)); + } + }); + } catch (GeoLocationDataException ex) { + logger.log(Level.SEVERE, "Failed to filter waypoints.", ex); + MessageNotifyUtil.Notify.error( + Bundle.GeoTopComponent_filter_exception_Title(), + Bundle.GeoTopComponent_filter_exception_msg()); + } + } + }); + } + /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always @@ -199,13 +267,18 @@ public final class GeolocationTopComponent extends TopComponent { private void initComponents() { mapPanel = new org.sleuthkit.autopsy.geolocation.MapPanel(); + filterPane = new org.sleuthkit.autopsy.geolocation.HidingPane(); setLayout(new java.awt.BorderLayout()); + + mapPanel.add(filterPane, java.awt.BorderLayout.LINE_START); + add(mapPanel, java.awt.BorderLayout.CENTER); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables + private org.sleuthkit.autopsy.geolocation.HidingPane filterPane; private org.sleuthkit.autopsy.geolocation.MapPanel mapPanel; // End of variables declaration//GEN-END:variables } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/HidingPane.java b/Core/src/org/sleuthkit/autopsy/geolocation/HidingPane.java new file mode 100755 index 0000000000..492888d15f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/HidingPane.java @@ -0,0 +1,120 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.geolocation; + +import java.awt.BorderLayout; +import java.awt.Font; +import java.awt.Point; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import javax.swing.Icon; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTabbedPane; +import org.openide.util.NbBundle.Messages; + +/** + * + * A JTabbed pane with one tab that says "Filters". When the user clicks on that + * table the content of the tab will be hidden. + * + * The content pane provides support for scrolling. + */ +public final class HidingPane extends JTabbedPane { + + private static final long serialVersionUID = 1L; + + private final JScrollPane scrollPane; + private final JPanel panel; + private final JLabel tabLabel; + + private boolean panelVisible = true; + + /** + * Constructs a new HidingFilterPane + */ + @Messages({ + "HidingPane_default_title=Filters" + }) + public HidingPane() { + super(); + + scrollPane = new JScrollPane(); + panel = new JPanel(); + panel.setLayout(new BorderLayout()); + panel.add(scrollPane, BorderLayout.CENTER); + tabLabel = new JLabel(Bundle.HidingPane_default_title()); + tabLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/geolocation/images/funnel.png"))); + tabLabel.setUI(new VerticalLabelUI(true)); + tabLabel.setOpaque(false); + Font font = tabLabel.getFont().deriveFont(18).deriveFont(Font.BOLD); + tabLabel.setFont(font); + + addTab(null, panel); + setTabComponentAt(0, tabLabel); + + this.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent evt) { + handleMouseClick(evt.getPoint()); + } + }); + + this.setTabPlacement(JTabbedPane.RIGHT); + } + + /** + * Change the title of the tab. + * + * @param title + */ + void setTitle(String title) { + tabLabel.setText(title); + } + + /** + * Set the icon that appears on the tab. + * + * @param icon + */ + void setIcon(Icon icon) { + tabLabel.setIcon(icon); + } + + /** + * Set the content for this panel. + * + * @param panel A panel to display in the tabbed pane. + */ + void setPanel(JPanel panel) { + scrollPane.setViewportView(panel); + } + + /** + * Handle the mouse click. + * + * @param point + */ + private void handleMouseClick(Point point) { + int index = indexAtLocation(point.x, point.y); + + if(index == -1) { + return; + } + + if(panelVisible) { + panel.removeAll(); + panel.revalidate(); + panelVisible = false; + } else { + panel.add(scrollPane, BorderLayout.CENTER); + panel.revalidate(); + panelVisible = true; + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java index 326d33f23e..da4a704dcf 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java @@ -56,7 +56,7 @@ import org.sleuthkit.datamodel.TskCoreException; /** * The map panel. This panel contains the jxmapviewer MapViewer */ -final class MapPanel extends javax.swing.JPanel { +final public class MapPanel extends javax.swing.JPanel { private static final Logger logger = Logger.getLogger(MapPanel.class.getName()); @@ -76,7 +76,7 @@ final class MapPanel extends javax.swing.JPanel { /** * Creates new form MapPanel */ - MapPanel() { + public MapPanel() { initComponents(); initMap(); diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java index 7eb765151a..7a28f49737 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java @@ -49,6 +49,7 @@ import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException; import org.sleuthkit.autopsy.geolocation.datamodel.Route; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint; +import org.sleuthkit.autopsy.geolocation.datamodel.WaypointBuilder; import org.sleuthkit.autopsy.timeline.actions.ViewArtifactInTimelineAction; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -79,7 +80,7 @@ final class MapWaypoint extends KdTree.XYZPoint implements org.jxmapviewer.viewe * @throws GeoLocationDataException */ static List getWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { - List points = Waypoint.getAllWaypoints(skCase); + List points = WaypointBuilder.getAllWaypoints(skCase); List routes = Route.getRoutes(skCase); for (Route route : routes) { @@ -94,6 +95,28 @@ final class MapWaypoint extends KdTree.XYZPoint implements org.jxmapviewer.viewe return mapPoints; } + + /** + * Returns a list of of MapWaypoint objects for the given list of + * datamodel.Waypoint objects. + * + * @param dmWaypoints + * + * @return List of MapWaypoint objects. List will be empty if dmWaypoints was + * empty or null. + */ + static List getWaypoints(List dmWaypoints) { + List mapPoints = new ArrayList<>(); + + if (dmWaypoints != null) { + + for (Waypoint point : dmWaypoints) { + mapPoints.add(new MapWaypoint(point)); + } + } + + return mapPoints; + } /** * Returns a MapWaypoint without a reference to the datamodel waypoint. diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/VerticalLabelUI.java b/Core/src/org/sleuthkit/autopsy/geolocation/VerticalLabelUI.java new file mode 100755 index 0000000000..2580031dfb --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/VerticalLabelUI.java @@ -0,0 +1,110 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.geolocation; + +import java.awt.Dimension; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Insets; +import java.awt.Rectangle; +import java.awt.geom.AffineTransform; + +import javax.swing.Icon; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.plaf.basic.BasicLabelUI; + + +/** + * This class is an overload of BasicLabelUI to draw labels vertically. + * + * This code was found at: + * https://tech.chitgoks.com/2009/11/13/rotate-jlabel-vertically/ + * + */ +final class VerticalLabelUI extends BasicLabelUI { + + private static final Rectangle paintIconR = new Rectangle(); + private static final Rectangle paintTextR = new Rectangle(); + private static final Rectangle paintViewR = new Rectangle(); + private static Insets paintViewInsets = new Insets(0, 0, 0, 0); + + static { + labelUI = new VerticalLabelUI(false); + } + + final boolean clockwise; + + /** + * Construct a new VerticalLabelUI + * @param clockwise + */ + VerticalLabelUI(boolean clockwise) { + super(); + this.clockwise = clockwise; + } + + @Override + public Dimension getPreferredSize(JComponent c) { + Dimension dim = super.getPreferredSize(c); + return new Dimension( dim.height, dim.width ); + } + + @Override + public void paint(Graphics g, JComponent c) { + JLabel label = (JLabel)c; + String text = label.getText(); + Icon icon = (label.isEnabled()) ? label.getIcon() : label.getDisabledIcon(); + + if ((icon == null) && (text == null)) { + return; + } + + FontMetrics fm = g.getFontMetrics(); + paintViewInsets = c.getInsets(paintViewInsets); + + paintViewR.x = paintViewInsets.left; + paintViewR.y = paintViewInsets.top; + + // Use inverted height & width + paintViewR.height = c.getWidth() - (paintViewInsets.left + paintViewInsets.right); + paintViewR.width = c.getHeight() - (paintViewInsets.top + paintViewInsets.bottom); + + paintIconR.x = paintIconR.y = paintIconR.width = paintIconR.height = 0; + paintTextR.x = paintTextR.y = paintTextR.width = paintTextR.height = 0; + + String clippedText = layoutCL(label, fm, text, icon, paintViewR, paintIconR, paintTextR); + + Graphics2D g2 = (Graphics2D) g; + AffineTransform tr = g2.getTransform(); + if (clockwise) { + g2.rotate( Math.PI / 2 ); + g2.translate( 0, - c.getWidth() ); + } else { + g2.rotate( - Math.PI / 2 ); + g2.translate( - c.getHeight(), 0 ); + } + + if (icon != null) { + icon.paintIcon(c, g, paintIconR.x, paintIconR.y); + } + + if (text != null) { + int textX = paintTextR.x; + int textY = paintTextR.y + fm.getAscent(); + + if (label.isEnabled()) { + paintEnabledText(label, g, clippedText, textX, textY); + } else { + paintDisabledText(label, g, clippedText, textX, textY); + } + } + g2.setTransform( tr ); + } + +} + diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java index bd5b39b6b7..428c659eca 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java @@ -262,186 +262,6 @@ public class Waypoint { return attributeMap; } - - /** - * Returns a list of Waypoints for the artifacts with geolocation - * information. - * - * List will include artifacts of type: TSK_GPS_TRACKPOINT TSK_GPS_SEARCH - * TSK_GPS_LAST_KNOWN_LOCATION TSK_GPS_BOOKMARK TSK_METADATA_EXIF - * - * @param skCase Currently open SleuthkitCase - * - * @return List of Waypoint - * - * @throws GeoLocationDataException - */ - public static List getAllWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { - List points = new ArrayList<>(); - - points.addAll(getTrackpointWaypoints(skCase)); - points.addAll(getEXIFWaypoints(skCase)); - points.addAll(getSearchWaypoints(skCase)); - points.addAll(getLastKnownWaypoints(skCase)); - points.addAll(getBookmarkWaypoints(skCase)); - - return points; - } - - /** - * Gets a list of Waypoints for TSK_GPS_TRACKPOINT artifacts. - * - * @param skCase Currently open SleuthkitCase - * - * @return List of Waypoint - * - * @throws GeoLocationDataException - */ - public static List getTrackpointWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { - List artifacts = null; - try { - artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT); - } catch (TskCoreException ex) { - throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_TRACKPOINT", ex); - } - - List points = new ArrayList<>(); - for (BlackboardArtifact artifact : artifacts) { - try { - Waypoint point = new TrackpointWaypoint(artifact); - points.add(point); - } catch (GeoLocationDataException ex) { - logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_TRACKPOINT artifactID: %d", artifact.getArtifactID())); - } - } - return points; - } - - /** - * Gets a list of Waypoints for TSK_METADATA_EXIF artifacts. - * - * @param skCase Currently open SleuthkitCase - * - * @return List of Waypoint - * - * @throws GeoLocationDataException - */ - static public List getEXIFWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { - List artifacts = null; - try { - artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF); - } catch (TskCoreException ex) { - throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_LAST_KNOWN_LOCATION", ex); - } - - List points = new ArrayList<>(); - if (artifacts != null) { - for (BlackboardArtifact artifact : artifacts) { - try { - Waypoint point = new EXIFWaypoint(artifact); - points.add(point); - } catch (GeoLocationDataException ex) { - // I am a little relucant to log this error because I suspect - // this will happen more often than not. It is valid for - // METADAT_EXIF to not have longitude and latitude - } - } - } - return points; - } - - /** - * Gets a list of Waypoints for TSK_GPS_SEARCH artifacts. - * - * @param skCase Currently open SleuthkitCase - * - * @return List of Waypoint - * - * @throws GeoLocationDataException - */ - public static List getSearchWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { - List artifacts = null; - try { - artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH); - } catch (TskCoreException ex) { - throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_SEARCH", ex); - } - - List points = new ArrayList<>(); - if (artifacts != null) { - for (BlackboardArtifact artifact : artifacts) { - try { - Waypoint point = new SearchWaypoint(artifact); - points.add(point); - } catch (GeoLocationDataException ex) { - logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_SEARCH artifactID: %d", artifact.getArtifactID())); - } - } - } - return points; - } - - /** - * Gets a list of Waypoints for TSK_GPS_LAST_KNOWN_LOCATION artifacts. - * - * @param skCase Currently open SleuthkitCase - * - * @return List of Waypoint - * - * @throws GeoLocationDataException - */ - public static List getLastKnownWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { - List artifacts = null; - try { - artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION); - } catch (TskCoreException ex) { - throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_LAST_KNOWN_LOCATION", ex); - } - - List points = new ArrayList<>(); - if (artifacts != null) { - for (BlackboardArtifact artifact : artifacts) { - try { - Waypoint point = new LastKnownWaypoint(artifact); - points.add(point); - } catch (GeoLocationDataException ex) { - logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_LAST_KNOWN_LOCATION artifactID: %d", artifact.getArtifactID())); - } - } - } - return points; - } - - /** - * Gets a list of Waypoints for TSK_GPS_BOOKMARK artifacts. - * - * @param skCase Currently open SleuthkitCase - * - * @return List of Waypoint - * - * @throws GeoLocationDataException - */ - public static List getBookmarkWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { - List artifacts = null; - try { - artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK); - } catch (TskCoreException ex) { - throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_BOOKMARK", ex); - } - - List points = new ArrayList<>(); - if (artifacts != null) { - for (BlackboardArtifact artifact : artifacts) { - try { - Waypoint point = new Waypoint(artifact); - points.add(point); - } catch (GeoLocationDataException ex) { - logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_BOOKMARK artifactID: %d", artifact.getArtifactID())); - } - } - } - return points; - } /** * Get a list of Waypoint.Property objects for the given artifact. This list diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java new file mode 100755 index 0000000000..0776d029fd --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java @@ -0,0 +1,484 @@ +/* + * + * Autopsy Forensic Browser + * + * Copyright 2019 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.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.CaseDbAccessManager; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.DataSource; + +/** + * Class for building lists of waypoints. + * + */ +public final class WaypointBuilder { + + private static final Logger logger = Logger.getLogger(WaypointBuilder.class.getName()); + + // SELECT statement for getting a list of waypoints. Replace the %s after + // after the SELECT with the list of parameters to return + final static String GEO_ARTIFACT_QUERY + = "SELECT artifact_id, artifact_type_id " + + "FROM blackboard_attributes " + + "WHERE attribute_type_id IN (%d, %d) "; //NON-NLS + + // This Query will return a list of waypoint artifacts + final static String GEO_ARTIFACT_WITH_DATA_SOURCES_QUERY + = "SELECT blackboard_attributes.artifact_id " + + "FROM blackboard_attributes " + + "JOIN blackboard_artifacts ON blackboard_attributes.artifact_id = blackboard_artifacts.artifact_id " + + "WHERE blackboard_attributes.attribute_type_id IN(%d, %d) " + + "AND data_source_obj_id IN (%s)"; //NON-NLS + + final static String MOST_RECENT_TIME + = "SELECT MAX(value_int64) - (%d * 86400)" //86400 is the number of seconds in a day. + + "FROM blackboard_attributes " + + "WHERE attribute_type_id IN(%d, %d) " + + "AND artifact_id " + + "IN ( " + + "%s" //GEO_ARTIFACT with or without data source + + " )"; + + final static String SELECT_WO_TIMESTAMP = + "SELECT DISTINCT artifact_id, artifact_type_id " + + "FROM blackboard_attributes " + + "WHERE artifact_id NOT IN (%s) " + + "AND artifact_id IN (%s)"; //NON-NLS + + /** + * A callback interface to process the results of waypoint filtering. + */ + public interface WaypointFilterQueryCallBack { + + /** + * This function will be called after the waypoints have been filtered. + * + * @param wwaypoints This of waypoints. + */ + void process(List wwaypoints); + } + + /** + * private constructor + */ + private WaypointBuilder() { + + } + + /** + * Returns a list of Waypoints for the artifacts with geolocation + * information. + * + * List will include artifacts of type: TSK_GPS_TRACKPOINT TSK_GPS_SEARCH + * TSK_GPS_LAST_KNOWN_LOCATION TSK_GPS_BOOKMARK TSK_METADATA_EXIF + * + * @param skCase Currently open SleuthkitCase + * + * @return List of Waypoint + * + * @throws GeoLocationDataException + */ + public static List getAllWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { + List points = new ArrayList<>(); + + points.addAll(getTrackpointWaypoints(skCase)); + points.addAll(getEXIFWaypoints(skCase)); + points.addAll(getSearchWaypoints(skCase)); + points.addAll(getLastKnownWaypoints(skCase)); + points.addAll(getBookmarkWaypoints(skCase)); + + return points; + } + + /** + * Gets a list of Waypoints for TSK_GPS_TRACKPOINT artifacts. + * + * @param skCase Currently open SleuthkitCase + * + * @return List of Waypoint + * + * @throws GeoLocationDataException + */ + public static List getTrackpointWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { + List artifacts = null; + try { + artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT); + } catch (TskCoreException ex) { + throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_TRACKPOINT", ex);//NON-NLS + } + + List points = new ArrayList<>(); + for (BlackboardArtifact artifact : artifacts) { + try { + Waypoint point = new TrackpointWaypoint(artifact); + points.add(point); + } catch (GeoLocationDataException ex) { + logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_TRACKPOINT artifactID: %d", artifact.getArtifactID()));//NON-NLS + } + } + return points; + } + + /** + * Gets a list of Waypoints for TSK_METADATA_EXIF artifacts. + * + * @param skCase Currently open SleuthkitCase + * + * @return List of Waypoint + * + * @throws GeoLocationDataException + */ + static public List getEXIFWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { + List artifacts = null; + try { + artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF); + } catch (TskCoreException ex) { + throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_LAST_KNOWN_LOCATION", ex);//NON-NLS + } + + List points = new ArrayList<>(); + if (artifacts != null) { + for (BlackboardArtifact artifact : artifacts) { + try { + Waypoint point = new EXIFWaypoint(artifact); + points.add(point); + } catch (GeoLocationDataException ex) { + // I am a little relucant to log this error because I suspect + // this will happen more often than not. It is valid for + // METADAT_EXIF to not have longitude and latitude + } + } + } + return points; + } + + /** + * Gets a list of Waypoints for TSK_GPS_SEARCH artifacts. + * + * @param skCase Currently open SleuthkitCase + * + * @return List of Waypoint + * + * @throws GeoLocationDataException + */ + public static List getSearchWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { + List artifacts = null; + try { + artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH); + } catch (TskCoreException ex) { + throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_SEARCH", ex);//NON-NLS + } + + List points = new ArrayList<>(); + if (artifacts != null) { + for (BlackboardArtifact artifact : artifacts) { + try { + Waypoint point = new SearchWaypoint(artifact); + points.add(point); + } catch (GeoLocationDataException ex) { + logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_SEARCH artifactID: %d", artifact.getArtifactID()));//NON-NLS + } + } + } + return points; + } + + /** + * Gets a list of Waypoints for TSK_GPS_LAST_KNOWN_LOCATION artifacts. + * + * @param skCase Currently open SleuthkitCase + * + * @return List of Waypoint + * + * @throws GeoLocationDataException + */ + public static List getLastKnownWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { + List artifacts = null; + try { + artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION); + } catch (TskCoreException ex) { + throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_LAST_KNOWN_LOCATION", ex);//NON-NLS + } + + List points = new ArrayList<>(); + if (artifacts != null) { + for (BlackboardArtifact artifact : artifacts) { + try { + Waypoint point = new LastKnownWaypoint(artifact); + points.add(point); + } catch (GeoLocationDataException ex) { + logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_LAST_KNOWN_LOCATION artifactID: %d", artifact.getArtifactID()));//NON-NLS + } + } + } + return points; + } + + /** + * Gets a list of Waypoints for TSK_GPS_BOOKMARK artifacts. + * + * @param skCase Currently open SleuthkitCase + * + * @return List of Waypoint + * + * @throws GeoLocationDataException + */ + public static List getBookmarkWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { + List artifacts = null; + try { + artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK); + } catch (TskCoreException ex) { + throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_BOOKMARK", ex);//NON-NLS + } + + List points = new ArrayList<>(); + if (artifacts != null) { + for (BlackboardArtifact artifact : artifacts) { + try { + Waypoint point = new Waypoint(artifact); + points.add(point); + } catch (GeoLocationDataException ex) { + logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_BOOKMARK artifactID: %d", artifact.getArtifactID()), ex);//NON-NLS + } + } + } + return points; + } + + /** + * Get a filtered list of waypoints. + * + * If showAll is true, the values of cntDaysFromRecent and notTimeStamp will + * be ignored. + * + * To include data from all dataSources pass a null or empty dataSource + * list. + * + * + * @param skCase Currently open sleuthkit case. + * @param dataSources This of data sources to filter the waypoints by. + * Pass a null or empty list to show way points for + * all dataSources. + * + * @param showAll True to get all waypoints. + * + * @param cntDaysFromRecent Number of days from the most recent time stamp + * to get waypoints for. This parameter will be + * ignored if showAll is true; + * + * @param noTimeStamp True to include waypoints without timestamp. + * This parameter will be ignored if showAll is + * true. + * + * @param queryCallBack Function to call after the DB query has + * completed. + * + * @throws GeoLocationDataException + */ + static public void getAllWaypoints(SleuthkitCase skCase, List dataSources, boolean showAll, int cntDaysFromRecent, boolean noTimeStamp, WaypointFilterQueryCallBack queryCallBack) throws GeoLocationDataException { + String query = buildQuery(dataSources, showAll, cntDaysFromRecent, noTimeStamp); + + logger.log(Level.INFO, query); + + try { + // The CaseDBAccessManager.select function will add a SELECT + // to the beginning of the query + if (query.startsWith("SELECT")) { //NON-NLS + query = query.replaceFirst("SELECT", ""); //NON-NLS + } + + skCase.getCaseDbAccessManager().select(query, new CaseDbAccessManager.CaseDbAccessQueryCallback() { + @Override + public void process(ResultSet rs) { + List waypoints = new ArrayList<>(); + try { + while (rs.next()) { + int artifact_type_id = rs.getInt("artifact_type_id"); //NON-NLS + long artifact_id = rs.getLong("artifact_id"); //NON-NLS + + BlackboardArtifact.ARTIFACT_TYPE type = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact_type_id); + + waypoints.addAll(getWaypointForArtifact(skCase.getBlackboardArtifact(artifact_id), type)); + + } + queryCallBack.process(waypoints); + } catch (GeoLocationDataException | SQLException | TskCoreException ex) { + logger.log(Level.WARNING, "Failed to filter waypoint.", ex); //NON-NLS + } + + } + }); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Failed to filter waypoint.", ex); //NON-NLS + } + } + + /** + * Create the query for getting a list of waypoints that do not have time + * stamps. + * + * @param dataSources List of data Sources to filter by + * + * @return SQL SELECT statement + */ + static private String buildQueryForWaypointsWOTimeStamps(List dataSources) { + String query = ""; + + query = String.format(SELECT_WO_TIMESTAMP, + String.format(GEO_ARTIFACT_QUERY, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID(), + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED.getTypeID()), + getWaypointListQuery(dataSources)); + + return query; + } + + /** + * Build the query to filter the list of waypoints. + * + * If showAll is true, the values of cntDaysFromRecent and noTimeStamp are + * ignored. + * + * @param dataSources This of data sources to filter the waypoints by. + * Pass a null or empty list to show way points for + * all dataSources. + * + * @param showAll True to get all waypoints. + * + * @param cntDaysFromRecent Number of days from the most recent time stamp + * to get waypoints for. This parameter will be + * ignored if showAll is true; + * + * @param noTimeStamp True to include waypoints without timestamp. + * This parameter will be ignored if showAll is + * true. + * + * @return + */ + static private String buildQuery(List dataSources, boolean showAll, int cntDaysFromRecent, boolean noTimeStamp) { + String mostRecentQuery = ""; + + if (!showAll && cntDaysFromRecent > 0) { + mostRecentQuery = String.format("AND value_int64 > (%s)", //NON-NLS + String.format(MOST_RECENT_TIME, + cntDaysFromRecent, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID(), + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED.getTypeID(), + getWaypointListQuery(dataSources) + )); + } + + // This givens us all artifact_ID that have time stamp + String query = String.format(GEO_ARTIFACT_QUERY, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID(), + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED.getTypeID()); + + // That are in the list of artifacts for the given data Sources + query += String.format("AND artifact_id IN(%s)", getWaypointListQuery(dataSources)); //NON-NLS + query += mostRecentQuery; + + if (noTimeStamp) { + query = String.format("%s UNION %s", buildQueryForWaypointsWOTimeStamps(dataSources), query); //NON-NLS + } + + return query; + } + + /** + * Returns the query to get a list of waypoints filted by the given data + * sources. + * + * An artifact is assumed to be a "waypoint" if it has the attributes + * TSK_GEO_LATITUDE or TSK_GEO_LATITUDE_START + * + * @param dataSources A list of data sources to filter by. If the list is + * null or empty the data source list will be ignored. + * + * @return + */ + static private String getWaypointListQuery(List dataSources) { + + if (dataSources == null || dataSources.isEmpty()) { + return String.format(GEO_ARTIFACT_QUERY, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID(), + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START.getTypeID()); + } + + String dataSourceList = ""; + for (DataSource source : dataSources) { + dataSourceList += Long.toString(source.getId()) + ","; + } + + if (!dataSourceList.isEmpty()) { + // Remove the last , + dataSourceList = dataSourceList.substring(0, dataSourceList.length() - 1); + } + + return String.format(GEO_ARTIFACT_WITH_DATA_SOURCES_QUERY, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID(), + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START.getTypeID(), + dataSourceList); + } + + /** + * Create a Waypoint object for the given Blackboard artifact. + * + * @param artifact The artifact to create the waypoint from + * @param type The type of artifact + * + * @return A new waypoint object + * + * @throws GeoLocationDataException + */ + static private List getWaypointForArtifact(BlackboardArtifact artifact, BlackboardArtifact.ARTIFACT_TYPE type) throws GeoLocationDataException { + List waypoints = new ArrayList<>(); + switch (type) { + case TSK_METADATA_EXIF: + waypoints.add(new EXIFWaypoint(artifact)); + break; + case TSK_GPS_BOOKMARK: + waypoints.add(new Waypoint(artifact)); + break; + case TSK_GPS_TRACKPOINT: + waypoints.add(new TrackpointWaypoint(artifact)); + break; + case TSK_GPS_SEARCH: + waypoints.add(new SearchWaypoint(artifact)); + break; + case TSK_GPS_ROUTE: + Route route = new Route(artifact); + waypoints.addAll(route.getRoute()); + break; + default: + waypoints.add(new Waypoint(artifact)); + break; + } + + return waypoints; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/images/blueGeo16.png b/Core/src/org/sleuthkit/autopsy/geolocation/images/blueGeo16.png new file mode 100755 index 0000000000000000000000000000000000000000..b89f013c410be1b7ba70c62153c598846ab759c9 GIT binary patch literal 741 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 literal 0 HcmV?d00001 diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/images/tick.png b/Core/src/org/sleuthkit/autopsy/geolocation/images/tick.png new file mode 100755 index 0000000000000000000000000000000000000000..a7d7a96be3f2282a62e3c0733bac89c7f6de7b4a GIT binary patch literal 582 zcmV-M0=fN(P)tYd4K$mX5uyr2F@fdffp{&DSHtl4|Bn9=ukpCx`#%PTUr-D(@b7;C zhJXJjrnw{;1KBM=6&|E`fsNrGL!Y6%zUh}QUl`(@V)PmQFtotEKmafTHP0uP}7&%m4pcKQ#X)BiAJ2y+PrtBI>9eEIt2-_c7)?*Lsf z5vX5*Af@m-wBJRV%#Fiz=BdPr0!2^az8K(fJ0K@xl?-_o)`1bna-SH57SeZUButL)d$cI_) zkpl5Q!w#U+YtHUCagF&eebP3jhEB literal 0 HcmV?d00001 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 eabdeef98b..a140338524 100644 --- a/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java @@ -47,6 +47,7 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException; import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint; import org.sleuthkit.autopsy.geolocation.datamodel.Route; +import org.sleuthkit.autopsy.geolocation.datamodel.WaypointBuilder; import org.sleuthkit.autopsy.report.ReportBranding; import org.sleuthkit.autopsy.report.ReportProgressPanel; import org.sleuthkit.datamodel.AbstractFile; @@ -331,11 +332,11 @@ class KMLReport implements GeneralReportModule { * @throws IOException */ void addLocationsToReport(SleuthkitCase skCase, String baseReportDir) throws GeoLocationDataException, IOException { - addExifMetadataContent(Waypoint.getEXIFWaypoints(skCase), baseReportDir); - addWaypoints(Waypoint.getBookmarkWaypoints(skCase), gpsBookmarksFolder, FeatureColor.BLUE, Bundle.Waypoint_Bookmark_Display_String()); - addWaypoints(Waypoint.getLastKnownWaypoints(skCase), gpsLastKnownLocationFolder, FeatureColor.PURPLE, Bundle.Waypoint_Last_Known_Display_String()); - addWaypoints(Waypoint.getSearchWaypoints(skCase), gpsSearchesFolder, FeatureColor.WHITE, Bundle.Waypoint_Search_Display_String()); - addWaypoints(Waypoint.getTrackpointWaypoints(skCase), gpsTrackpointsFolder, FeatureColor.WHITE, Bundle.Waypoint_Trackpoint_Display_String()); + addExifMetadataContent(WaypointBuilder.getEXIFWaypoints(skCase), baseReportDir); + addWaypoints(WaypointBuilder.getBookmarkWaypoints(skCase), gpsBookmarksFolder, FeatureColor.BLUE, Bundle.Waypoint_Bookmark_Display_String()); + addWaypoints(WaypointBuilder.getLastKnownWaypoints(skCase), gpsLastKnownLocationFolder, FeatureColor.PURPLE, Bundle.Waypoint_Last_Known_Display_String()); + addWaypoints(WaypointBuilder.getSearchWaypoints(skCase), gpsSearchesFolder, FeatureColor.WHITE, Bundle.Waypoint_Search_Display_String()); + addWaypoints(WaypointBuilder.getTrackpointWaypoints(skCase), gpsTrackpointsFolder, FeatureColor.WHITE, Bundle.Waypoint_Trackpoint_Display_String()); } /**