From 76433fea7af9522f1515441fa201736afbec383c Mon Sep 17 00:00:00 2001 From: Douglas Gillespie <50671166+douggillespie@users.noreply.github.com> Date: Thu, 28 Mar 2024 14:47:56 +0000 Subject: [PATCH 1/3] Click offline linking Corrupted database not linking offline clicks back to binary data so added some new search features which match on file and click number if other links on UID fail. --- dependency-reduced-pom.xml | 2 +- pom.xml | 2 +- src/PamController/PamController.java | 2 +- src/PamController/PamguardVersionInfo.java | 6 +-- src/PamguardMVC/PamDataBlock.java | 2 +- .../superdet/SubdetectionInfo.java | 36 ++++++++++++++++ .../superdet/SuperDetDataBlock.java | 43 ++++++++++++++++++- src/generalDatabase/PamSubtableData.java | 18 ++++++++ src/generalDatabase/SQLLogging.java | 6 ++- 9 files changed, 108 insertions(+), 9 deletions(-) diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml index 73fb08dd..217f240b 100644 --- a/dependency-reduced-pom.xml +++ b/dependency-reduced-pom.xml @@ -4,7 +4,7 @@ org.pamguard Pamguard Pamguard Java12+ - 2.02.10b + 2.02.10ae Pamguard for Java 12+, using Maven to control dependcies www.pamguard.org diff --git a/pom.xml b/pom.xml index bbb2fbe6..8051e75d 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.pamguard Pamguard - 2.02.10ad + 2.02.10ae Pamguard Java12+ Pamguard for Java 12+, using Maven to control dependcies www.pamguard.org diff --git a/src/PamController/PamController.java b/src/PamController/PamController.java index 447c876e..54f7735f 100644 --- a/src/PamController/PamController.java +++ b/src/PamController/PamController.java @@ -196,7 +196,7 @@ public class PamController implements PamControllerInterface, PamSettings { private Timer diagnosticTimer; - private boolean debugDumpBufferAtRestart = true; + private boolean debugDumpBufferAtRestart = false; private NetworkController networkController; private int nNetPrepared; diff --git a/src/PamController/PamguardVersionInfo.java b/src/PamController/PamguardVersionInfo.java index 12814aa5..f4710929 100644 --- a/src/PamController/PamguardVersionInfo.java +++ b/src/PamController/PamguardVersionInfo.java @@ -16,7 +16,7 @@ public class PamguardVersionInfo { * @return release type */ static public ReleaseType getReleaseType() { - return ReleaseType.OTHER; + return ReleaseType.BETA; } /** @@ -31,12 +31,12 @@ public class PamguardVersionInfo { * Version number, major version.minorversion.sub-release. * Note: can't go higher than sub-release 'f' */ - static public final String version = "2.02.10ad"; + static public final String version = "2.02.10ae"; /** * Release date */ - static public final String date = "13 March 2024"; + static public final String date = "15 March 2024"; // /** // * Release type - Beta or Core diff --git a/src/PamguardMVC/PamDataBlock.java b/src/PamguardMVC/PamDataBlock.java index ddc42abe..ba5d6a29 100644 --- a/src/PamguardMVC/PamDataBlock.java +++ b/src/PamguardMVC/PamDataBlock.java @@ -636,7 +636,7 @@ public class PamDataBlock extends PamObservable { /** * Find a dataunit based on it's database index. If there have been no updates, - * then database indexes should be in order and a fast find canbe used. If + * then database indexes should be in order and a fast find can be used. If * however, there have been updates, then things will not be in order so it's * necessary to go through everything from start to end. * diff --git a/src/PamguardMVC/superdet/SubdetectionInfo.java b/src/PamguardMVC/superdet/SubdetectionInfo.java index 228f9ccc..396dea7d 100644 --- a/src/PamguardMVC/superdet/SubdetectionInfo.java +++ b/src/PamguardMVC/superdet/SubdetectionInfo.java @@ -63,6 +63,13 @@ public class SubdetectionInfo implements Comparable implements Comparable implements Comparable1 in a millisecond. + */ + Integer clickNo = subTableData.getClickNumber(); + if (clickNo == null) { + return 0; + } + else { + subTableData.setChildUID(dataUnit.getUID()); + dataUnit.updateDataUnit(System.currentTimeMillis()); + return (int) (clickNo - duFileInfo.getIndexInFile()); + } + } } diff --git a/src/generalDatabase/PamSubtableData.java b/src/generalDatabase/PamSubtableData.java index 35a8dc27..a05a6c1e 100644 --- a/src/generalDatabase/PamSubtableData.java +++ b/src/generalDatabase/PamSubtableData.java @@ -81,6 +81,11 @@ public class PamSubtableData { * subdetection table, and not the index from the Click_Detector_Clicks table) */ private long dbIndex; + + /** + * Click number. only exists for clicks table, but needed for corrupt database. + */ + private Integer clickNumber; /** * @@ -158,5 +163,18 @@ public class PamSubtableData { this.childUTC = childUTC; } + /** + * @return the clickNumber + */ + public Integer getClickNumber() { + return clickNumber; + } + + /** + * @param clickNumber the clickNumber to set + */ + public void setClickNumber(Integer clickNumber) { + this.clickNumber = clickNumber; + } } diff --git a/src/generalDatabase/SQLLogging.java b/src/generalDatabase/SQLLogging.java index 905ee28f..087a039f 100644 --- a/src/generalDatabase/SQLLogging.java +++ b/src/generalDatabase/SQLLogging.java @@ -2077,8 +2077,9 @@ public abstract class SQLLogging { ArrayList tableList = new ArrayList(); int n = 0; try { + PamSubtableDefinition subtableTableDef = (PamSubtableDefinition) getTableDefinition(); + PamTableItem clickNoItem = subtableTableDef.findTableItem("ClickNo"); while (subtableResults.next()) { - PamSubtableDefinition subtableTableDef = (PamSubtableDefinition) getTableDefinition(); PamTableItem tableItem; // transferDataFromResult(con.getSqlTypes(), subtableResults); for (int i = 0; i < subtableTableDef.getTableItemCount(); i++) { @@ -2102,6 +2103,9 @@ public abstract class SQLLogging { subtableData.setLongName(subtableTableDef.getLongName().getStringValue()); subtableData.setBinaryFilename(subtableTableDef.getBinaryfile().getStringValue()); subtableData.setDbIndex(subtableTableDef.getIndexItem().getIntegerValue()); + if (clickNoItem != null) { + subtableData.setClickNumber(clickNoItem.getIntegerValue()); + } try { subtableData.setChildUID(subtableTableDef.getUidItem().getLongObject()); } From 8252034f13bf542ffa7d263fffd06fb8f4183086 Mon Sep 17 00:00:00 2001 From: Douglas Gillespie <50671166+douggillespie@users.noreply.github.com> Date: Thu, 28 Mar 2024 16:43:21 +0000 Subject: [PATCH 2/3] Merge updates from Tethys branch (#133) * Tidying Lots of GUI improvement and code tidying. Functionality to export gzipped documents to reduce traffic. * V 2.02.10ba for user testing * Tidy up click selector Improve layout and tips on dialog and improve logic for manual and automatic event types. * Menu tide up --- .classpath | 3 +- dependency-reduced-pom.xml | 2 +- pom.xml | 2 +- src/PamController/DataIntegrityChecker.java | 12 + src/PamController/DataOutputStore.java | 7 + src/PamController/PamguardVersionInfo.java | 4 +- src/PamUtils/worker/PamWorkDialog.java | 17 + src/PamView/panel/SeparatorBorder.java | 27 ++ .../species/RWTethysDataProvider.java | 1 + src/binaryFileStorage/BinaryStore.java | 8 + .../checker/BinaryIntegrityChecker.java | 54 +++ .../dataSelector/ClickDataSelector.java | 21 +- .../dataSelector/ClickSelectPanel.java | 28 +- src/dataMap/OfflineDataMap.java | 19 + src/generalDatabase/DBControlUnit.java | 6 + src/tethys/CollectionHandler.java | 38 ++ src/tethys/TethysControl.java | 45 +- src/tethys/TethysTimeFuncs.java | 13 + .../calibration/CalibrationHandler.java | 17 +- .../swing/CalibrationsContactCard.java | 33 +- .../swing/CalibrationsExportWizard.java | 4 +- .../swing/CalibrationsMainPanel.java | 98 +++-- .../calibration/swing/CalibrationsTable.java | 1 + src/tethys/dbxml/DBXMLConnect.java | 120 ++++-- src/tethys/dbxml/DBXMLQueries.java | 3 + src/tethys/deployment/DeploymentHandler.java | 98 +++-- src/tethys/deployment/DeploymentOverview.java | 110 ++--- src/tethys/deployment/EffortFunctions.java | 242 +++++------ src/tethys/deployment/RecordingList.java | 211 ++++++++- src/tethys/deployment/RecordingPeriod.java | 7 + .../deployment/swing/EffortProblemDialog.java | 163 +++++++ .../swing/ProjectInformationPanel.java | 7 + .../deployment/swing/RecordingGapDialog.java | 3 +- .../detection/BinnedGranularityHandler.java | 12 + .../detection/CallGranularityHandler.java | 6 + .../detection/DetectionExportProgress.java | 17 +- src/tethys/detection/DetectionsHandler.java | 157 +++++-- .../EncounterGranularityHandler.java | 35 +- src/tethys/detection/GranularityHandler.java | 97 +++++ .../localization/LocalizationHandler.java | 17 + src/tethys/niluswraps/NilusChecker.java | 2 +- .../niluswraps/NilusSettingsWrapper.java | 4 +- src/tethys/output/StreamExportParams.java | 6 +- src/tethys/output/TethysExportParams.java | 18 + src/tethys/pamdata/AutoTethysProvider.java | 1 + .../species/DataBlockSpeciesManager.java | 26 +- src/tethys/species/ITISFunctions.java | 41 +- src/tethys/species/ITISTypes.java | 4 +- src/tethys/species/SpeciesMapItem.java | 14 + .../species/swing/DataBlockSpeciesDialog.java | 11 +- .../species/swing/SpeciesSearchDialog.java | 33 +- src/tethys/species/swing/SpeciesSubPanel.java | 20 +- .../swing/DatablockDetectionsPanel.java | 99 ++++- src/tethys/swing/DatablockSynchPanel.java | 101 ++++- src/tethys/swing/DeploymentExportPanel.java | 5 +- src/tethys/swing/DeploymentsPanel.java | 112 +++-- src/tethys/swing/FancyClientButton.java | 30 +- .../swing/PAMGuardDeploymentsTable.java | 45 +- src/tethys/swing/TethysConnectionPanel.java | 11 +- src/tethys/swing/TethysDeploymentsTable.java | 9 +- src/tethys/swing/TethysExportPanel.java | 227 ++++++++++ src/tethys/swing/TippedButton.java | 77 ++++ src/tethys/swing/XMLStringView.java | 5 +- .../swing/export/DeploymentPeriodPanel.java | 4 + .../swing/export/DescriptionTypePanel.java | 5 + src/tethys/swing/export/ExportWorkerCard.java | 14 +- .../swing/export/ResponsiblePartyPanel.java | 6 + src/tethys/tooltips/TethysTips.java | 403 ++++++++++++++++++ 68 files changed, 2534 insertions(+), 564 deletions(-) create mode 100644 src/PamController/DataIntegrityChecker.java create mode 100644 src/PamView/panel/SeparatorBorder.java create mode 100644 src/binaryFileStorage/checker/BinaryIntegrityChecker.java create mode 100644 src/tethys/CollectionHandler.java create mode 100644 src/tethys/deployment/swing/EffortProblemDialog.java create mode 100644 src/tethys/localization/LocalizationHandler.java create mode 100644 src/tethys/swing/TethysExportPanel.java create mode 100644 src/tethys/swing/TippedButton.java create mode 100644 src/tethys/tooltips/TethysTips.java diff --git a/.classpath b/.classpath index 26c568d7..188c7291 100644 --- a/.classpath +++ b/.classpath @@ -6,9 +6,8 @@ - + - diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml index 217f240b..fd1fe9e4 100644 --- a/dependency-reduced-pom.xml +++ b/dependency-reduced-pom.xml @@ -4,7 +4,7 @@ org.pamguard Pamguard Pamguard Java12+ - 2.02.10ae + 2.02.10bb Pamguard for Java 12+, using Maven to control dependcies www.pamguard.org diff --git a/pom.xml b/pom.xml index 8051e75d..e6b66c5f 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.pamguard Pamguard - 2.02.10ae + 2.02.10bb Pamguard Java12+ Pamguard for Java 12+, using Maven to control dependcies www.pamguard.org diff --git a/src/PamController/DataIntegrityChecker.java b/src/PamController/DataIntegrityChecker.java new file mode 100644 index 00000000..566298e4 --- /dev/null +++ b/src/PamController/DataIntegrityChecker.java @@ -0,0 +1,12 @@ +package PamController; + +/** + * Provides a set of functions that can check and repair data. + * @author dg50 + * + */ +public interface DataIntegrityChecker { + + public boolean checkDataStore(); + +} diff --git a/src/PamController/DataOutputStore.java b/src/PamController/DataOutputStore.java index a3536410..05c88d66 100644 --- a/src/PamController/DataOutputStore.java +++ b/src/PamController/DataOutputStore.java @@ -29,4 +29,11 @@ public interface DataOutputStore extends OfflineDataStore { */ public boolean deleteDataFrom(long timeMillis); + /** + * Get a data integrity checker. This can be called at startup to see if there is a problem. + * @return + */ + public DataIntegrityChecker getInegrityChecker(); + + } diff --git a/src/PamController/PamguardVersionInfo.java b/src/PamController/PamguardVersionInfo.java index f4710929..f579eca6 100644 --- a/src/PamController/PamguardVersionInfo.java +++ b/src/PamController/PamguardVersionInfo.java @@ -31,12 +31,12 @@ public class PamguardVersionInfo { * Version number, major version.minorversion.sub-release. * Note: can't go higher than sub-release 'f' */ - static public final String version = "2.02.10ae"; + static public final String version = "2.02.10bb"; /** * Release date */ - static public final String date = "15 March 2024"; + static public final String date = "22 March 2024"; // /** // * Release type - Beta or Core diff --git a/src/PamUtils/worker/PamWorkDialog.java b/src/PamUtils/worker/PamWorkDialog.java index b4a41d3f..2a3d89c8 100644 --- a/src/PamUtils/worker/PamWorkDialog.java +++ b/src/PamUtils/worker/PamWorkDialog.java @@ -1,5 +1,7 @@ package PamUtils.worker; +import java.awt.Dimension; +import java.awt.Point; import java.awt.Window; import javax.swing.BoxLayout; @@ -23,6 +25,9 @@ public class PamWorkDialog extends PamDialog { mainPanel.setBorder(new TitledBorder("Task Progress")); // GridBagConstraints c = new PamGridBagContraints(); mainPanel.add(progressBar = new JProgressBar(0, 100)); + Dimension sz = progressBar.getPreferredSize(); + sz.width = 300; + progressBar.setPreferredSize(sz); textRows = new PamTextDisplay[nTextRows]; for (int i = 0; i < nTextRows; i++) { // c.gridy++; @@ -35,6 +40,18 @@ public class PamWorkDialog extends PamDialog { getButtonPanel().setVisible(false); setDialogComponent(mainPanel); setResizable(true); + + if (parentFrame != null) { + Dimension prefSize = this.getPreferredSize(); + Point screenLoc = parentFrame.getLocationOnScreen(); + int x = (parentFrame.getWidth()-prefSize.width)/2; + int y = (parentFrame.getHeight()-prefSize.height)/2; + if (screenLoc != null) { + x += screenLoc.x; + y += screenLoc.y; + } + setLocation(x, y); + } } public void update(PamWorkProgressMessage progressMsg) { diff --git a/src/PamView/panel/SeparatorBorder.java b/src/PamView/panel/SeparatorBorder.java new file mode 100644 index 00000000..f42192d4 --- /dev/null +++ b/src/PamView/panel/SeparatorBorder.java @@ -0,0 +1,27 @@ +package PamView.panel; + +import java.awt.Component; +import java.awt.FontMetrics; +import java.awt.Graphics; + +import javax.swing.border.TitledBorder; + +public class SeparatorBorder extends TitledBorder { + + private static final long serialVersionUID = 1L; + + public SeparatorBorder(String title) { + super(title); + // TODO Auto-generated constructor stub + } + public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { + FontMetrics fm = g.getFontMetrics(); + if (fm != null) { + height = fm.getHeight()-EDGE_SPACING*2-1; + } + else { + height = 12; + } + super.paintBorder(c, g, x, y, width, height); + } +} diff --git a/src/RightWhaleEdgeDetector/species/RWTethysDataProvider.java b/src/RightWhaleEdgeDetector/species/RWTethysDataProvider.java index 3577bd12..caff472e 100644 --- a/src/RightWhaleEdgeDetector/species/RWTethysDataProvider.java +++ b/src/RightWhaleEdgeDetector/species/RWTethysDataProvider.java @@ -29,6 +29,7 @@ public class RWTethysDataProvider extends AutoTethysProvider { Parameters parameters = detection.getParameters(); parameters.setScore((double) rweDataUnit.rweSound.soundType); double snr = 20.*Math.log10(rweDataUnit.rweSound.signal/rweDataUnit.rweSound.noise); + snr = AutoTethysProvider.roundDecimalPlaces(snr, 1); parameters.setSNRDB(snr); return detection; diff --git a/src/binaryFileStorage/BinaryStore.java b/src/binaryFileStorage/BinaryStore.java index 96b89b3f..1785eb6f 100644 --- a/src/binaryFileStorage/BinaryStore.java +++ b/src/binaryFileStorage/BinaryStore.java @@ -37,6 +37,7 @@ import dataMap.OfflineDataMap; import dataMap.OfflineDataMapPoint; import PamController.AWTScheduler; import PamController.DataInputStore; +import PamController.DataIntegrityChecker; import PamController.DataOutputStore; import PamController.OfflineDataStore; import PamController.PamControlledUnit; @@ -74,6 +75,7 @@ import annotation.binary.AnnotationBinaryData; import annotation.handler.AnnotationHandler; import backupmanager.BackupInformation; import binaryFileStorage.backup.BinaryBackupStream; +import binaryFileStorage.checker.BinaryIntegrityChecker; import binaryFileStorage.layoutFX.BinaryStoreGUIFX; /** @@ -946,6 +948,8 @@ PamSettingsSource, DataOutputStore { PamController.getInstance().notifyModelChanged(PamControllerInterface.CHANGED_OFFLINE_DATASTORE); // System.out.println("BinaryDataMapMaker really done " + this); dataMapMaker = null; + + getInegrityChecker().checkDataStore(); } @Override @@ -2593,5 +2597,9 @@ PamSettingsSource, DataOutputStore { public String getDataLocation() { return binaryStoreSettings.getStoreLocation(); } + @Override + public DataIntegrityChecker getInegrityChecker() { + return new BinaryIntegrityChecker(this); + } } diff --git a/src/binaryFileStorage/checker/BinaryIntegrityChecker.java b/src/binaryFileStorage/checker/BinaryIntegrityChecker.java new file mode 100644 index 00000000..011b9c05 --- /dev/null +++ b/src/binaryFileStorage/checker/BinaryIntegrityChecker.java @@ -0,0 +1,54 @@ +package binaryFileStorage.checker; + +import java.util.ArrayList; + +import PamController.DataIntegrityChecker; +import PamController.PamController; +import PamView.dialog.warn.WarnOnce; +import PamguardMVC.PamDataBlock; +import binaryFileStorage.BinaryStore; +import dataMap.OfflineDataMap; + +public class BinaryIntegrityChecker implements DataIntegrityChecker { + + private BinaryStore binaryStore; + + public BinaryIntegrityChecker(BinaryStore binaryStore) { + this.binaryStore = binaryStore; + } + + public boolean checkDataStore() { + checkMapOverlaps(); + + + return true; + } + + private boolean checkMapOverlaps() { + boolean ok = true; + ArrayList dataBlocks = binaryStore.getStreamingDataBlocks(false); + for (PamDataBlock aBlock : dataBlocks) { + ok &= checkMapOverlaps(aBlock); + } + return ok; + } + + private boolean checkMapOverlaps(PamDataBlock aBlock) { + OfflineDataMap dataMap = aBlock.getOfflineDataMap(binaryStore); + if (dataMap == null) { + return true; + } + long overlaps = dataMap.checkOverlaps(); + if (overlaps <= 0) { + return true; + } + String warn = String.format("Binary data %s has overlapping data files, with a maximum overlap of %3.1fs
" + + "This can occur when data have been reprocessed multiple times offline into " + + "the same folders.
Since files sometimes get slightly different names, old files are" + + "not always overwritten.
" + + "You should determine which files are 'old' by looking at file creation dates and delete them.", + aBlock.getLongDataName(), (double) overlaps / 1000.); + WarnOnce.showNamedWarning("BINOVERLAPWARNING", PamController.getMainFrame(), "Data Integrity Warning", warn, WarnOnce.WARNING_MESSAGE); + return false; + } +} diff --git a/src/clickDetector/dataSelector/ClickDataSelector.java b/src/clickDetector/dataSelector/ClickDataSelector.java index a7a1d4d8..5be77c2a 100644 --- a/src/clickDetector/dataSelector/ClickDataSelector.java +++ b/src/clickDetector/dataSelector/ClickDataSelector.java @@ -112,14 +112,23 @@ public class ClickDataSelector extends DataSelector { // see if there is a super detection and see if it's got a comment. String comment = oev.getComment(); + + boolean isAutomatic = false; if (comment != null) { - if (clickAlarmParameters.onlineAutoEvents && comment.startsWith("Automatic")) { - return true; - } - if (clickAlarmParameters.onlineManualEvents && comment.startsWith("Manual")) { - return true; - } + isAutomatic = comment.startsWith("Automatic"); } + if (isAutomatic && clickAlarmParameters.onlineAutoEvents) { + return true; + } + else if (clickAlarmParameters.onlineManualEvents) { + return true; + } +// if (clickAlarmParameters.onlineAutoEvents && comment.startsWith("Automatic")) { +// return true; +// } +// if (clickAlarmParameters.onlineManualEvents && comment.startsWith("Manual")) { +// return true; +// } /* * Otherwise need to work out where the hell the event type is in the * list of event types and see if it's wanted. diff --git a/src/clickDetector/dataSelector/ClickSelectPanel.java b/src/clickDetector/dataSelector/ClickSelectPanel.java index 859ee841..512e088d 100644 --- a/src/clickDetector/dataSelector/ClickSelectPanel.java +++ b/src/clickDetector/dataSelector/ClickSelectPanel.java @@ -30,6 +30,8 @@ import PamView.dialog.PamDialog; import PamView.dialog.PamDialogPanel; import PamView.dialog.PamGridBagContraints; import PamView.panel.PamAlignmentPanel; +import PamView.panel.SeparatorBorder; +import PamView.panel.WestAlignedPanel; public class ClickSelectPanel implements PamDialogPanel { @@ -40,6 +42,8 @@ public class ClickSelectPanel implements PamDialogPanel { private ClickDataSelector clickDataSelector; private JPanel mainPanel; private boolean isViewer; + + public static final String mainTip = "You should select options in both the Click Type and the Event Type panels"; public ClickSelectPanel(ClickDataSelector clickDataSelector, boolean allowScores, boolean useEventTypes) { this.clickDataSelector = clickDataSelector; @@ -79,7 +83,9 @@ public class ClickSelectPanel implements PamDialogPanel { private LookupList lutList; public EventTypePanel() { - setBorder(new TitledBorder("Event Type Selection")); + setBorder(new SeparatorBorder("Event Type Selection")); + + setToolTipText(mainTip); } void setParams() { @@ -102,16 +108,24 @@ public class ClickSelectPanel implements PamDialogPanel { boxPanel.add(onlineAuto = new JCheckBox("Automatically detected click trains"), c); onlineAuto.setSelected(clickAlarmParameters.onlineAutoEvents); + useUnassigned.setToolTipText("Clicks that are NOT part of a manual or automatic click train"); + onlineManual.setToolTipText("Clicks that are part of a manually marked click train"); + onlineAuto.setToolTipText("Clicks that are part of an automatically detected click train"); + lutList = LookUpTables.getLookUpTables().getLookupList(ClicksOffline.ClickTypeLookupName); if (lutList == null) { return; } + c.gridy++; + boxPanel.add(new JLabel("OR the following click train types ...", JLabel.LEFT), c); useType = new JCheckBox[lutList.getList().size()]; for (int i = 0; i < useType.length; i++) { c.gridy++; boxPanel.add(useType[i] = new JCheckBox(lutList.getList().get(i).getText()), c); useType[i].setSelected(clickAlarmParameters.isUseEventType(lutList.getList().get(i).getCode())); + String tip = String.format("Clicks that are part of a click train labelled as %s", lutList.getList().get(i).getText()); + useType[i].setToolTipText(tip); } } @@ -158,10 +172,9 @@ public class ClickSelectPanel implements PamDialogPanel { northPanel = new JPanel(); northPanel.setLayout(new GridBagLayout()); GridBagConstraints c = new PamGridBagContraints(); - northPanel.setBorder(new TitledBorder("Echoes")); c.gridwidth = 3; c.anchor = GridBagConstraints.WEST; - northPanel.add(useEchoes = new JCheckBox("Use Echoes"), c); + northPanel.add(new PamAlignmentPanel(useEchoes = new JCheckBox("Use Echoes"), BorderLayout.WEST), c); c.gridwidth = 1; c.gridy++; c.gridx = 0; @@ -178,11 +191,13 @@ public class ClickSelectPanel implements PamDialogPanel { northPanel.add(scoreByAmplitude = new JCheckBox("Score by amplitude"), c); scoreByAmplitude.setVisible(allowScores); scoreByAmplitude.addActionListener(new AllSpeciesListener()); - add(BorderLayout.NORTH, northPanel); + WestAlignedPanel walpn; + add(BorderLayout.NORTH, walpn = new WestAlignedPanel(northPanel)); + walpn.setBorder(new SeparatorBorder("Echoes")); JPanel centralOuterPanel = new JPanel(new BorderLayout()); centralPanel.setLayout(new GridBagLayout()); - centralOuterPanel.setBorder(new TitledBorder("Click Type Selection")); + centralOuterPanel.setBorder(new SeparatorBorder("Click Type Selection")); add(BorderLayout.CENTER, centralOuterPanel); JScrollPane scrollPane = new DialogScrollPane(new PamAlignmentPanel(centralPanel, BorderLayout.WEST), 10); @@ -197,6 +212,9 @@ public class ClickSelectPanel implements PamDialogPanel { clearAll.addActionListener(new AutoSelect(false)); centralOuterPanel.add(BorderLayout.SOUTH, new PamAlignmentPanel(centralEastPanel, BorderLayout.WEST)); + centralOuterPanel.setToolTipText(mainTip); + + setToolTipText(mainTip); } void setParams() { diff --git a/src/dataMap/OfflineDataMap.java b/src/dataMap/OfflineDataMap.java index d51ee120..d62f2224 100644 --- a/src/dataMap/OfflineDataMap.java +++ b/src/dataMap/OfflineDataMap.java @@ -844,4 +844,23 @@ abstract public class OfflineDataMap { obs.updateDataMap(this, mapPoint); } } + + /** + * Check to see if any data maps overlap in time. + * @return largest overlap between maps. 0 or -ve means no overlap. + */ + public long checkOverlaps() { + long bigLap = Long.MIN_VALUE; + TmapPoint prevPoint = null; + for (TmapPoint mapPoint : mapPoints) { + if (prevPoint != null) { + long olap = prevPoint.getEndTime() - mapPoint.getStartTime(); +// if (mapPoint.getStartTime() < prevPoint.getEndTime()) { + bigLap = Math.max(olap, bigLap); +// } + } + prevPoint = mapPoint; + } + return bigLap; + } } diff --git a/src/generalDatabase/DBControlUnit.java b/src/generalDatabase/DBControlUnit.java index 0b4239b8..a48971da 100644 --- a/src/generalDatabase/DBControlUnit.java +++ b/src/generalDatabase/DBControlUnit.java @@ -18,6 +18,7 @@ import generalDatabase.backup.DatabaseBackupStream; import pamScrollSystem.ViewLoadObserver; import pamViewFX.pamTask.PamTaskUpdate; import PamController.AWTScheduler; +import PamController.DataIntegrityChecker; import PamController.DataOutputStore; import PamController.OfflineDataStore; import PamController.PamConfiguration; @@ -524,5 +525,10 @@ public class DBControlUnit extends DBControl implements DataOutputStore { } return state; } + @Override + public DataIntegrityChecker getInegrityChecker() { + // TODO Auto-generated method stub + return null; + } } diff --git a/src/tethys/CollectionHandler.java b/src/tethys/CollectionHandler.java new file mode 100644 index 00000000..66208fa0 --- /dev/null +++ b/src/tethys/CollectionHandler.java @@ -0,0 +1,38 @@ +package tethys; + +abstract public class CollectionHandler { + + private Collection collection; + + protected TethysControl tethysControl; + + /** + * @param tethysControl + * @param collection + */ + public CollectionHandler(TethysControl tethysControl, Collection collection) { + this.tethysControl = tethysControl; + this.collection = collection; + } + + public String collectionName() { + return collection.collectionName(); + } + + /** + * @return the collection + */ + public Collection getCollection() { + return collection; + } + + /** + * @return the tethysControl + */ + public TethysControl getTethysControl() { + return tethysControl; + } + + public abstract String getHelpPoint(); + +} diff --git a/src/tethys/TethysControl.java b/src/tethys/TethysControl.java index 83e445b7..42889f45 100644 --- a/src/tethys/TethysControl.java +++ b/src/tethys/TethysControl.java @@ -32,6 +32,8 @@ import PamController.PamSettingManager; import PamController.PamSettings; import PamUtils.PamFileChooser; import PamUtils.PamFileFilter; +import PamUtils.worker.PamWorkWrapper; +import PamUtils.worker.PamWorker; import PamView.PamTabPanel; import PamView.dialog.warn.WarnOnce; import PamguardMVC.PamDataBlock; @@ -104,7 +106,9 @@ public class TethysControl extends PamControlledUnit implements PamSettings, Tet serverCheckTimer = new Timer(10000, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - checkServer(); + ServerStatus serverState = checkServer(); + // check less often when it's OK to generate less debug output. + serverCheckTimer.setDelay(serverState.ok ? 600000 : 5000); } }); serverCheckTimer.setInitialDelay(0); @@ -469,6 +473,8 @@ public class TethysControl extends PamControlledUnit implements PamSettings, Tet // case PamControllerInterface.INITIALIZATION_COMPLETE: initializationStuff(); break; + case PamControllerInterface.HYDROPHONE_ARRAY_CHANGED: + sendStateUpdate(new TethysState(StateType.UPDATEMETADATA)); } } @@ -525,7 +531,32 @@ public class TethysControl extends PamControlledUnit implements PamSettings, Tet } } - private void countProjectDetections() { +// /** +// * Count project detections in a worker thread; +// */ +// public void countProjectDetectionsT() { +// sendStateUpdate(new TethysState(StateType.EXPORTRDATA, Collection.Detections)); +// PamWorker worker = new PamWorker(new PamWorkWrapper() { +// +// @Override +// public String runBackgroundTask(PamWorker pamWorker) { +// countProjectDetections(); +// return null; +// } +// +// @Override +// public void taskFinished(String result) { +// sendStateUpdate(new TethysState(StateType.NEWPAMGUARDSELECTION, Collection.Detections)); +// +// } +// }, getGuiFrame(), 0, "Updating detections counts"); +// worker.start(); +// } + + /** + * Count project detections in the current thread. + */ + private synchronized void countProjectDetections() { if (dataBlockSynchInfos == null) { return; } @@ -549,12 +580,6 @@ public class TethysControl extends PamControlledUnit implements PamSettings, Tet i++; } -// int[] counts = dbxmlQueries.countDataForProject(deplData.getProject(), dataPrefixes); -// if (counts != null) { -// for ( i = 0; i < counts.length; i++ ) { -// dataBlockSynchInfos.get(i).setDataCount(counts[i]); -// } -// } } /** @@ -688,11 +713,9 @@ public class TethysControl extends PamControlledUnit implements PamSettings, Tet * @param dataBlock */ public void exportedDetections(PamDataBlock dataBlock) { - sendStateUpdate(new TethysState(StateType.EXPORTRDATA, Collection.Detections)); countProjectDetections(); - sendStateUpdate(new TethysState(StateType.NEWPAMGUARDSELECTION, Collection.Detections)); +// sendStateUpdate(new TethysState(StateType.NEWPAMGUARDSELECTION, Collection.Detections)); } - /** * @return the calibrationHandler */ diff --git a/src/tethys/TethysTimeFuncs.java b/src/tethys/TethysTimeFuncs.java index 70d11704..e238fb22 100644 --- a/src/tethys/TethysTimeFuncs.java +++ b/src/tethys/TethysTimeFuncs.java @@ -12,6 +12,7 @@ import javax.xml.datatype.DatatypeFactory; import javax.xml.datatype.XMLGregorianCalendar; import PamUtils.PamCalendar; +import nilus.Helper; public class TethysTimeFuncs { @@ -54,6 +55,18 @@ public class TethysTimeFuncs { * @return */ public static XMLGregorianCalendar fromGregorianXML(String gregorianString) { + + try { + XMLGregorianCalendar xmlCal = Helper.timestamp(gregorianString); + return xmlCal; + } catch (DatatypeConfigurationException e1) { + // TODO Auto-generated catch block +// e1.printStackTrace(); + } + /** + * Above should work just fine. If it doesn't use my own code below... + */ + // typical string is 2018-10-20T00:00:00Z if (gregorianString == null) { return null; diff --git a/src/tethys/calibration/CalibrationHandler.java b/src/tethys/calibration/CalibrationHandler.java index 37dc186e..2aff00d8 100644 --- a/src/tethys/calibration/CalibrationHandler.java +++ b/src/tethys/calibration/CalibrationHandler.java @@ -1,6 +1,5 @@ package tethys.calibration; -import java.lang.reflect.Field; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -37,6 +36,7 @@ import nilus.MetadataInfo; import nilus.QualityValueBasic; import nilus.ResponsibleParty; import tethys.Collection; +import tethys.CollectionHandler; import tethys.DocumentInfo; import tethys.DocumentNilusObject; import tethys.TethysControl; @@ -52,9 +52,7 @@ import tethys.niluswraps.NilusUnpacker; import tethys.pamdata.AutoTethysProvider; import tethys.reporter.TethysReporter; -public class CalibrationHandler implements TethysStateObserver { - - private TethysControl tethysControl; +public class CalibrationHandler extends CollectionHandler implements TethysStateObserver { private ArrayList> calibrationsList; @@ -65,10 +63,14 @@ public class CalibrationHandler implements TethysStateObserver { public static final String[] qaTypes = {"unverified", "valid", "invalid"}; private Helper nilusHelper; + + public static final String helpPoint = "utilities.tethys.docs.calibrations"; + /** * @param tethysControl */ public CalibrationHandler(TethysControl tethysControl) { + super(tethysControl, Collection.Calibrations); this.tethysControl = tethysControl; calibrationsList = new ArrayList(); tethysControl.addStateObserver(this); try { @@ -504,7 +506,7 @@ public class CalibrationHandler implements TethysStateObserver { } String seachPattern = makeChannelNamePart(iChan); for (int i = 0; i < calibrationsList.size(); i++) { - String docName = calibrationsList.get(i).getDocumentName(); + String docName = calibrationsList.get(i).getDocumentId(); if (docName.endsWith(seachPattern)) { return true; } @@ -570,4 +572,9 @@ public class CalibrationHandler implements TethysStateObserver { } return theseCals; } + + @Override + public String getHelpPoint() { + return helpPoint; + } } diff --git a/src/tethys/calibration/swing/CalibrationsContactCard.java b/src/tethys/calibration/swing/CalibrationsContactCard.java index 2dc6e581..0ad03c36 100644 --- a/src/tethys/calibration/swing/CalibrationsContactCard.java +++ b/src/tethys/calibration/swing/CalibrationsContactCard.java @@ -5,6 +5,7 @@ import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.util.ArrayList; import java.util.Date; import javax.swing.BoxLayout; @@ -18,6 +19,9 @@ import javax.xml.datatype.XMLGregorianCalendar; import org.jdesktop.swingx.JXDatePicker; +import Array.ArrayManager; +import Array.Hydrophone; +import Array.PamArray; import PamView.dialog.PamDialog; import PamView.dialog.PamGridBagContraints; import PamView.panel.WestAlignedPanel; @@ -43,6 +47,8 @@ public class CalibrationsContactCard extends CalibrationsCard { private JButton copyDown, copyUp; + private JComboBox hydrophoneSelection; + public CalibrationsContactCard(PamWizard pamWizard) { super(pamWizard, "Contact Details"); // TODO Auto-generated constructor stub @@ -57,17 +63,26 @@ public class CalibrationsContactCard extends CalibrationsCard { JPanel datePanel = new JPanel(new GridBagLayout()); JPanel lp = new WestAlignedPanel(datePanel); - lp.setBorder(new TitledBorder("Calibration date")); + lp.setBorder(new TitledBorder("Date and hydrophones")); GridBagConstraints c = new PamGridBagContraints(); datePanel.add(new JLabel("Calibration date: ", JLabel.RIGHT), c); datePicker = new JXDatePicker(); c.gridx++; datePanel.add(datePicker, c); - c.gridx = 0; - c.gridy++; - datePanel.add(new JLabel("Update Frequency", JLabel.RIGHT), c); +// c.gridx = 0; + c.gridx++; + datePanel.add(new JLabel(" Update Frequency ", JLabel.RIGHT), c); c.gridx++; datePanel.add(updateInterval, c); + c.gridx = 0; + c.gridy++; + datePanel.add(new JLabel(" Hydrophones ", JLabel.RIGHT), c); + c.gridwidth = 5; + c.gridx++; + hydrophoneSelection = new JComboBox<>(); + datePanel.add(hydrophoneSelection, c); + hydrophoneSelection.setToolTipText("Select which hydrophone calibrations to export"); + calibrator = new ResponsiblePartyPanel("Technical Person"); dataManager = new ResponsiblePartyPanel("Data Manager"); @@ -202,6 +217,16 @@ public class CalibrationsContactCard extends CalibrationsCard { datePicker.setDate(new Date(TethysTimeFuncs.millisFromGregorianXML(ts))); } + hydrophoneSelection.removeAllItems(); + hydrophoneSelection.addItem("All hydrophones"); + PamArray array = ArrayManager.getArrayManager().getCurrentArray(); + ArrayList phones = array.getHydrophoneArray(); + int i = 0; + for (Hydrophone phone : phones) { + String txt = String.format("Hydrophone %d, %s, %3.1f dBre1\u00B5Pa", i, phone.getType(), phone.getSensitivity()); + hydrophoneSelection.addItem(txt); + i++; + } } diff --git a/src/tethys/calibration/swing/CalibrationsExportWizard.java b/src/tethys/calibration/swing/CalibrationsExportWizard.java index 74bb356a..1ab7bc6e 100644 --- a/src/tethys/calibration/swing/CalibrationsExportWizard.java +++ b/src/tethys/calibration/swing/CalibrationsExportWizard.java @@ -5,6 +5,7 @@ import java.awt.Window; import PamView.wizard.PamWizard; import PamView.wizard.PamWizardCard; import nilus.Calibration; +import tethys.calibration.CalibrationHandler; public class CalibrationsExportWizard extends PamWizard { @@ -13,8 +14,9 @@ public class CalibrationsExportWizard extends PamWizard { private CalibrationsExportWizard(Window parentFrame, Calibration sampleDocument) { super(parentFrame, "Calibrations Export"); this.sampleDocument = sampleDocument; - addCard(new CalibrationProcessCard(this)); addCard(new CalibrationsContactCard(this)); + addCard(new CalibrationProcessCard(this)); + setHelpPoint(CalibrationHandler.helpPoint); } public static Calibration showWizard(Window parentFrame, Calibration sampleDocument) { diff --git a/src/tethys/calibration/swing/CalibrationsMainPanel.java b/src/tethys/calibration/swing/CalibrationsMainPanel.java index e13365ef..cd1275f7 100644 --- a/src/tethys/calibration/swing/CalibrationsMainPanel.java +++ b/src/tethys/calibration/swing/CalibrationsMainPanel.java @@ -15,53 +15,47 @@ import PamView.panel.PamPanel; import tethys.TethysControl; import tethys.TethysState; import tethys.calibration.CalibrationHandler; +import tethys.deployment.PInstrument; +import tethys.swing.TethysExportPanel; import tethys.swing.TethysGUIPanel; +import tethys.swing.TippedButton; -public class CalibrationsMainPanel extends TethysGUIPanel { +public class CalibrationsMainPanel extends TethysExportPanel { private CalibrationHandler calibrationHandler; private CalibrationsTable calibrationsTable; - private JPanel mainPanel; - - private JPanel ctrlPanel; - - private JButton exportButton; - - private JLabel warning; +// private JPanel mainPanel; +// +// private JPanel ctrlPanel; +// +// private TippedButton exportButton; +// +// private JLabel warning; public CalibrationsMainPanel(TethysControl tethysControl, CalibrationHandler calibrationHandler) { - super(tethysControl); + super(tethysControl, calibrationHandler, false); this.calibrationHandler = calibrationHandler; - mainPanel = new PamPanel(new BorderLayout()); + JPanel mainPanel = getMainPanel(); +// mainPanel = new PamPanel(new BorderLayout()); mainPanel.setBorder(new TitledBorder("Instrument calibration information")); calibrationsTable = new CalibrationsTable(tethysControl, calibrationHandler); mainPanel.add(BorderLayout.CENTER, calibrationsTable.getComponent()); - ctrlPanel = new PamPanel(new BorderLayout()); - exportButton = new JButton("Export ..."); - ctrlPanel.add(BorderLayout.WEST, exportButton); - warning = new JLabel(); - ctrlPanel.add(BorderLayout.CENTER, warning); - mainPanel.add(BorderLayout.NORTH, ctrlPanel); - exportButton.setToolTipText("Export calibration data to database"); - exportButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - exportCalibrations(); - } - }); - } - - protected void exportCalibrations() { - calibrationHandler.exportAllCalibrations(); - } - - @Override - public JComponent getComponent() { - return mainPanel; +// ctrlPanel = new PamPanel(new BorderLayout()); +// exportButton = new TippedButton("Export ...", "Export calibration data to database"); +// ctrlPanel.add(BorderLayout.WEST, exportButton); +// warning = new JLabel(); +// ctrlPanel.add(BorderLayout.CENTER, warning); +// mainPanel.add(BorderLayout.NORTH, ctrlPanel); +// exportButton.addActionListener(new ActionListener() { +// @Override +// public void actionPerformed(ActionEvent e) { +// exportCalibrations(); +// } +// }); } @Override @@ -71,7 +65,45 @@ public class CalibrationsMainPanel extends TethysGUIPanel { } private void enableControls() { - exportButton.setEnabled(getTethysControl().isServerOk()); + if (getTethysControl().isServerOk() == false) { + disableExport("Tethys Server not running"); + return; + } + if (isHydrophoneNamed() == false) { + disableExport("Can't export calibrations until the Hydrophone array has been correctly named"); + return; + }; + enableExport(true); + } + + /** + * Check to see if hydrophone is named correctly. + * @return + */ + private boolean isHydrophoneNamed() { + PInstrument currentInstrument = getTethysControl().getDeploymentHandler().getCurrentArrayInstrument(); + if (currentInstrument == null) { + return false; + } + if (currentInstrument.instrumentId == null || currentInstrument.instrumentType == null) { + return false; + } + + if (currentInstrument.instrumentId.length() == 0 || currentInstrument.instrumentType.length() == 0) { + return false; + } + return true; + } + + @Override + protected void exportButtonPressed(ActionEvent e) { + calibrationHandler.exportAllCalibrations(); + } + + @Override + protected void optionsButtonPressed(ActionEvent e) { + // TODO Auto-generated method stub + } } diff --git a/src/tethys/calibration/swing/CalibrationsTable.java b/src/tethys/calibration/swing/CalibrationsTable.java index 45b91f18..c9405f1a 100644 --- a/src/tethys/calibration/swing/CalibrationsTable.java +++ b/src/tethys/calibration/swing/CalibrationsTable.java @@ -120,6 +120,7 @@ public class CalibrationsTable extends TethysGUIPanel { }); popMenu.add(menuItem); } + popMenu.addSeparator(); if (n > 1) { menuItem = new JMenuItem("Delete selected documents"); menuItem.addActionListener(new ActionListener() { diff --git a/src/tethys/dbxml/DBXMLConnect.java b/src/tethys/dbxml/DBXMLConnect.java index 9b12ecd5..1da977f0 100644 --- a/src/tethys/dbxml/DBXMLConnect.java +++ b/src/tethys/dbxml/DBXMLConnect.java @@ -3,14 +3,17 @@ package tethys.dbxml; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; +import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; +import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.nio.file.Files; import java.util.ArrayList; +import java.util.zip.GZIPOutputStream; import javax.xml.bind.JAXBException; @@ -67,7 +70,7 @@ public class DBXMLConnect { } return true; } - + /** * Get the client. The client will only be recreated if the url changes @@ -113,9 +116,9 @@ public class DBXMLConnect { } return file; } - + /** - * Create a temporary nilus file. + * Create a temporary nilus file. * @param nilusObject * @return * @throws TethysException @@ -124,20 +127,20 @@ public class DBXMLConnect { String tempName = getTempFileName(nilusObject); tempName = tempDirectory.getAbsolutePath() + File.separator + tempName + ".xml"; File tempFile = new File(tempName); - File retFile = createXMLDocument(nilusObject, tempFile); + File retFile = createXMLDocument(nilusObject, tempFile); retFile.deleteOnExit(); return retFile; } - - + + public boolean postAndLog(Object nilusObject) throws TethysException { return postAndLog(nilusObject, null); } - + /** * I don't think this should ever be used since everything goes a bit pear - * shaped if the documentName isn't the same as the Id. However, for Calibration + * shaped if the documentName isn't the same as the Id. However, for Calibration * documents this is no longer the case, since a Calibration can have multiple * entries on different dates, so allow it ! * @param nilusObject @@ -145,11 +148,11 @@ public class DBXMLConnect { * @return * @throws TethysException */ - public boolean postAndLog(Object nilusObject, String documentName) throws TethysException - { + public boolean postAndLog(Object nilusObject, String documentName) throws TethysException + { boolean ok = NilusChecker.warnEmptyFields(tethysControl.getGuiFrame(), nilusObject); - - + + TethysException e = null; boolean success = false; try { @@ -168,13 +171,13 @@ public class DBXMLConnect { } return success; } - + /** * take a nilus object loaded with PamGuard data and post it to the Tethys database * * @param pamGuardObjs a nilus object loaded with PamGuard data * @return error string, null string means there are no errors - * @throws TethysException + * @throws TethysException */ private boolean postToTethys(Object nilusObject, String documentName) throws TethysException { @@ -192,12 +195,35 @@ public class DBXMLConnect { MarshalXML marshal = new MarshalXML(); marshal.createInstance(objClass); marshal.marshal(nilusObject, tempFile.toString()); + // above lines have made a file. Are now going to gzip it before sending to Tethys + File zipFile = null; + try { + zipFile = zipOutputFile(tempFile); + } + catch (FileNotFoundException e1){ + System.out.println(e1.getMessage()); + } + catch (IOException e2) { + System.out.println(e2.getMessage()); + } + String finalName; + if (zipFile == null) { + finalName = bodgeName; + } + else { + finalName = zipFile.toString(); + } + + // tempFile = stripXMLHeader(tempFile); importReturn = Importer.ImportFiles(params.getFullServerName(), collection.collectionName(), - new String[] { bodgeName }, "", "", false); + new String[] { finalName }, "", "", false); tempFile.deleteOnExit(); + if (zipFile != null) { + zipFile.deleteOnExit(); + } } catch(IllegalArgumentException e) { throw new TethysException("IllegalArgumentException posting to Tethys: " + e.getMessage(), null); } catch (IOException e) { @@ -207,21 +233,49 @@ public class DBXMLConnect { } /* - * The returned string consists of the file name, then an XML report. - * Quite hard to see much common structure in this, so just look for + * The returned string consists of the file name, then an XML report. + * Quite hard to see much common structure in this, so just look for * two words, and */ boolean error = importReturn.contains(""); String name = tempFile.getName(); TethysReporter.getTethysReporter().addReport(new TethysReport(success, collection, name, name)); -// error = !success; might be a better options. +// error = !success; might be a better options. if (error) { throw new TethysException("Error posting to Tethys", importReturn); } return success; } + /** + * Zip an xml file (or any file) into a gz file with a new end + * @param xmlFile + * @return + * @throws FileNotFoundException + * @throws IOException + */ + private File zipOutputFile(File xmlFile) throws FileNotFoundException, IOException { + String zipName = xmlFile.toString() + "-temp-.gz"; + File zipFile = new File(zipName); + GZIPOutputStream opStream = new GZIPOutputStream(new FileOutputStream(zipFile)); + InputStream fis = new FileInputStream(xmlFile); + int chunkSize = 100*1024; + byte[] buffer = new byte[chunkSize]; +// ZipEntry zipEntry = new ZipEntry(xmlFile.getName()); + int bytesRead; + while ((bytesRead = fis.read(buffer)) >= 0) { + opStream.write(buffer, 0, bytesRead); + } + + opStream.close(); + + fis.close(); + + + return zipFile; + } + /** * Update a document within Tethys. We're assuming that a * document with the same name in the same collection already @@ -229,7 +283,7 @@ public class DBXMLConnect { * the removedocument function * @param nilusDocument * @return - * @throws TethysException + * @throws TethysException */ public boolean updateDocument(Object nilusDocument) throws TethysException { deleteDocument(nilusDocument); @@ -242,7 +296,7 @@ public class DBXMLConnect { * class to identify the correct collection. * @param nilusDocument * @return - * @throws TethysException + * @throws TethysException */ public boolean deleteDocument(Object nilusDocument) throws TethysException { @@ -259,7 +313,7 @@ public class DBXMLConnect { ['ECoastNARW0'] -An error will throw an exception. +An error will throw an exception. */ } catch (Exception e) { @@ -275,7 +329,7 @@ An error will throw an exception. * Delete a Deploymnet and any contained Detections document. Doesn't work ! * @param deploymentId * @return - * @throws TethysException + * @throws TethysException */ public boolean deleteDeployment(String deploymentId) throws TethysException { ArrayList detDocNames = tethysControl.getDbxmlQueries().getDetectionsDocuments(deploymentId); @@ -292,8 +346,8 @@ An error will throw an exception. } /** - * Remove a document based on a collection name and a cdocument Id. - * @param collection collection name. + * Remove a document based on a collection name and a cdocument Id. + * @param collection collection name. * @param documentName document name (not the internal Document Id) * @return * @throws TethysException @@ -303,8 +357,8 @@ An error will throw an exception. } /** - * Remove a document based on a collection name and a document namw. - * @param collectionName collection name. + * Remove a document based on a collection name and a document namw. + * @param collectionName collection name. * @param documentName document name (not the internal Document Id) * @return * @throws TethysException @@ -319,7 +373,7 @@ An error will throw an exception. ['ECoastNARW0'] - An error will throw an exception. + An error will throw an exception. */ } catch (Exception e) { @@ -330,14 +384,14 @@ An error will throw an exception. } /** - * check the return string from importFiles and if it's an - * error, throw an exception. Otherwise do nothing. + * check the return string from importFiles and if it's an + * error, throw an exception. Otherwise do nothing. * @param fileError */ private void checkReturnString(String fileError) { /** * Example good string is - * + * C:\Users\dg50\AppData\Local\Temp\PAMGuardTethys\20080311_2DSimplex_0.xml: 7360 bytes @@ -367,7 +421,7 @@ C:\Users\dg50\AppData\Local\Temp\PAMGuardTethys\20080311_2DSimplex_0.xmlnot: 0 b /** * Seems we have to get rid of the line - * which is being put there by the marshaller ? + * which is being put there by the marshaller ? * @param tempFile */ private File stripXMLHeader(File tempFile) { @@ -377,7 +431,7 @@ C:\Users\dg50\AppData\Local\Temp\PAMGuardTethys\20080311_2DSimplex_0.xmlnot: 0 b try { BufferedReader reader = new BufferedReader(new FileReader(tempFile)); BufferedWriter writer = new BufferedWriter(new FileWriter(tempTemp)); - String line = reader.readLine(); + String line = reader.readLine(); while (line != null) { // see if the line has any unicode in it int len = line.length(); diff --git a/src/tethys/dbxml/DBXMLQueries.java b/src/tethys/dbxml/DBXMLQueries.java index 460fbc66..d4c0be9a 100644 --- a/src/tethys/dbxml/DBXMLQueries.java +++ b/src/tethys/dbxml/DBXMLQueries.java @@ -515,6 +515,9 @@ public class DBXMLQueries { * first query for Detections documents associated with this deployment and datablock. * updated May 23 */ + if (dataBlock == null) { + return null; + } String queryNoDepl = "{\"species\":{\"query\":{\"op\":\"lib:completename2tsn\",\"optype\":\"function\",\"operands\":[\"%s\"]},\"return\":{\"op\":\"lib:tsn2completename\",\"optype\":\"function\",\"operands\":[\"%s\"]}},\"return\":[\"Detections/Id\"],\"select\":[{\"op\":\"=\",\"operands\":[\"Detections/Algorithm/Software\",\"LongDataName\"],\"optype\":\"binary\"}],\"enclose\":1}"; String queryWithDepl = "{\"species\":{\"query\":{\"op\":\"lib:completename2tsn\",\"optype\":\"function\",\"operands\":[\"%s\"]},\"return\":{\"op\":\"lib:tsn2completename\",\"optype\":\"function\",\"operands\":[\"%s\"]}},\"return\":[\"Detections/Id\"],\"select\":[{\"op\":\"=\",\"operands\":[\"Detections/DataSource/DeploymentId\",\"TheDeploymentId\"],\"optype\":\"binary\"},{\"op\":\"=\",\"operands\":[\"Detections/Algorithm/Software\",\"LongDataName\"],\"optype\":\"binary\"}],\"enclose\":1}"; String query; diff --git a/src/tethys/deployment/DeploymentHandler.java b/src/tethys/deployment/DeploymentHandler.java index 0c435fc0..8d82ac48 100644 --- a/src/tethys/deployment/DeploymentHandler.java +++ b/src/tethys/deployment/DeploymentHandler.java @@ -73,6 +73,7 @@ import nilus.UnknownSensor; import pamMaths.PamVector; import pamMaths.STD; import tethys.Collection; +import tethys.CollectionHandler; import tethys.TethysControl; import tethys.TethysLocationFuncs; import tethys.TethysState; @@ -83,6 +84,7 @@ import tethys.TethysState.StateType; import tethys.dbxml.DBXMLConnect; import tethys.dbxml.TethysException; import tethys.deployment.swing.DeploymentWizard; +import tethys.deployment.swing.EffortProblemDialog; import tethys.deployment.swing.RecordingGapDialog; import tethys.niluswraps.PDeployment; import tethys.output.TethysExportParams; @@ -98,16 +100,9 @@ import tethys.swing.DeploymentTableObserver; * @author dg50 * */ -public class DeploymentHandler implements TethysStateObserver, DeploymentTableObserver { +public class DeploymentHandler extends CollectionHandler implements TethysStateObserver, DeploymentTableObserver { - private TethysControl tethysControl; - - /** - * @return the tethysControl - */ - public TethysControl getTethysControl() { - return tethysControl; - } +// private TethysControl tethysControl; private EffortFunctions effortFunctions; @@ -119,8 +114,11 @@ public class DeploymentHandler implements TethysStateObserver, DeploymentTableOb private DeploymentExportOpts deploymentExportOptions = new DeploymentExportOpts(); + public static final String helpPoint = "utilities.tethys.docs.deployments"; + public DeploymentHandler(TethysControl tethysControl) { - super(); + + super(tethysControl, Collection.Deployments); this.tethysControl = tethysControl; @@ -368,8 +366,39 @@ public class DeploymentHandler implements TethysStateObserver, DeploymentTableOb public void createPamguardOverview() { deploymentOverview = effortFunctions.makeRecordingOverview(); + + checkDeploymentOverview(deploymentOverview); + updateProjectDeployments(); matchPamguard2Tethys(deploymentOverview, projectDeployments); + + tethysControl.sendStateUpdate(new TethysState(StateType.NEWPAMGUARDSELECTION)); + } + + /** + * Check the deployment overview for consistency.
+ * Take the raw audio information and the binary information and check they are similar. + * if not, ask the user what to do. + * @param deploymentOverview + */ + private void checkDeploymentOverview(DeploymentOverview overview) { + RecordingList rawList = overview.getRawDataList(); + RecordingList binList = overview.getBinaryDataList(); + if (rawList == null || binList == null) { + return; // nothing to do + } + double similarity = rawList.getSimilarity(binList); + if (similarity > 0.95) { + return; + } + /* + * if we get here, it seems like the two lists are very different, so + * show a dialog to ask the user what to do. + */ + RecordingList selList = EffortProblemDialog.showDialog(tethysControl.getGuiFrame(), overview); + if (selList != null) { + tethysControl.getTethysExportParams().setEffortSourceName(selList.getSourceName()); + } } /** @@ -381,22 +410,22 @@ public class DeploymentHandler implements TethysStateObserver, DeploymentTableOb if (exportOptions != null) { this.deploymentExportOptions = exportOptions; deploymentOverview = getDeploymentOverview(); - ArrayList allPeriods = deploymentOverview.getRecordingPeriods(); + RecordingList allPeriods = deploymentOverview.getMasterList(getTethysControl()); exportDeployments(allPeriods); } } /** * Export deployments docs. Playing with a couple of different ways of doing this. - * @param selectedDeployments + * @param allPeriods */ - public void exportDeployments(ArrayList selectedDeployments) { + public void exportDeployments(RecordingList allPeriods) { TethysReporter.getTethysReporter().clear(); if (deploymentExportOptions.separateDeployments) { - exportSeparateDeployments(selectedDeployments); + exportSeparateDeployments(allPeriods); } else { - exportOneDeploymnet(selectedDeployments); + exportOneDeploymnet(allPeriods); } TethysReporter.getTethysReporter().showReport(tethysControl.getGuiFrame(), true); } @@ -404,7 +433,7 @@ public class DeploymentHandler implements TethysStateObserver, DeploymentTableOb /** * Make one big deployment document with all the recording periods in it. */ - private void exportOneDeploymnet(ArrayList selectedDeployments) { + private void exportOneDeploymnet(RecordingList recordingList) { // do the lot, whatever ... Float sampleRate = null; AcquisitionControl daq = (AcquisitionControl) PamController.getInstance().findControlledUnit(AcquisitionControl.class, null); @@ -414,10 +443,9 @@ public class DeploymentHandler implements TethysStateObserver, DeploymentTableOb sampleRate = daqParams.sampleRate; } - selectedDeployments = getDeploymentOverview().getRecordingPeriods(); int freeId = getTethysControl().getDeploymentHandler().getFirstFreeDeploymentId(); - RecordingPeriod onePeriod = new RecordingPeriod(selectedDeployments.get(0).getRecordStart(), - selectedDeployments.get(selectedDeployments.size()-1).getRecordStop()); + RecordingPeriod onePeriod = new RecordingPeriod(recordingList.getStart(), + recordingList.getEnd()); TethysExportParams exportParams = tethysControl.getTethysExportParams(); String id = String.format("%s_%s", exportParams.getDatasetName(), "all"); Deployment deployment = createDeploymentDocument(freeId, onePeriod, id); @@ -425,7 +453,8 @@ public class DeploymentHandler implements TethysStateObserver, DeploymentTableOb Deployment globalMeta = getTethysControl().getGlobalDeplopymentData(); deployment.setCruise(globalMeta.getCruise()); deployment.setSite(globalMeta.getSite()); - if (selectedDeployments.size() > 1) { + ArrayList effortPeriods = recordingList.getEffortPeriods(); + if (recordingList.size() > 1) { // // now need to remove the sampling details - don't though, add invalid periods instead. // SamplingDetails samplingDetails = deployment.getSamplingDetails(); // samplingDetails.getChannel().clear(); @@ -440,9 +469,9 @@ public class DeploymentHandler implements TethysStateObserver, DeploymentTableOb deployment.setQualityAssurance(qa = new AcousticDataQAType()); } List qualityList = qa.getQuality(); - for (int i = 1; i < selectedDeployments.size(); i++) { - long end = selectedDeployments.get(i-1).getRecordStop(); - long start = selectedDeployments.get(i).getRecordStart(); + for (int i = 1; i < recordingList.size(); i++) { + long end = effortPeriods.get(i-1).getRecordStop(); + long start = effortPeriods.get(i).getRecordStart(); Quality q = new Quality(); q.setStart(TethysTimeFuncs.xmlGregCalFromMillis(end)); q.setEnd(TethysTimeFuncs.xmlGregCalFromMillis(start)); @@ -479,14 +508,15 @@ public class DeploymentHandler implements TethysStateObserver, DeploymentTableOb /** * Make a separate deployment document for every recording period. */ - private void exportSeparateDeployments(ArrayList selectedDeployments) { + private void exportSeparateDeployments(RecordingList recordingList) { int freeId = getTethysControl().getDeploymentHandler().getFirstFreeDeploymentId(); // fill in a few things from here Deployment globalMeta = getTethysControl().getGlobalDeplopymentData(); TethysExportParams exportParams = tethysControl.getTethysExportParams(); - for (int i = 0; i < selectedDeployments.size(); i++) { - RecordingPeriod recordPeriod = selectedDeployments.get(i); + ArrayList effortPeriods = recordingList.getEffortPeriods(); + for (int i = 0; i < recordingList.size(); i++) { + RecordingPeriod recordPeriod = effortPeriods.get(i); PDeployment exDeploymnet = recordPeriod.getMatchedTethysDeployment(); Deployment deployment = null; String id = String.format("%s_%d", exportParams.getDatasetName(), i); @@ -532,8 +562,9 @@ public class DeploymentHandler implements TethysStateObserver, DeploymentTableOb if (deployments == null || deploymentOverview == null) { return; } - ArrayList recordingPeriods = deploymentOverview.getRecordingPeriods(); - for (RecordingPeriod aPeriod : recordingPeriods) { + RecordingList recordingList = deploymentOverview.getMasterList(getTethysControl()); + ArrayList effortPeriods = recordingList.getEffortPeriods(); + for (RecordingPeriod aPeriod : effortPeriods) { PDeployment closestDeployment = findClosestDeployment(aPeriod, deployments); aPeriod.setMatchedTethysDeployment(closestDeployment); if (closestDeployment != null) { @@ -592,7 +623,8 @@ public class DeploymentHandler implements TethysStateObserver, DeploymentTableOb if (deploymentOverview == null) { return matched; } - for (RecordingPeriod period : deploymentOverview.getRecordingPeriods()) { + ArrayList effortPeriods = deploymentOverview.getMasterList(getTethysControl()).getEffortPeriods(); + for (RecordingPeriod period : effortPeriods) { PDeployment deployment = period.getMatchedTethysDeployment(); if (deployment != null) { if (matched.contains(deployment) == false) { @@ -1230,7 +1262,8 @@ public class DeploymentHandler implements TethysStateObserver, DeploymentTableOb } regimens.add(regimen); - DutyCycleInfo dutyCycleInf = deploymentOverview.getDutyCycleInfo(); + RecordingList recordingList = deploymentOverview.getMasterList(getTethysControl()); + DutyCycleInfo dutyCycleInf = recordingList.assessDutyCycle(); boolean isDS = dutyCycleInf != null && dutyCycleInf.isDutyCycled; if (isDS) { DutyCycle dutyCycle = new DutyCycle(); @@ -1307,4 +1340,9 @@ public class DeploymentHandler implements TethysStateObserver, DeploymentTableOb return deploymentExportOptions; } + @Override + public String getHelpPoint() { + return helpPoint; + } + } diff --git a/src/tethys/deployment/DeploymentOverview.java b/src/tethys/deployment/DeploymentOverview.java index f296df8e..3f39c653 100644 --- a/src/tethys/deployment/DeploymentOverview.java +++ b/src/tethys/deployment/DeploymentOverview.java @@ -1,16 +1,6 @@ package tethys.deployment; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.ListIterator; - -import Acquisition.AcquisitionControl; -import Acquisition.AcquisitionParameters; -import Acquisition.DaqStatusDataUnit; -import PamController.PamControlledUnit; -import PamController.PamController; -import PamguardMVC.PamDataBlock; +import tethys.TethysControl; /** * Class to give a general overview of all the effort in PAMGuard which will form the @@ -22,59 +12,83 @@ import PamguardMVC.PamDataBlock; */ public class DeploymentOverview { - private ArrayList recordingPeriods = new ArrayList<>(); + private RecordingList rawDataList; - private DutyCycleInfo dutyCycleInfo; + private RecordingList binaryDataList; + +// private DutyCycleInfo dutyCycleInfo; - public DeploymentOverview(DutyCycleInfo dutyCycleInfo) { - super(); - this.dutyCycleInfo = dutyCycleInfo; - } +// public DeploymentOverview(DutyCycleInfo dutyCycleInfo) { +// super(); +// this.dutyCycleInfo = dutyCycleInfo; +// } - public DeploymentOverview(DutyCycleInfo dutyCycleInfo, ArrayList tempPeriods) { - this.dutyCycleInfo = dutyCycleInfo; - this.recordingPeriods = tempPeriods; - } - - - public void addRecordingPeriod(long start, long stop) { - addRecordingPeriod(new RecordingPeriod(start, stop)); + public DeploymentOverview(DutyCycleInfo dutyCycleInfo, RecordingList rawDataList, RecordingList binaryDataList) { +// this.dutyCycleInfo = dutyCycleInfo; + this.rawDataList = rawDataList; + this.binaryDataList = binaryDataList; } - private void addRecordingPeriod(RecordingPeriod recordingPeriod) { - recordingPeriods.add(recordingPeriod); + /** + * @return the rawDataList + */ + public RecordingList getRawDataList() { + return rawDataList; } - public ArrayList getRecordingPeriods() { - return recordingPeriods; + /** + * @return the binaryDataList + */ + public RecordingList getBinaryDataList() { + return binaryDataList; } - public DutyCycleInfo getDutyCycleInfo() { - return dutyCycleInfo; +// /** +// * @return the dutyCycleInfo +// */ +// public DutyCycleInfo getDutyCycleInfo() { +// return dutyCycleInfo; +// } + + public RecordingList getMasterList(TethysControl tethysControl) { + return getMasterList(tethysControl.getTethysExportParams().getEffortSourceName()); + } + + public RecordingList getMasterList(String effortSourceName) { + if (effortSourceName == null) { + return getLongestList(); + } + if (binaryDataList != null & binaryDataList.getSourceName().equals(effortSourceName)) { + return binaryDataList; + } + if (rawDataList != null & rawDataList.getSourceName().equals(effortSourceName)) { + return rawDataList; + } + return getLongestList(); } /** - * Get the start time of the first recording + * Get the recording list with the greatest duration (start to end) + * not looking at coverage between those times. * @return */ - public Long getFirstStart() { - if (recordingPeriods.size() > 0) { - return recordingPeriods.get(0).getRecordStart(); + public RecordingList getLongestList() { + if (binaryDataList == null) { + return rawDataList; } - return null; - } - - /** - * Get the end time of the last recording - * @return - */ - public Long getLastEnd() { - if (recordingPeriods.size() > 0) { - return recordingPeriods.get(recordingPeriods.size()-1).getRecordStop(); + if (rawDataList == null) { + return binaryDataList; + } + long lRaw = rawDataList.duration(); + long lBin = binaryDataList.duration(); + + if (lRaw > lBin) { + return rawDataList; + } + else { + return binaryDataList; } - return null; } - - + } diff --git a/src/tethys/deployment/EffortFunctions.java b/src/tethys/deployment/EffortFunctions.java index 2fd8f383..b7e5b260 100644 --- a/src/tethys/deployment/EffortFunctions.java +++ b/src/tethys/deployment/EffortFunctions.java @@ -41,36 +41,40 @@ public class EffortFunctions { this.tethysControl = tethysControl; } - private DeploymentOverview createOverview(RecordingList tempPeriods) { - - DutyCycleInfo dutyCycleinfo = assessDutyCycle(tempPeriods); - if (dutyCycleinfo == null) { - return null; - } - - // if it's duty cycles, then we only want a single entry. - RecordingList deploymentPeriods; - if (dutyCycleinfo.isDutyCycled == false) { - deploymentPeriods = tempPeriods; - } - else { - deploymentPeriods = new RecordingList(); - deploymentPeriods.add(new RecordingPeriod(tempPeriods.get(0).getRecordStart(), tempPeriods.get(tempPeriods.size()-1).getRecordStop())); - } - /* - * do another sort of the deploymentPeriods. The start stops were in the order they went into the - * database in the hope that pairs were the right way round. Now check all data are/ - */ - Collections.sort(deploymentPeriods, new Comparator() { - @Override - public int compare(RecordingPeriod o1, RecordingPeriod o2) { - return (int) (o1.getRecordStart()-o2.getRecordStart()); - } - }); - - DeploymentOverview deploymentOverview = new DeploymentOverview(dutyCycleinfo, deploymentPeriods); - return deploymentOverview; - } +// private DeploymentOverview createOverview(RecordingList tempPeriods) { +// +// tempPeriods.sort(); +// +// DutyCycleInfo dutyCycleinfo = assessDutyCycle(tempPeriods); +// if (dutyCycleinfo == null) { +// return null; +// } +// +// +// // if it's duty cycles, then we only want a single entry. +// RecordingList deploymentPeriods; +// if (dutyCycleinfo.isDutyCycled == false) { +// deploymentPeriods = tempPeriods; +// } +// else { +// deploymentPeriods = new RecordingList(tempPeriods.getSourceName()); +// deploymentPeriods.add(new RecordingPeriod(tempPeriods.get(0).getRecordStart(), tempPeriods.get(tempPeriods.size()-1).getRecordStop())); +// } +// /* +// * do another sort of the deploymentPeriods. The start stops were in the order they went into the +// * database in the hope that pairs were the right way round. Now check all data are/ +// */ +// deploymentPeriods.sort(); +//// Collections.sort(deploymentPeriods, new Comparator() { +//// @Override +//// public int compare(RecordingPeriod o1, RecordingPeriod o2) { +//// return (int) (o1.getRecordStart()-o2.getRecordStart()); +//// } +//// }); +// +// DeploymentOverview deploymentOverview = new DeploymentOverview(dutyCycleinfo, deploymentPeriods); +// return deploymentOverview; +// } public DeploymentOverview makeRecordingOverview() { @@ -79,13 +83,18 @@ public class EffortFunctions { RecordingList binaryPeriods = listBinaryFiles(); - long l1 = listDuration(recordingPeriods); - long l2 = listDuration(binaryPeriods); - if (listDuration(binaryPeriods) > listDuration(recordingPeriods)) { - recordingPeriods = binaryPeriods; - } + // see what the similarity is between them +// double sim = recordingPeriods.getSimilarity(binaryPeriods); +// double testSim = recordingPeriods.getSimilarity(recordingPeriods); - DeploymentOverview deploymentOverview = createOverview(recordingPeriods); +// long l1 = listDuration(recordingPeriods); +// long l2 = listDuration(binaryPeriods); +// if (listDuration(binaryPeriods) > listDuration(recordingPeriods)) { +// recordingPeriods = binaryPeriods; +// } +// +// DeploymentOverview deploymentOverview = createOverview(recordingPeriods); + DeploymentOverview deploymentOverview = new DeploymentOverview(null, recordingPeriods, binaryPeriods); return deploymentOverview; } @@ -128,7 +137,8 @@ public class EffortFunctions { } } } - bestList = mergeRecordings(bestList); + DeploymentExportOpts exportOptions = tethysControl.getDeploymentHandler().getDeploymentExportOptions(); + bestList.mergeRecordingPeriods(exportOptions.maxRecordingGapSeconds*1000); return bestList; } @@ -138,7 +148,7 @@ public class EffortFunctions { if (mapPoints == null) { return null; } - RecordingList periods = new RecordingList(); + RecordingList periods = new RecordingList(dataMap.getDataMapName()); for (OfflineDataMapPoint mapPoint : mapPoints) { periods.add(new RecordingPeriod(mapPoint.getStartTime(), mapPoint.getEndTime())); } @@ -219,117 +229,60 @@ public class EffortFunctions { // PamCalendar.formatDBDateTime(aP.getRecordStop())); // } - tempPeriods = mergeRecordings(tempPeriods); - - return tempPeriods; - } - - /** - * Merge close recordings and discard ones that are too short. - * @param tempPeriods all recording periods, may be from consecutive files. - * @return merged list. - */ - private RecordingList mergeRecordings(RecordingList tempPeriods) { - // now go through those and merge into longer periods where there is no gap between files. - if (tempPeriods == null) { - return null; - } - + tempPeriods.sort(); DeploymentExportOpts exportOptions = tethysControl.getDeploymentHandler().getDeploymentExportOptions(); - - ListIterator iterator = tempPeriods.listIterator(); - RecordingPeriod prevPeriod = null; - while (iterator.hasNext()) { - RecordingPeriod nextPeriod = iterator.next(); - long nextDur = nextPeriod.getRecordStop()-nextPeriod.getRecordStart(); - if (nextDur == 0) { - continue; - } - if (prevPeriod != null) { - long gap = nextPeriod.getRecordStart() - prevPeriod.getRecordStop(); - long prevDur = prevPeriod.getRecordStop()-prevPeriod.getRecordStart(); - if (gap < exportOptions.maxRecordingGapSeconds*1000) { - // ignoring up to 3s gap or a sample error < 2%.Dunno if this is sensible or not. - prevPeriod.setRecordStop(nextPeriod.getRecordStop()); - iterator.remove(); - nextPeriod = prevPeriod; - } - } - prevPeriod = nextPeriod; - } - // now remove ones which are too short even after merging. - iterator = tempPeriods.listIterator(); - while (iterator.hasNext()) { - RecordingPeriod nextPeriod = iterator.next(); - long duration = nextPeriod.getDuration(); - if (duration < exportOptions.minRecordingLengthSeconds*1000L) { - iterator.remove(); - } - } + tempPeriods.mergeRecordingPeriods(exportOptions.maxRecordingGapSeconds*1000); return tempPeriods; } - /** - * Work out whether or not the data are evenly duty cycled by testing the - * distributions of on and off times. - * @param tempPeriods - * @return - */ - private DutyCycleInfo assessDutyCycle(RecordingList tempPeriods) { - if (tempPeriods == null) { - return null; - } - int n = tempPeriods.size(); - if (n < 2) { - return new DutyCycleInfo(false, 0,0,n); - } - double[] ons = new double[n-1]; // ignore the last one since it may be artificially shortened which is OK - double[] gaps = new double[n-1]; - for (int i = 0; i < n-1; i++) { - ons[i] = tempPeriods.get(i).getDuration()/1000.; - gaps[i] = (tempPeriods.get(i+1).getRecordStart()-tempPeriods.get(i).getRecordStop())/1000.; - } - /* now look at how consistent those values are - * But some data gets messed by small gaps, so want to - * remove outliers and concentrate on say 80% of the data. - */ - ons = getDistributionCentre(ons, 80); - gaps = getDistributionCentre(gaps, 80); - Arrays.sort(gaps); +// /** +// * Merge close recordings and discard ones that are too short. +// * @param tempPeriods all recording periods, may be from consecutive files. +// * @return merged list. +// */ +// private RecordingList mergeRecordings(RecordingList tempPeriods) { +// // now go through those and merge into longer periods where there is no gap between files. +// if (tempPeriods == null) { +// return null; +// } +// +// DeploymentExportOpts exportOptions = tethysControl.getDeploymentHandler().getDeploymentExportOptions(); +// +// ListIterator iterator = tempPeriods.listIterator(); +// RecordingPeriod prevPeriod = null; +// while (iterator.hasNext()) { +// RecordingPeriod nextPeriod = iterator.next(); +// long nextDur = nextPeriod.getRecordStop()-nextPeriod.getRecordStart(); +// if (nextDur == 0) { +// continue; +// } +// if (prevPeriod != null) { +// long gap = nextPeriod.getRecordStart() - prevPeriod.getRecordStop(); +// long prevDur = prevPeriod.getRecordStop()-prevPeriod.getRecordStart(); +// if (gap < exportOptions.maxRecordingGapSeconds*1000) { +// // ignoring up to 3s gap or a sample error < 2%.Dunno if this is sensible or not. +// prevPeriod.setRecordStop(nextPeriod.getRecordStop()); +// iterator.remove(); +// nextPeriod = prevPeriod; +// } +// } +// prevPeriod = nextPeriod; +// } +// // now remove ones which are too short even after merging. +// iterator = tempPeriods.listIterator(); +// while (iterator.hasNext()) { +// RecordingPeriod nextPeriod = iterator.next(); +// long duration = nextPeriod.getDuration(); +// if (duration < exportOptions.minRecordingLengthSeconds*1000L) { +// iterator.remove(); +// } +// } +// +// return tempPeriods; +// } - STD std = new STD(); - double onsMean = std.getMean(ons); - double onsSTD = std.getSTD(ons); - double gapsMean = std.getMean(gaps); - double gapsSTD = std.getSTD(gaps); - boolean dutyCycle = onsSTD/onsMean < .05 && gapsSTD/gapsMean < 0.05; - DutyCycleInfo cycleInfo = new DutyCycleInfo(dutyCycle, onsMean, gapsMean, tempPeriods.size()); - return cycleInfo; - } - - /** - * Get the central part of a distribution without any outliers so - * that we can get a better assessment of duty cycle. - * @param data unsorted distribution data. - * @param percent percentage to include (half this removed from top and bottom) - * @return - */ - private double[] getDistributionCentre(double[] data, double percent) { - if (data == null) { - return null; - } - Arrays.sort(data); - int nRem = (int) Math.round(data.length * (100-percent)/200); - int newLen = data.length-nRem*2; - double[] subdata = Arrays.copyOfRange(data, nRem, data.length-2*nRem); - if (subdata.length < 2) { - return data; - } - return subdata; - } - /** * Get data times from any other datamap, since this will generally match the acquisition anyway @@ -360,16 +313,17 @@ public class EffortFunctions { return null; } // get the times out of it. - RecordingList recPeriods = new RecordingList(); + RecordingList recPeriods = new RecordingList(bestMap.getDataMapName()); List mapPoints = bestMap.getMapPoints(); for (OfflineDataMapPoint mapPoint : mapPoints) { recPeriods.add(new RecordingPeriod(mapPoint.getStartTime(), mapPoint.getEndTime())); + recPeriods.add(mapPoint.getStartTime(), mapPoint.getEndTime()); } return recPeriods; } private RecordingList extractTimesFromStatus(ArrayList allStatusData) { - RecordingList tempPeriods = new RecordingList(); + RecordingList tempPeriods = new RecordingList("Data acquisition status"); long dataStart = Long.MAX_VALUE; long dataEnd = Long.MIN_VALUE; Long lastStart = null; diff --git a/src/tethys/deployment/RecordingList.java b/src/tethys/deployment/RecordingList.java index 34b41c1d..038c0e69 100644 --- a/src/tethys/deployment/RecordingList.java +++ b/src/tethys/deployment/RecordingList.java @@ -1,12 +1,43 @@ package tethys.deployment; +import java.io.Serializable; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; +import java.util.Iterator; -public class RecordingList extends ArrayList { +import PamUtils.PamCalendar; +import pamMaths.STD; + +/** + * Information about periods of effort that might come from either the raw data recordings or + * an analysis of binary data maps. + * @author dg50 + * + */ +public class RecordingList implements Serializable { private static final long serialVersionUID = 1L; + + private ArrayList effortPeriods = new ArrayList(); + + /** + * Name / source of this list. + */ + private String sourceName; + + /** + * @param sourceName + */ + public RecordingList(String sourceName) { + this.sourceName = sourceName; + } + + public RecordingList(String sourceName, ArrayList selectedDeployments) { + this.sourceName = sourceName; + this.effortPeriods = selectedDeployments; + } /** * Get the duration of the recording periods from start to end. @@ -21,27 +52,27 @@ public class RecordingList extends ArrayList { * @return */ public long getStart() { - if (size() == 0) { + if (effortPeriods.size() == 0) { return 0; } - return get(0).getRecordStart(); + return effortPeriods.get(0).getRecordStart(); } /** * get the end of the last in the list. */ public long getEnd() { - if (size() == 0) { + if (effortPeriods.size() == 0) { return 0; } - return get(size()-1).getRecordStop(); + return effortPeriods.get(effortPeriods.size()-1).getRecordStop(); } /** * Sort the list in ascending order. */ public void sort() { - Collections.sort(this, new Comparator() { + Collections.sort(effortPeriods, new Comparator() { @Override public int compare(RecordingPeriod o1, RecordingPeriod o2) { @@ -49,4 +80,172 @@ public class RecordingList extends ArrayList { } }); } + + /** + * Get the coverage as a fraction. This is the sum of the individual periods divided + * by the start to end times + * @return + */ + public double getCoverage() { + long cov = 0; + long durTot = 0; + if (effortPeriods.size() == 0) { + return 0; + } + Iterator it = effortPeriods.iterator(); + while (it.hasNext()) { + RecordingPeriod rp = it.next(); + cov += rp.getDuration(); + } + durTot = getEnd()-getStart(); + return (double) cov / (double) durTot; + } + + /** + * Merge recording periods, with a max gap between periods in milliseconds. + * @param maxGap + * @return the number of periods removed. + */ + public int mergeRecordingPeriods(long maxGap) { + if (effortPeriods.size() < 2) { + return 0; + } + Iterator it = effortPeriods.iterator(); + RecordingPeriod prev = it.next(); + int removed = 0; + while (it.hasNext()) { + RecordingPeriod curr = it.next(); + if (curr.getRecordStart() - prev.getRecordStop() <= maxGap) { + prev.setRecordStop(curr.getRecordStop()); + it.remove(); + removed++; + } + else { + prev = curr; + } + } + return removed; + } + + /** + * Work out whether or not the data are evenly duty cycled by testing the + * distributions of on and off times. + * @param tempPeriods + * @return + */ + public DutyCycleInfo assessDutyCycle() { + if (effortPeriods == null) { + return null; + } + int n = effortPeriods.size(); + if (n < 2) { + return new DutyCycleInfo(false, 0,0,n); + } + double[] ons = new double[n-1]; // ignore the last one since it may be artificially shortened which is OK + double[] gaps = new double[n-1]; + for (int i = 0; i < n-1; i++) { + ons[i] = effortPeriods.get(i).getDuration()/1000.; + gaps[i] = (effortPeriods.get(i+1).getRecordStart()-effortPeriods.get(i).getRecordStop())/1000.; + } + /* now look at how consistent those values are + * But some data gets messed by small gaps, so want to + * remove outliers and concentrate on say 80% of the data. + */ + ons = getDistributionCentre(ons, 80); + gaps = getDistributionCentre(gaps, 80); + Arrays.sort(gaps); + + STD std = new STD(); + double onsMean = std.getMean(ons); + double onsSTD = std.getSTD(ons); + double gapsMean = std.getMean(gaps); + double gapsSTD = std.getSTD(gaps); + boolean dutyCycle = onsSTD/onsMean < .05 && gapsSTD/gapsMean < 0.05; + DutyCycleInfo cycleInfo = new DutyCycleInfo(dutyCycle, onsMean, gapsMean, effortPeriods.size()); + return cycleInfo; + } + /** + * Get the central part of a distribution without any outliers so + * that we can get a better assessment of duty cycle. + * @param data unsorted distribution data. + * @param percent percentage to include (half this removed from top and bottom) + * @return + */ + private double[] getDistributionCentre(double[] data, double percent) { + if (data == null) { + return null; + } + Arrays.sort(data); + int nRem = (int) Math.round(data.length * (100-percent)/200); + int newLen = data.length-nRem*2; + double[] subdata = Arrays.copyOfRange(data, nRem, data.length-2*nRem); + if (subdata.length < 2) { + return data; + } + return subdata; + } + + /** + * @return the sourceName + */ + public String getSourceName() { + return sourceName; + } + + @Override + public String toString() { + if (effortPeriods.size() == 0) { + return "Empty recording list"; + } + String str = String.format("%s: %s to %s, %3.1f%% coverage", getSourceName(), + PamCalendar.formatDBDateTime(getStart()), + PamCalendar.formatDBDateTime(getEnd()), getCoverage()*100); + return str; + } + + /** + * Get similarity to another recording list. 1 = identical, 0 means not even overlapping. + * @param other other recording list. + * @return measure of similarity. + */ + public double getSimilarity(RecordingList other) { + double sim1 = (double) other.duration() / (double) this.duration(); + if (sim1 > 1) { + sim1 = 1./sim1; + } + long overlap = Math.min(other.getEnd(), this.getEnd()) - Math.max(other.getStart(), this.getStart()); + overlap = Math.max(0, overlap); + long longest = Math.max(other.duration(), this.duration()); + double sim2 = (double) overlap / (double) longest; + + return Math.min(sim1, sim2); + } + + /** + * Add a recording period to the list. + * @param recordingPeriod + */ + public void add(RecordingPeriod recordingPeriod) { + effortPeriods.add(recordingPeriod); + } + + /** + * Add a recording period to the list. + * @param startTime + * @param endTime + */ + public void add(long startTime, long endTime) { + add (new RecordingPeriod(startTime, endTime)); + } + + public int size() { + return effortPeriods.size(); + } + + /** + * @return the effortPeriods + */ + public ArrayList getEffortPeriods() { + return effortPeriods; + } } diff --git a/src/tethys/deployment/RecordingPeriod.java b/src/tethys/deployment/RecordingPeriod.java index ad010827..ae985347 100644 --- a/src/tethys/deployment/RecordingPeriod.java +++ b/src/tethys/deployment/RecordingPeriod.java @@ -1,5 +1,6 @@ package tethys.deployment; +import PamUtils.PamCalendar; import tethys.niluswraps.PDeployment; public class RecordingPeriod { @@ -71,5 +72,11 @@ public class RecordingPeriod { selected = !selected; return selected; } + + @Override + public String toString() { + return String.format("%s to %s, %s", PamCalendar.formatDBDateTime(recordStart), + PamCalendar.formatDBDateTime(recordStop), PamCalendar.formatDuration(getDuration())); + } } diff --git a/src/tethys/deployment/swing/EffortProblemDialog.java b/src/tethys/deployment/swing/EffortProblemDialog.java new file mode 100644 index 00000000..5fafa71b --- /dev/null +++ b/src/tethys/deployment/swing/EffortProblemDialog.java @@ -0,0 +1,163 @@ +package tethys.deployment.swing; + +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.GridLayout; +import java.awt.Window; + +import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.border.TitledBorder; + +import PamUtils.PamCalendar; +import PamView.dialog.PamDialog; +import PamView.dialog.PamGridBagContraints; +import tethys.deployment.DeploymentOverview; +import tethys.deployment.RecordingList; + +/** + * Handle problems when binary and raw effort don't add up + * @author dg50 + * + */ +public class EffortProblemDialog extends PamDialog { + + private JRadioButton useRaw, useBinary, useNeither; + + private JLabel generalInfo; + + private InfoSet[] infoSets = new InfoSet[2]; + + private RecordingList chosenList; + + private DeploymentOverview deploymentOverview; + + private static EffortProblemDialog singleInstance; + + private static final String[] setNames = {"Raw data", "Binary data"}; + + private EffortProblemDialog(Window parentFrame) { + super(parentFrame, "Deployment Effort", false); + JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BorderLayout()); + mainPanel.setBorder(new TitledBorder("Effort information")); + String info = "There is a mismatch between the time period covered by the raw
" + + "data recordings and the time covered in the binary data.
" + + "Select the one you wish to use, or Cancel and sort out your data
" + + "prior to restarting the Tethys export process"; + generalInfo = new JLabel(info); +// generalInfo.setBorder(new TitledBorder("General")); + mainPanel.add(generalInfo, BorderLayout.NORTH); + JPanel botPanel = new JPanel(new GridLayout(2, 1)); + mainPanel.add(botPanel, BorderLayout.CENTER); + ButtonGroup bg = new ButtonGroup(); + for (int i = 0; i < 2; i++) { + GridBagConstraints c = new PamGridBagContraints(); + JPanel subPanel = new JPanel(new GridBagLayout()); + botPanel.add(subPanel); + infoSets[i] = new InfoSet(setNames[i]); + c.gridwidth = 2; + subPanel.add(infoSets[i].name, c); + c.gridx += c.gridwidth; + subPanel.add(infoSets[i].select, c); + c.gridx = 0; + c.gridy++; + c.gridwidth = 1; + subPanel.add(new JLabel("Start: ", JLabel.RIGHT), c); + c.gridx++; + subPanel.add(infoSets[i].start, c); + c.gridx++; + subPanel.add(new JLabel("End: ", JLabel.RIGHT), c); + c.gridx++; + subPanel.add(infoSets[i].end, c); + c.gridy++; + c.gridx = 0; + subPanel.add(new JLabel("Duration: ", JLabel.RIGHT), c); + c.gridx++; + subPanel.add(infoSets[i].duration, c); + c.gridx++; + subPanel.add(new JLabel("Coverage: ", JLabel.RIGHT), c); + c.gridx++; + subPanel.add(infoSets[i].occupancy, c); + + bg.add(infoSets[i].select); + } + + setDialogComponent(mainPanel); + setResizable(true); + } + + public static RecordingList showDialog(Window parentFrame, DeploymentOverview deploymentOverview) { + singleInstance = new EffortProblemDialog(parentFrame); + singleInstance.setData(deploymentOverview); + singleInstance.setVisible(true); + return singleInstance.chosenList; + } + + private void setData(DeploymentOverview deploymentOverview) { + this.deploymentOverview = deploymentOverview; + RecordingList rl; + for (int i = 0; i < 2; i++) { + if (i == 0) { + rl = deploymentOverview.getRawDataList(); + } + else { + rl = deploymentOverview.getBinaryDataList(); + } + infoSets[i].start.setText(PamCalendar.formatDBDateTime(rl.getStart())); + infoSets[i].end.setText(PamCalendar.formatDBDateTime(rl.getEnd())); + infoSets[i].duration.setText(PamCalendar.formatDuration(rl.duration())); + infoSets[i].occupancy.setText(String.format("%3.0f%%", rl.getCoverage()*100.)); + } + invalidate(); + pack(); + } + + @Override + public boolean getParams() { + if (infoSets[0].select.isSelected()) { + chosenList = deploymentOverview.getRawDataList(); + return true; + } + if (infoSets[1].select.isSelected()) { + chosenList = deploymentOverview.getBinaryDataList(); + return true; + } + return false; + } + + @Override + public void cancelButtonPressed() { + // TODO Auto-generated method stub + + } + + @Override + public void restoreDefaultSettings() { + // TODO Auto-generated method stub + + } + + private class InfoSet { + JLabel name, start, end, duration, occupancy; + JCheckBox select; + /** + * + */ + public InfoSet(String name) { + super(); + this.name = new JLabel(name); + this.start = new JLabel(" "); + this.end = new JLabel(" "); + this.select = new JCheckBox("Select " + name); + duration = new JLabel(" "); + occupancy = new JLabel(" "); + } + } + +} diff --git a/src/tethys/deployment/swing/ProjectInformationPanel.java b/src/tethys/deployment/swing/ProjectInformationPanel.java index 1ced05b1..12a88a9a 100644 --- a/src/tethys/deployment/swing/ProjectInformationPanel.java +++ b/src/tethys/deployment/swing/ProjectInformationPanel.java @@ -23,6 +23,7 @@ import nilus.Deployment; import tethys.TethysControl; import tethys.swing.NewProjectDialog; import tethys.swing.SelectProjectDialog; +import tethys.tooltips.TethysTips; /** * Panel for entering project information @@ -109,6 +110,12 @@ public class ProjectInformationPanel { }); } + project.setToolTipText(TethysTips.findTip(Deployment.class, "Project")); + cruise.setToolTipText(TethysTips.findTip(Deployment.class, "Cruise")); + region.setToolTipText(TethysTips.findTip(Deployment.class, "Region")); + site.setToolTipText(TethysTips.findTip(Deployment.class, "Site")); + + } /** diff --git a/src/tethys/deployment/swing/RecordingGapDialog.java b/src/tethys/deployment/swing/RecordingGapDialog.java index 399eb3bc..9915a0e4 100644 --- a/src/tethys/deployment/swing/RecordingGapDialog.java +++ b/src/tethys/deployment/swing/RecordingGapDialog.java @@ -36,7 +36,7 @@ public class RecordingGapDialog extends PamDialog { c.gridx++; mainPanel.add(new JLabel(" seconds", JLabel.RIGHT), c); - maxGap.setToolTipText("Maximum gap between recording periods. Periods with a gap less than this will be counted as one"); + maxGap.setToolTipText("Maximum gap between recording periods. Sequential periods with a gap less than this will be counted as one"); minLength.setToolTipText("Minimum recording length. Recording sections shorter than this will be ignored"); setDialogComponent(mainPanel); @@ -80,6 +80,7 @@ public class RecordingGapDialog extends PamDialog { @Override public void restoreDefaultSettings() { DeploymentExportOpts defaults = new DeploymentExportOpts(); + setParams(defaults); } } diff --git a/src/tethys/detection/BinnedGranularityHandler.java b/src/tethys/detection/BinnedGranularityHandler.java index db984b27..13b92d58 100644 --- a/src/tethys/detection/BinnedGranularityHandler.java +++ b/src/tethys/detection/BinnedGranularityHandler.java @@ -6,11 +6,16 @@ import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.Map.Entry; + +import javax.xml.datatype.DatatypeConstants; +import javax.xml.datatype.XMLGregorianCalendar; + import java.util.Set; import PamguardMVC.PamDataBlock; import PamguardMVC.PamDataUnit; import nilus.Detection; +import nilus.Detections; import nilus.SpeciesIDType; import tethys.TethysControl; import tethys.TethysTimeFuncs; @@ -54,6 +59,8 @@ public class BinnedGranularityHandler extends GranularityHandler { public void prepare(long timeMillis) { // long binStart = DetectionsHandler.roundDownBinStart(timeMillis, binDurationMillis); // startBin(binStart); +// startBin(timeMillis); + currentDetections.clear(); } // private void startBin(long timeMillis) { @@ -169,4 +176,9 @@ public class BinnedGranularityHandler extends GranularityHandler { return closeBins(timeMillis); } + @Override + protected boolean autoEffortFix(Detections detections, Detection det) { + return contractDetection(detections, det); + } + } diff --git a/src/tethys/detection/CallGranularityHandler.java b/src/tethys/detection/CallGranularityHandler.java index 4ff9a888..03397596 100644 --- a/src/tethys/detection/CallGranularityHandler.java +++ b/src/tethys/detection/CallGranularityHandler.java @@ -3,6 +3,7 @@ package tethys.detection; import PamguardMVC.PamDataBlock; import PamguardMVC.PamDataUnit; import nilus.Detection; +import nilus.Detections; import tethys.TethysControl; import tethys.output.StreamExportParams; import tethys.output.TethysExportParams; @@ -37,4 +38,9 @@ public class CallGranularityHandler extends GranularityHandler { return null; } + @Override + protected boolean autoEffortFix(Detections detections, Detection det) { + return expandEffort(detections, det); + } + } diff --git a/src/tethys/detection/DetectionExportProgress.java b/src/tethys/detection/DetectionExportProgress.java index abfa74b8..24a982a5 100644 --- a/src/tethys/detection/DetectionExportProgress.java +++ b/src/tethys/detection/DetectionExportProgress.java @@ -6,10 +6,11 @@ import tethys.niluswraps.PDeployment; public class DetectionExportProgress { public static final int STATE_GATHERING = 1; - public static final int STATE_CANCELED = 2; - public static final int STATE_COMPLETE = 3; - public static final int STATE_WRITING = 4; - public static final int STATE_COUNTING = 5; + public static final int STATE_COUNTING = 2; + public static final int STATE_WRITING = 3; + public static final int STATE_CANCELED = 4; + public static final int STATE_COMPLETE = 5; + public PDeployment currentDeployment; public Detections currentDetections; public long lastUnitTime; @@ -17,12 +18,18 @@ public class DetectionExportProgress { public int exportCount; public int skipCount; public int state; + public int totalDeployments, deploymentsDone; + public int nMapPoints; + public int doneMapPoints; - public DetectionExportProgress(PDeployment currentDeployment, Detections currentDetections, long lastUnitTime, + public DetectionExportProgress(PDeployment currentDeployment, Detections currentDetections, int nMapPoints, int doneMapPoints, + long lastUnitTime, long totalUnits, int exportCount, int skipCount, int state) { super(); this.currentDeployment = currentDeployment; this.currentDetections = currentDetections; + this.nMapPoints = nMapPoints; + this.doneMapPoints = doneMapPoints; this.lastUnitTime = lastUnitTime; this.totalUnits = totalUnits; this.exportCount = exportCount; diff --git a/src/tethys/detection/DetectionsHandler.java b/src/tethys/detection/DetectionsHandler.java index f797c8ee..cd995bbb 100644 --- a/src/tethys/detection/DetectionsHandler.java +++ b/src/tethys/detection/DetectionsHandler.java @@ -1,9 +1,13 @@ package tethys.detection; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.ListIterator; import javax.swing.SwingWorker; +import javax.xml.datatype.DatatypeConstants; +import javax.xml.datatype.XMLGregorianCalendar; import PamController.PamControlledUnit; import PamController.PamController; @@ -32,6 +36,7 @@ import nilus.Detections; import nilus.GranularityEnumType; import nilus.Helper; import tethys.Collection; +import tethys.CollectionHandler; import tethys.TethysControl; import tethys.TethysTimeFuncs; import tethys.dbxml.DBXMLConnect; @@ -39,7 +44,6 @@ import tethys.dbxml.TethysException; import tethys.deployment.DeploymentHandler; import tethys.niluswraps.PDeployment; import tethys.niluswraps.PDetections; -import tethys.output.DatablockSynchInfo; import tethys.output.StreamExportParams; import tethys.output.TethysExportParams; import tethys.pamdata.TethysDataProvider; @@ -54,9 +58,7 @@ import tethys.swing.export.DetectionsExportWizard; * @author dg50 * */ -public class DetectionsHandler { - - private TethysControl tethysControl; +public class DetectionsHandler extends CollectionHandler { public int uniqueDetectionsId=1; public int uniqueDetectionId; @@ -65,12 +67,14 @@ public class DetectionsHandler { private ExportWorker exportWorker; + public static final String helpPoint = "utilities.tethys.docs.detect_localize"; + /** * * @param tethysControl */ public DetectionsHandler(TethysControl tethysControl) { - super(); + super(tethysControl, Collection.Detections); this.tethysControl = tethysControl; } @@ -296,9 +300,14 @@ public class DetectionsHandler { viewerLoadPolicy = ViewerLoadPolicy.LOAD_UTCNORMAL; } GranularityHandler granularityHandler = GranularityHandler.getHandler(streamExportParams.granularity, tethysControl, dataBlock, exportParams, streamExportParams); + int totalMaps = 0; + int totalMappedPoints = 0; + int totalLoadedDatas = 0; + int totalMapPoints = dataMap.getNumMapPoints(); + int doneMapPoints = 0; for (PDeployment deployment : deployments) { int documentCount = 0; - prog = new DetectionExportProgress(deployment, null, + prog = new DetectionExportProgress(deployment, null, totalMapPoints, doneMapPoints, lastUnitTime, totalCount, exportCount, skipCount, DetectionExportProgress.STATE_COUNTING); exportObserver.update(prog); granularityHandler.prepare(deployment.getAudioStart()); @@ -308,9 +317,10 @@ public class DetectionsHandler { for (OfflineDataMapPoint mapPoint : mapPoints) { if (!activeExport) { - prog = new DetectionExportProgress(deployment, null, + prog = new DetectionExportProgress(deployment, null,totalMapPoints, doneMapPoints, lastUnitTime, totalCount, exportCount, skipCount, DetectionExportProgress.STATE_CANCELED); exportObserver.update(prog); + break; } if (mapPoint.getEndTime() < deployment.getAudioStart()) { @@ -319,10 +329,13 @@ public class DetectionsHandler { if (mapPoint.getStartTime() >= deployment.getAudioEnd()) { break; } + totalMaps ++; + totalMappedPoints += mapPoint.getNDatas(); dataBlock.loadViewerData(mapPoint.getStartTime(), mapPoint.getEndTime(), null); ArrayList dataCopy = dataBlock.getDataCopy(deployment.getAudioStart(), deployment.getAudioEnd(), true, dataSelector); -// System.out.printf("%d loaded from %s to %s %d kept\n", dataBlock.getUnitsCount(), PamCalendar.formatDateTime(mapPoint.getStartTime()), -// PamCalendar.formatDateTime(mapPoint.getEndTime()), dataCopy.size()); + totalLoadedDatas += dataCopy.size(); + System.out.printf("%d loaded from %s to %s %d kept\n", dataBlock.getUnitsCount(), PamCalendar.formatDateTime(mapPoint.getStartTime()), + PamCalendar.formatDateTime(mapPoint.getEndTime()), dataCopy.size()); skipCount += dataBlock.getUnitsCount() - dataCopy.size(); for (PamDataUnit dataUnit : dataCopy) { /* @@ -334,7 +347,7 @@ public class DetectionsHandler { documentCount+=dets.length; if (exportCount % 100 == 0) { - prog = new DetectionExportProgress(deployment, null, + prog = new DetectionExportProgress(deployment, null,totalMapPoints, doneMapPoints, lastUnitTime, totalCount, exportCount, skipCount, DetectionExportProgress.STATE_COUNTING); exportObserver.update(prog); } @@ -345,15 +358,17 @@ public class DetectionsHandler { // onEffort.getDetection().add(det); lastUnitTime = dataUnit.getTimeMilliseconds(); } - - prog = new DetectionExportProgress(deployment, null, + doneMapPoints++; + prog = new DetectionExportProgress(deployment, null,totalMapPoints, doneMapPoints, lastUnitTime, totalCount, exportCount, skipCount, DetectionExportProgress.STATE_COUNTING); exportObserver.update(prog); if (viewerLoadPolicy == ViewerLoadPolicy.LOAD_ALWAYS_EVERYTHING) { break; } - + if (!activeExport) { + return 0; + } } Detection dets[] = granularityHandler.cleanup(deployment.getAudioEnd()); if (dets != null) { @@ -397,28 +412,33 @@ public class DetectionsHandler { if (viewerLoadPolicy == null) { viewerLoadPolicy = ViewerLoadPolicy.LOAD_UTCNORMAL; } + int totalMapPoints = dataMap.getNumMapPoints(); + int doneMapPoints = 0; GranularityHandler granularityHandler = GranularityHandler.getHandler(streamExportParams.granularity, tethysControl, dataBlock, exportParams, streamExportParams); for (PDeployment deployment : deployments) { int documentCount = 0; - prog = new DetectionExportProgress(deployment, null, + prog = new DetectionExportProgress(deployment, null,totalMapPoints, doneMapPoints, lastUnitTime, totalCount, exportCount, skipCount, DetectionExportProgress.STATE_COUNTING); exportObserver.update(prog); granularityHandler.prepare(deployment.getAudioStart()); - if (currentDetections == null) { - currentDetections = startDetectionsDocument(deployment, dataBlock, streamExportParams); - currentDetections.getEffort().setStart(TethysTimeFuncs.xmlGregCalFromMillis(deployment.getAudioStart())); - } // export everything in that deployment. // need to loop through all map points in this interval. List mapPoints = dataMap.getMapPoints(); for (OfflineDataMapPoint mapPoint : mapPoints) { if (!activeExport) { - prog = new DetectionExportProgress(deployment, currentDetections, + prog = new DetectionExportProgress(deployment, currentDetections,totalMapPoints, doneMapPoints, lastUnitTime, totalCount, exportCount, skipCount, DetectionExportProgress.STATE_CANCELED); exportObserver.update(prog); + break; } + if (currentDetections == null) { + // needed in inner loop in case doc gets written at 500000. + currentDetections = startDetectionsDocument(deployment, dataBlock, streamExportParams); + currentDetections.getEffort().setStart(TethysTimeFuncs.xmlGregCalFromMillis(deployment.getAudioStart())); + } + if (mapPoint.getEndTime() < deployment.getAudioStart()) { continue; } @@ -427,6 +447,7 @@ public class DetectionsHandler { } dataBlock.loadViewerData(mapPoint.getStartTime(), mapPoint.getEndTime(), null); ArrayList dataCopy = dataBlock.getDataCopy(deployment.getAudioStart(), deployment.getAudioEnd(), true, dataSelector); + Collections.sort(dataCopy); skipCount += dataBlock.getUnitsCount() - dataCopy.size(); DetectionGroup onEffort = currentDetections.getOnEffort(); for (PamDataUnit dataUnit : dataCopy) { @@ -442,24 +463,27 @@ public class DetectionsHandler { } } if (exportCount % 100 == 0) { - prog = new DetectionExportProgress(deployment, null, + prog = new DetectionExportProgress(deployment, currentDetections, totalMapPoints, doneMapPoints, lastUnitTime, totalCount, exportCount, skipCount, DetectionExportProgress.STATE_GATHERING); exportObserver.update(prog); } lastUnitTime = dataUnit.getTimeMilliseconds(); } - prog = new DetectionExportProgress(deployment, currentDetections, + doneMapPoints ++; + prog = new DetectionExportProgress(deployment, currentDetections,totalMapPoints, doneMapPoints, lastUnitTime, totalCount, exportCount, skipCount, DetectionExportProgress.STATE_GATHERING); exportObserver.update(prog); - if (documentCount > 500000 && mapPoint != dataMap.getLastMapPoint()) { - prog = new DetectionExportProgress(deployment, currentDetections, + if (documentCount > 50000000 && mapPoint != dataMap.getLastMapPoint()) { + prog = new DetectionExportProgress(deployment, currentDetections,totalMapPoints, doneMapPoints, lastUnitTime, totalCount, exportCount, skipCount, DetectionExportProgress.STATE_WRITING); exportObserver.update(prog); closeDetectionsDocument(currentDetections, mapPoint.getEndTime()); try { - dbxmlConnect.postAndLog(currentDetections); + if (checkDetectionsDocument(currentDetections, granularityHandler)) { + dbxmlConnect.postAndLog(currentDetections); + } } catch (TethysException e) { tethysControl.showException(e); } @@ -469,8 +493,14 @@ public class DetectionsHandler { if (viewerLoadPolicy == ViewerLoadPolicy.LOAD_ALWAYS_EVERYTHING) { break; } + if (!activeExport) { + break; + } + } + + if (!activeExport) { + return DetectionExportProgress.STATE_CANCELED; } - if (currentDetections != null) { Detection dets[] = granularityHandler.cleanup(deployment.getAudioEnd()); @@ -481,11 +511,13 @@ public class DetectionsHandler { currentDetections.getOnEffort().getDetection().add(dets[dd]); } } - prog = new DetectionExportProgress(deployment, currentDetections, + prog = new DetectionExportProgress(deployment, currentDetections,totalMapPoints, doneMapPoints, lastUnitTime, totalCount, exportCount, skipCount, DetectionExportProgress.STATE_WRITING); closeDetectionsDocument(currentDetections, deployment.getAudioEnd()); try { - dbxmlConnect.postAndLog(currentDetections); + if (checkDetectionsDocument(currentDetections, granularityHandler)) { + dbxmlConnect.postAndLog(currentDetections); + } } catch (TethysException e) { tethysControl.showException(e); } @@ -493,7 +525,7 @@ public class DetectionsHandler { } } - prog = new DetectionExportProgress(null, null, + prog = new DetectionExportProgress(null, null,totalMapPoints, totalMapPoints, lastUnitTime, totalCount, exportCount, skipCount, DetectionExportProgress.STATE_COMPLETE); exportObserver.update(prog); return DetectionExportProgress.STATE_COMPLETE; @@ -552,7 +584,7 @@ public class DetectionsHandler { supportSoft.setVersion(getSupportSoftwareVersion(dataBlock)); supSoft.add(supportSoft); detections.setAlgorithm(algorithm); - detections.setUserId("Unknown user"); + detections.setUserId("PAMGuard user"); detections.setEffort(getDetectorEffort(deployment, dataBlock, exportParams)); return detections; @@ -567,6 +599,50 @@ public class DetectionsHandler { private void closeDetectionsDocument(Detections detections, Long audioEnd) { detections.getEffort().setEnd(TethysTimeFuncs.xmlGregCalFromMillis(audioEnd)); } + + /** + * Run some checks on the Detections document prior to submission.
+ * Currently, is is just a check that the detections are within the effort times. + * @param detections Detections document + * @return false if there is an outstanding problem. + */ + private boolean checkDetectionsDocument(Detections detections, GranularityHandler granularityHandler) { + XMLGregorianCalendar effStart = detections.getEffort().getStart(); + XMLGregorianCalendar effEnd = detections.getEffort().getEnd(); + DetectionGroup dets = detections.getOnEffort(); + List detList = dets.getDetection(); + ListIterator detIt = detList.listIterator(); + while (detIt.hasNext()) { + Detection det = detIt.next(); + XMLGregorianCalendar detS = det.getStart(); + XMLGregorianCalendar detE = det.getEnd(); + if (effStart.compare(detS) == DatatypeConstants.GREATER) { + if (granularityHandler.autoEffortFix(detections, det)) { + continue; + } + String str = String.format("A Detection at %s starts before the document effort start at %s
" + + "Do you want to adjust the effort start time or abort export ?", detS, effStart); + int ans = WarnOnce.showNamedWarning("TETHYSDETNOTINEFFORT", tethysControl.getGuiFrame(), "Detection Document Warning", str, WarnOnce.OK_CANCEL_OPTION); + if (ans == WarnOnce.CANCEL_OPTION) { + return false; + } + detections.getEffort().setStart(detS); + } + if (effEnd.compare(detE) == DatatypeConstants.LESSER) { + if (granularityHandler.autoEffortFix(detections, det)) { + continue; + } + String str = String.format("A Detection at %s-%s ends
after the document effort end at %s
" + + "Do you want to adjust the effort end time or abort export ?", detS, detE, effStart); + int ans = WarnOnce.showNamedWarning("TETHYSDETNOTINEFFORT", tethysControl.getGuiFrame(), "Detection Document Warning", str, WarnOnce.OK_CANCEL_OPTION); + if (ans == WarnOnce.CANCEL_OPTION) { + return false; + } + detections.getEffort().setEnd(detE); + } + } + return true; + } /** * Worker thread for exporting detections. @@ -599,13 +675,16 @@ public class DetectionsHandler { protected Integer doInBackground() throws Exception { Integer ans = null; try { - int count = countDetections(dataBlock, exportParams, exportObserver); - String msg = String.format("Do you want to go ahead and output %d %s detections to Tethys?", - count, exportParams.granularity); - int doit = WarnOnce.showWarning("Tethys Detections Export", msg, WarnOnce.OK_CANCEL_OPTION); - if (doit == WarnOnce.OK_OPTION) { +// int count = countDetections(dataBlock, exportParams, exportObserver); +// if (activeExport == false) { +// return 0; +// } +// String msg = String.format("Do you want to go ahead and output %d %s detections to Tethys?", +// count, exportParams.granularity); +// int doit = WarnOnce.showWarning("Tethys Detections Export", msg, WarnOnce.OK_CANCEL_OPTION); +// if (doit == WarnOnce.OK_OPTION) { ans = exportDetections(dataBlock, exportParams, this); - } +// } } catch (Exception e) { e.printStackTrace(); @@ -616,7 +695,7 @@ public class DetectionsHandler { @Override protected void done() { // this. - DetectionExportProgress prog = new DetectionExportProgress(null, null, 0, 0, 0, 0, DetectionExportProgress.STATE_COMPLETE); + DetectionExportProgress prog = new DetectionExportProgress(null, null, 0, 0, 0, 0, 0, 0, DetectionExportProgress.STATE_COMPLETE); tethysControl.exportedDetections(dataBlock); exportObserver.update(prog); TethysReporter.getTethysReporter().showReport(tethysControl.getGuiFrame(), true); @@ -661,4 +740,10 @@ public class DetectionsHandler { DetectionsExportWizard.showDialog(tethysControl.getGuiFrame(), tethysControl, dataBlock); } + + + @Override + public String getHelpPoint() { + return helpPoint; + } } diff --git a/src/tethys/detection/EncounterGranularityHandler.java b/src/tethys/detection/EncounterGranularityHandler.java index 57dd8757..54e70db5 100644 --- a/src/tethys/detection/EncounterGranularityHandler.java +++ b/src/tethys/detection/EncounterGranularityHandler.java @@ -11,6 +11,7 @@ import java.util.Map.Entry; import PamguardMVC.PamDataBlock; import PamguardMVC.PamDataUnit; import nilus.Detection; +import nilus.Detections; import nilus.SpeciesIDType; import tethys.TethysControl; import tethys.TethysTimeFuncs; @@ -48,7 +49,7 @@ public class EncounterGranularityHandler extends GranularityHandler { @Override public void prepare(long timeMillis) { - + currentDetections.clear(); } @Override @@ -77,6 +78,7 @@ public class EncounterGranularityHandler extends GranularityHandler { currentDetections.put(groupName, det); } else { + // add to current detection. Set new end time and increment count det.setEnd(TethysTimeFuncs.xmlGregCalFromMillis(dataUnit.getEndTimeInMilliseconds())); int count = det.getCount().intValue() + 1; @@ -122,34 +124,17 @@ public class EncounterGranularityHandler extends GranularityHandler { } } - // private Detection[] checkCurrentEncounters(long timeMilliseconds) { - // if (currentDetections == null || currentDetections.size() == 0) { - // return null; - // } - // int nGood = 0; - // Detection[] newDetections = new Detection[currentDetections.size()]; - // Iterator detIt = currentDetections.iterator(); - // while (detIt.hasNext()) { - // Detection aDet = detIt.next(); - // Long detEnd = TethysTimeFuncs.millisFromGregorianXML(aDet.getEnd()); - // if (timeMilliseconds-detEnd > maxGapMillis) { - // detIt.remove(); - // newDetections[nGood++] = aDet; - // } - // } - // - // if (nGood == 0) { - // return null; - // } - // else { - // return Arrays.copyOf(newDetections, nGood); - // } - // } @Override public Detection[] cleanup(long timeMillis) { // get everything still on the go. - return checkCurrentEncounters(timeMillis + maxGapMillis); + return checkCurrentEncounters(timeMillis + maxGapMillis*10); } + @Override + protected boolean autoEffortFix(Detections detections, Detection det) { + return expandEffort(detections, det); + } + + } diff --git a/src/tethys/detection/GranularityHandler.java b/src/tethys/detection/GranularityHandler.java index d172405e..47ef87e3 100644 --- a/src/tethys/detection/GranularityHandler.java +++ b/src/tethys/detection/GranularityHandler.java @@ -1,8 +1,16 @@ package tethys.detection; +import java.util.List; +import java.util.ListIterator; + +import javax.xml.datatype.DatatypeConstants; +import javax.xml.datatype.XMLGregorianCalendar; + import PamguardMVC.PamDataBlock; import PamguardMVC.PamDataUnit; import nilus.Detection; +import nilus.DetectionGroup; +import nilus.Detections; import nilus.GranularityEnumType; import tethys.TethysControl; import tethys.output.StreamExportParams; @@ -118,4 +126,93 @@ public abstract class GranularityHandler { } return null; } + + /** + * Automatically fix mismatches between effort and detections. This will be called if a + * detection or part of a detection is outside of the start and end defined by the effort. If it's a + * small difference, i.e. if the detection at least overlaps the effort then it can be automatically + * fixed by truncating the detection (for binned types) or by a small extension to the effort (for encounter + * and call types). + * @param detections nilus Detections object + * @param det a single detection + * @return true if it was fixed automatically. False otherwise. + */ + protected abstract boolean autoEffortFix(Detections detections, Detection det); + + /** + * Check that the detection at least overlaps the effort period. + * @param detections nilus Detections object + * @param det a single detection + * @return true if the overlap + */ + protected boolean effortOverlap(Detections detections, Detection det) { + XMLGregorianCalendar effStart = detections.getEffort().getStart(); + XMLGregorianCalendar effEnd = detections.getEffort().getEnd(); + XMLGregorianCalendar detStart = det.getStart(); + XMLGregorianCalendar detEnd = det.getEnd(); + if (effStart.compare(detEnd) == DatatypeConstants.GREATER) { + return false; + } + if (effEnd.compare(detStart) == DatatypeConstants.LESSER) { + return false; + } + return true; + } + + /** + * Fix effort / detection problem but contracting the start / end times of the detection + * @param detections nilus Detections object + * @param det a single detection + * @return true if fixed automatically + */ + protected boolean contractDetection(Detections detections, Detection det) { + if (effortOverlap(detections, det) == false) { + return false; + } + // at least some overlap, so fix it. + // going to fix it my shortening the detection, and leave the effort alone. + XMLGregorianCalendar effStart = detections.getEffort().getStart(); + XMLGregorianCalendar effEnd = detections.getEffort().getEnd(); + XMLGregorianCalendar detStart = det.getStart(); + XMLGregorianCalendar detEnd = det.getEnd(); + + + if (effStart.compare(detStart) == DatatypeConstants.GREATER) { + System.out.printf("Fix Detections change detection start from %s to %s\n", detStart, effStart); + det.setStart(effStart); + } + if (effEnd.compare(detEnd) == DatatypeConstants.LESSER) { + System.out.printf("Fix Detections change detection end from %s to %s\n", detEnd, effEnd); + det.setEnd(effEnd); + } + return true; + } + + /** + * Fix effort / detection problem but expanding the start / end times of the effort + * @param detections nilus Detections object + * @param det a single detection + * @return true if fixed automatically + */ + protected boolean expandEffort(Detections detections, Detection det) { + if (effortOverlap(detections, det) == false) { + return false; + } + // at least some overlap, so fix it. + // going to fix it my shortening the detection, and leave the effort alone. + XMLGregorianCalendar effStart = detections.getEffort().getStart(); + XMLGregorianCalendar effEnd = detections.getEffort().getEnd(); + XMLGregorianCalendar detStart = det.getStart(); + XMLGregorianCalendar detEnd = det.getEnd(); + + if (effStart.compare(detStart) == DatatypeConstants.GREATER) { + System.out.printf("Fix Detections change effort start from %s to %s\n", effStart, detStart); + detections.getEffort().setStart(detStart); + } + if (effEnd.compare(detEnd) == DatatypeConstants.LESSER) { + System.out.printf("Fix Detections change effort end from %s to %s\n", effEnd, detEnd); + detections.getEffort().setEnd(detEnd); + } + return true; + } } diff --git a/src/tethys/localization/LocalizationHandler.java b/src/tethys/localization/LocalizationHandler.java new file mode 100644 index 00000000..69105b5e --- /dev/null +++ b/src/tethys/localization/LocalizationHandler.java @@ -0,0 +1,17 @@ +package tethys.localization; + +import nilus.CylindricalCoordinateType; +import nilus.LocalizationType; +import nilus.Localize.Effort.CoordinateReferenceSystem; + +public class LocalizationHandler { + + + public LocalizationType getLoc() { + LocalizationType lt = new LocalizationType(); + CylindricalCoordinateType cct = new CylindricalCoordinateType(); +// cct.set + CoordinateReferenceSystem cr; + return null; + } +} diff --git a/src/tethys/niluswraps/NilusChecker.java b/src/tethys/niluswraps/NilusChecker.java index 43a53e97..e9107540 100644 --- a/src/tethys/niluswraps/NilusChecker.java +++ b/src/tethys/niluswraps/NilusChecker.java @@ -56,7 +56,7 @@ public class NilusChecker { for (Field f : emptyFields) { msg += String.format("
Field %s in object %s", f.getName(), f.getDeclaringClass().getName()); } - msg += "

It is likely that this document will fail to write to the Tethys database."; + msg += "

It is possible that this document will fail to write to the Tethys database."; String tit = "Incomplete Tethys data"; WarnOnce.showWarning(owner, tit, msg, WarnOnce.WARNING_MESSAGE); return false; diff --git a/src/tethys/niluswraps/NilusSettingsWrapper.java b/src/tethys/niluswraps/NilusSettingsWrapper.java index 68fa9628..6cb3f0ce 100644 --- a/src/tethys/niluswraps/NilusSettingsWrapper.java +++ b/src/tethys/niluswraps/NilusSettingsWrapper.java @@ -129,7 +129,9 @@ public class NilusSettingsWrapper implements Serializable, Clo Document doc = builder.parse(new InputSource(new StringReader(xmlString))); return doc; } catch (Exception e) { - e.printStackTrace(); + System.out.println(e.getMessage()); + System.out.println("Nilus Settings wrapper - Error parsing string\n" + xmlString); +// e.printStackTrace(); } return null; } diff --git a/src/tethys/output/StreamExportParams.java b/src/tethys/output/StreamExportParams.java index aabbff2d..ca4b18ed 100644 --- a/src/tethys/output/StreamExportParams.java +++ b/src/tethys/output/StreamExportParams.java @@ -98,7 +98,11 @@ public class StreamExportParams implements Serializable { * @return */ public DescriptionType getNilusDetectionDescription() { - return getDetectionDescription().getDescription(); + WrappedDescriptionType desc = getDetectionDescription(); + if (desc == null) { + return null; + } + return desc.getDescription(); } } diff --git a/src/tethys/output/TethysExportParams.java b/src/tethys/output/TethysExportParams.java index 5eb129cb..a0ff683b 100644 --- a/src/tethys/output/TethysExportParams.java +++ b/src/tethys/output/TethysExportParams.java @@ -45,6 +45,8 @@ public class TethysExportParams implements Serializable, Cloneable{ private String datasetName; public boolean listDocsInPamguard; + + private String effortSourceName; /** @@ -121,6 +123,22 @@ public class TethysExportParams implements Serializable, Cloneable{ return streamParamsMap.get(longDataName); } + /** + * Source name for type of effort. + * @param sourceName + */ + public void setEffortSourceName(String sourceName) { + this.effortSourceName = sourceName; + } + + /** + * Source name for type of effort. + * @return the effortSourceName + */ + public String getEffortSourceName() { + return effortSourceName; + } + } diff --git a/src/tethys/pamdata/AutoTethysProvider.java b/src/tethys/pamdata/AutoTethysProvider.java index 1c31671e..365a4dd1 100644 --- a/src/tethys/pamdata/AutoTethysProvider.java +++ b/src/tethys/pamdata/AutoTethysProvider.java @@ -286,6 +286,7 @@ abstract public class AutoTethysProvider implements TethysDataProvider { detParams.setMaxFreqHz(freqs[1]); } double ampli = dataUnit.getAmplitudeDB(); + ampli = roundDecimalPlaces(ampli, 1); detParams.setReceivedLevelDB(ampli); // DataUnitBaseData basicData = dataUnit.getBasicData(); gotTonalContour(dataUnit, detParams); diff --git a/src/tethys/species/DataBlockSpeciesManager.java b/src/tethys/species/DataBlockSpeciesManager.java index 7817cf82..28c89a23 100644 --- a/src/tethys/species/DataBlockSpeciesManager.java +++ b/src/tethys/species/DataBlockSpeciesManager.java @@ -97,7 +97,10 @@ abstract public class DataBlockSpeciesManager { public SpeciesMapItem getSpeciesItem(T dataUnit) { String speciesString = getSpeciesCode(dataUnit); if (speciesString == null) { - return getDefaultDefaultSpecies(); + SpeciesMapItem def = getDefaultDefaultSpecies(); + if (def != null) { + speciesString = def.getPamguardName(); + } } DataBlockSpeciesMap speciesMap = getDatablockSpeciesMap(); if (speciesMap == null) { @@ -127,7 +130,26 @@ abstract public class DataBlockSpeciesManager { if (allCodes.size() == 0) { allCodes.add("Unknown"); } - return allCodes; + return makeUniqueList(allCodes); + } + + /** + * Make sure all entries in an array list are unique. + * @param list + * @return updated list. + */ + public ArrayList makeUniqueList(ArrayList list) { + if (list == null) { + return null; + } + ArrayList newList = new ArrayList(); + for (String aStr : list) { + if (newList.contains(aStr)) { + continue; + } + newList.add(aStr); + } + return newList; } public DataBlockSpeciesMap getDatablockSpeciesMap() { diff --git a/src/tethys/species/ITISFunctions.java b/src/tethys/species/ITISFunctions.java index 763c99f3..252e3bb8 100644 --- a/src/tethys/species/ITISFunctions.java +++ b/src/tethys/species/ITISFunctions.java @@ -70,7 +70,7 @@ public class ITISFunctions { // PAMGuardXMLPreview xmlPreview = new PAMGuardXMLPreview(null, "returned", qResult.queryResult) PamguardXMLWriter pamXMLWriter = PamguardXMLWriter.getXMLWriter(); String fDoc = pamXMLWriter.getAsString(doc, true); - System.out.println(fDoc); +// System.out.println(fDoc); String tsn = dbQueries.getElementData(docEl, "tsn"); if (tsn == null) { @@ -91,7 +91,46 @@ public class ITISFunctions { return new TethysITISResult(itisCode, taxunit, latin, vernacular); } + /** + * Search species codes. If the search term is a valid Integer number + * then it's assumed to be an ITIS code and the function should + * return a single map item. If it's non-integer, it's assumed to + * be a common or latin name search + * @param searchTerm + * @return array list of possible matches. + */ public ArrayList searchSpecies(String searchTerm) { + Integer intVal = null; + try { + intVal = Integer.valueOf(searchTerm); + } + catch (NumberFormatException e) { + intVal = null; + } + if (intVal != null) { + return searchCodes(intVal); + } + else { // assume name search + return searchNames(searchTerm); + } + } + + private ArrayList searchCodes(Integer intCode) { + ArrayList mapItems = new ArrayList(); + TethysITISResult result = getITISInformation(intCode); + if (result != null) { + mapItems.add(new SpeciesMapItem(intCode, "", "", result.getLatin(), result.getVernacular())); + } + return mapItems; + } + + /** + * Search common and latin names for partial matched of the search term + * and return an array list of all possible matches. + * @param searchTerm + * @return + */ + public ArrayList searchNames(String searchTerm) { ArrayList items = new ArrayList(); String xQ = "let $target := \"thespeciessearchterm\" \r\n" + "return\r\n" diff --git a/src/tethys/species/ITISTypes.java b/src/tethys/species/ITISTypes.java index 771b180d..f1503c2e 100644 --- a/src/tethys/species/ITISTypes.java +++ b/src/tethys/species/ITISTypes.java @@ -7,8 +7,8 @@ package tethys.species; */ public class ITISTypes { - public static final int OTHER = 0; - public static final int ANTHROPOGENIC = 1; + public static final int OTHER = -10; + public static final int ANTHROPOGENIC = 1758; public static final String getName(int code) { switch (code) { diff --git a/src/tethys/species/SpeciesMapItem.java b/src/tethys/species/SpeciesMapItem.java index f1595a7a..82edd9b4 100644 --- a/src/tethys/species/SpeciesMapItem.java +++ b/src/tethys/species/SpeciesMapItem.java @@ -41,6 +41,14 @@ public class SpeciesMapItem implements Serializable, Cloneable { */ private String callType; + /** + * + * @param itisCode + * @param callType + * @param pamguardName + * @param latinName + * @param commonName + */ public SpeciesMapItem(int itisCode, String callType, String pamguardName, String latinName, String commonName) { super(); this.itisCode = itisCode; @@ -50,6 +58,12 @@ public class SpeciesMapItem implements Serializable, Cloneable { this.commonName = commonName; } + /** + * + * @param itisCode + * @param callType + * @param pamguardName + */ public SpeciesMapItem(int itisCode, String callType, String pamguardName) { super(); this.itisCode = itisCode; diff --git a/src/tethys/species/swing/DataBlockSpeciesDialog.java b/src/tethys/species/swing/DataBlockSpeciesDialog.java index 3181493f..5c6ed347 100644 --- a/src/tethys/species/swing/DataBlockSpeciesDialog.java +++ b/src/tethys/species/swing/DataBlockSpeciesDialog.java @@ -49,16 +49,7 @@ public class DataBlockSpeciesDialog extends PamDialog { + "\"Other Phenomena\" (-10). " + "
When known, a call or sound type should " + "be specified (see help for more information)."; - nPanel.add(BorderLayout.CENTER, new JLabel(otherMsg , JLabel.LEFT)); -// JPanel nwBit = new JPanel(new FlowLayout()); -// JButton exportButton = new JButton("Export"); -// exportButton.addActionListener(SpeciesMapManager.getInstance().getExportAction(parentFrame)); -// nwBit.add(exportButton); -// JButton importButton = new JButton("Import"); -// importButton.addActionListener(SpeciesMapManager.getInstance().getImportAction(parentFrame)); -// nwBit.add(importButton); -// nPanel.add(BorderLayout.WEST, nwBit); - + nPanel.add(BorderLayout.CENTER, new JLabel(otherMsg , JLabel.LEFT)); mainPanel.add(BorderLayout.NORTH, nPanel); setDialogComponent(mainPanel); diff --git a/src/tethys/species/swing/SpeciesSearchDialog.java b/src/tethys/species/swing/SpeciesSearchDialog.java index 78d430e3..01dcfacb 100644 --- a/src/tethys/species/swing/SpeciesSearchDialog.java +++ b/src/tethys/species/swing/SpeciesSearchDialog.java @@ -104,11 +104,11 @@ public class SpeciesSearchDialog extends PamDialog { setResizable(true); setDialogComponent(mainPanel); } - public static SpeciesMapItem showDialog(Window parentFrame, TethysControl tethysControl) { + public static SpeciesMapItem showDialog(Window parentFrame, TethysControl tethysControl, Integer currentCode) { if (singleInstance == null) { singleInstance = new SpeciesSearchDialog(parentFrame, tethysControl); } - singleInstance.setParams(); + singleInstance.setParams(currentCode); singleInstance.setVisible(true); return singleInstance.selectedItem; } @@ -131,6 +131,9 @@ public class SpeciesSearchDialog extends PamDialog { public void setMapItems(ArrayList newMapItems) { this.speciesMapItems = newMapItems; + if (newMapItems != null && newMapItems.size() == 1) { + setSelectedItem(newMapItems.get(0)); + } tableModel.fireTableDataChanged(); } @@ -188,14 +191,20 @@ public class SpeciesSearchDialog extends PamDialog { } - private void setParams() { - searchText.setText(null); - clearResults(); + private void setParams(Integer currentCode) { + if (currentCode == null) { + searchText.setText(null); + clearResults(); + } + else { + searchText.setText(currentCode.toString()); + searchTethys(); + } } private void clearResults() { speciesMapItems = null; - selectedItem = null; + setSelectedItem(null); } @Override public boolean getParams() { @@ -216,6 +225,10 @@ public class SpeciesSearchDialog extends PamDialog { } + private void enableControls() { + getOkButton().setEnabled(selectedItem != null); + } + private class TableMouse extends MouseAdapter { @Override @@ -225,12 +238,18 @@ public class SpeciesSearchDialog extends PamDialog { } int selectedRow = resultTable.getSelectedRow(); if (selectedRow >= 0 && selectedRow < speciesMapItems.size()) { - selectedItem = speciesMapItems.get(selectedRow); + setSelectedItem(speciesMapItems.get(selectedRow)); } tableModel.fireTableDataChanged(); } } + + private void setSelectedItem(SpeciesMapItem selItem) { + this.selectedItem = selItem; + enableControls(); + } + private class DataModel extends AbstractTableModel { private String[] colNames = {"Select", "TSN", "Name", "Common Name"}; diff --git a/src/tethys/species/swing/SpeciesSubPanel.java b/src/tethys/species/swing/SpeciesSubPanel.java index f55919f4..4b25d71f 100644 --- a/src/tethys/species/swing/SpeciesSubPanel.java +++ b/src/tethys/species/swing/SpeciesSubPanel.java @@ -114,12 +114,12 @@ public class SpeciesSubPanel { } ITISFunctions itisFunctions = tethysControl.getItisFunctions(); String itisString = this.itisCode.getText(); - if (itisString == null || itisString.length() == 0) { +// if (itisString == null || itisString.length() == 0) { searchForCode(tethysControl, itisFunctions); - } - else { - getCodeInformation(tethysControl, itisFunctions, itisString); - } +// } +// else { +// getCodeInformation(tethysControl, itisFunctions, itisString); +// } // System.out.println(itisInfo); } @@ -144,7 +144,15 @@ public class SpeciesSubPanel { } private void searchForCode(TethysControl tethysControl, ITISFunctions itisFunctions) { - SpeciesMapItem speciesItem = SpeciesSearchDialog.showDialog(tethysControl.getGuiFrame(), tethysControl); + Integer currentCode = null; + try { + currentCode = Integer.valueOf(itisCode.getText()); + } + catch (NumberFormatException e) { + + } + + SpeciesMapItem speciesItem = SpeciesSearchDialog.showDialog(tethysControl.getGuiFrame(), tethysControl, currentCode); if (speciesItem != null) { itisCode.setText(String.format("%d", speciesItem.getItisCode())); latinName.setText(speciesItem.getLatinName()); diff --git a/src/tethys/swing/DatablockDetectionsPanel.java b/src/tethys/swing/DatablockDetectionsPanel.java index b5b75397..05f05507 100644 --- a/src/tethys/swing/DatablockDetectionsPanel.java +++ b/src/tethys/swing/DatablockDetectionsPanel.java @@ -5,6 +5,7 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.util.ArrayList; import java.util.List; import javax.swing.JComponent; @@ -19,6 +20,8 @@ import javax.swing.table.AbstractTableModel; import javax.swing.table.JTableHeader; import javax.xml.datatype.XMLGregorianCalendar; +import PamUtils.worker.PamWorkWrapper; +import PamUtils.worker.PamWorker; import PamView.PamGui; import PamView.dialog.warn.WarnOnce; import PamView.tables.SwingTableColumnWidths; @@ -42,7 +45,7 @@ import tethys.niluswraps.PDetections; * @author dg50 * */ -public class DatablockDetectionsPanel extends TethysGUIPanel implements StreamTableObserver { +public class DatablockDetectionsPanel extends TethysGUIPanel implements StreamTableObserver, PamWorkWrapper { private JPanel mainPanel; @@ -118,15 +121,38 @@ public class DatablockDetectionsPanel extends TethysGUIPanel implements StreamTa @Override public void selectDataBlock(PamDataBlock dataBlock) { + if (this.dataBlock == dataBlock) { + return; // stops lots of requerying, which matters when database is large. + } this.dataBlock = dataBlock; - dataBlockName.setText(dataBlock.getLongDataName()); - streamDetectionsSummary = getTethysControl().getDetectionsHandler().getStreamDetections(dataBlock); + if (dataBlock == null) { + dataBlockName.setText("Select data in panel on the left"); + return; + } + else { + dataBlockName.setText(dataBlock.getLongDataName()); + } + // need to re-thread this to stop user panicing that nothing is happening. + PamWorker w = new PamWorker(this, getTethysControl().getGuiFrame(), 0, "Searching database for " + dataBlock.getDataName()); + w.start(); + } + + @Override + public void taskFinished(String result) { tableModel.fireTableDataChanged(); } + @Override + public String runBackgroundTask(PamWorker pamWorker) { + streamDetectionsSummary = getTethysControl().getDetectionsHandler().getStreamDetections(dataBlock); + return null; + } + @Override public void updateState(TethysState tethysState) { if (dataBlock != null) { + PamDataBlock currBlock = dataBlock; + selectDataBlock(null); selectDataBlock(dataBlock); } } @@ -164,16 +190,9 @@ public class DatablockDetectionsPanel extends TethysGUIPanel implements StreamTa JPopupMenu popMenu = new JPopupMenu(); + JMenuItem menuItem; if (rows.length == 1) { - JMenuItem menuItem = new JMenuItem("Delete document " + pDets.detections.getId()); - menuItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - deleteDocument(pDets); - } - }); - popMenu.add(menuItem); - + menuItem = new JMenuItem("Display document " + pDets.detections.getId()); menuItem.addActionListener(new ActionListener() { @Override @@ -191,9 +210,19 @@ public class DatablockDetectionsPanel extends TethysGUIPanel implements StreamTa } }); popMenu.add(menuItem); + + popMenu.addSeparator(); + menuItem = new JMenuItem("Delete document " + pDets.detections.getId()); + menuItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + deleteDocument(pDets); + } + }); + popMenu.add(menuItem); } else if (rows.length > 0){ - JMenuItem menuItem = new JMenuItem("Delete multiple Detections documents"); + menuItem = new JMenuItem("Delete multiple Detections documents"); menuItem.addActionListener(new ActionListener() { @Override @@ -217,20 +246,50 @@ public class DatablockDetectionsPanel extends TethysGUIPanel implements StreamTa if (ans != WarnOnce.OK_OPTION) { return; } + + ArrayList toDelete = new ArrayList(); + for (int i = 0; i < rows.length; i++) { int row = rows[i]; PDetections pDets = detectionsForRow(row); if (pDets == null) { continue; } - try { - getTethysControl().getDbxmlConnect().deleteDocument(pDets.detections); - } catch (TethysException e) { - getTethysControl().showException(e); - } + toDelete.add(pDets.detections); } - getTethysControl().exportedDetections(dataBlock); - selectDataBlock(dataBlock); // force table update. + DeleteDocs dd = new DeleteDocs(toDelete); + PamWorker worker = new PamWorker(dd, getTethysControl().getGuiFrame(), 1, "Deleting Detections documents"); + worker.start(); + + } + + private class DeleteDocs implements PamWorkWrapper { + + private ArrayList toDelete; + + public DeleteDocs(ArrayList toDelete) { + this.toDelete = toDelete; + } + + @Override + public Integer runBackgroundTask(PamWorker pamWorker) { + for (Detections dets : toDelete) { + try { + + getTethysControl().getDbxmlConnect().deleteDocument(dets); + } catch (TethysException e) { + getTethysControl().showException(e); + } + } + return toDelete.size(); + } + + @Override + public void taskFinished(Integer result) { + getTethysControl().exportedDetections(dataBlock); + selectDataBlock(dataBlock); // force table update. + } + } protected void deleteDocument(PDetections pDets) { diff --git a/src/tethys/swing/DatablockSynchPanel.java b/src/tethys/swing/DatablockSynchPanel.java index e23e308f..54985d64 100644 --- a/src/tethys/swing/DatablockSynchPanel.java +++ b/src/tethys/swing/DatablockSynchPanel.java @@ -1,6 +1,7 @@ package tethys.swing; import java.awt.BorderLayout; +import java.awt.Color; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentEvent; @@ -13,6 +14,7 @@ import java.util.ArrayList; import javax.swing.JButton; import javax.swing.JComponent; +import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; @@ -36,9 +38,9 @@ import tethys.niluswraps.PDeployment; import tethys.output.DatablockSynchInfo; import tethys.species.DataBlockSpeciesManager; -public class DatablockSynchPanel extends TethysGUIPanel { +public class DatablockSynchPanel extends TethysExportPanel { - public JPanel mainPanel; +// public JPanel mainPanel; private JTable synchTable; @@ -48,39 +50,46 @@ public class DatablockSynchPanel extends TethysGUIPanel { private ArrayList tableObservers = new ArrayList<>(); - private JButton exportButton; +// private TippedButton exportButton; +// private JLabel exportWarning; + + public DatablockSynchPanel(TethysControl tethysControl) { - super(tethysControl); - mainPanel = new PamPanel(new BorderLayout()); + super(tethysControl, tethysControl.getDetectionsHandler(), false); +// mainPanel = new PamPanel(new BorderLayout()); + JPanel mainPanel = getMainPanel(); mainPanel.setBorder(new TitledBorder("PAMGuard data blocks")); synchTableModel = new SynchTableModel(); synchTable = new JTable(synchTableModel); new SwingTableColumnWidths(tethysControl.getUnitName()+"SynchTable", synchTable); JScrollPane scrollPane = new JScrollPane(synchTable); mainPanel.add(BorderLayout.CENTER, scrollPane); - PamPanel ctrlPanel = new PamPanel(new BorderLayout()); - exportButton = new JButton("Export ..."); - ctrlPanel.add(BorderLayout.WEST, exportButton); - mainPanel.add(BorderLayout.NORTH, ctrlPanel); +// PamPanel ctrlPanel = new PamPanel(new BorderLayout()); +// exportButton = new TippedButton("Export ...", "Export Detections document"); +// exportWarning = new JLabel(" "); +// exportWarning.setForeground(Color.RED); +// ctrlPanel.add(BorderLayout.WEST, exportButton); +// ctrlPanel.add(BorderLayout.CENTER, exportWarning); +// mainPanel.add(BorderLayout.NORTH, ctrlPanel); synchTable.addMouseListener(new MouseActions()); synchTable.addKeyListener(new KeyActions()); - exportButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - exportData(); - } - }); +// exportButton.addActionListener(new ActionListener() { +// @Override +// public void actionPerformed(ActionEvent e) { +// exportData(); +// } +// }); enableExportButton(); } - @Override - public JComponent getComponent() { - return mainPanel; - } +// @Override +// public JComponent getComponent() { +// return mainPanel; +// } private class KeyActions extends KeyAdapter { @Override @@ -141,13 +150,50 @@ public class DatablockSynchPanel extends TethysGUIPanel { } private void enableExportButton() { + if (!getTethysControl().isServerOk()) { + disableExport("Tethys Server not running"); + return; + } int[] rows = synchTable.getSelectedRows(); - boolean en = rows != null && rows.length == 1; ArrayList deployments = getTethysControl().getDeploymentHandler().getMatchedDeployments(); if (deployments == null || deployments.size() == 0) { - en = false; + disableExport("No Deployment document(s). Export Deployments prior to exporting Detections"); + return; } - exportButton.setEnabled(getTethysControl().isServerOk() & en); + boolean en = rows != null && rows.length == 1; + if (!en) { + disableExport("No PAMGuard datablock selected (click a row on the table below)"); + return; + } + + PamDataBlock dataBlock = dataBlockSynchInfo.get(rows[0]).getDataBlock(); + String mapError = checkSpeciesManager(dataBlock); + if (mapError != null) { + disableExport("Unable to export due to species map error: " + mapError + ". Right click table row to edit species list"); + return; + } + + enableExport(true); + } + +// public void disableExport(String reason) { +// if (reason == null) { +// exportButton.setEnabled(true); +// exportWarning.setText(null); +// } +// else { +// exportButton.disable(reason); +// exportWarning.setText(" " + reason); +// } +// } + + private String checkSpeciesManager(PamDataBlock dataBlock) { + DataBlockSpeciesManager spManager = dataBlock.getDatablockSpeciesManager(); + if (spManager == null) { + return "No species manager"; + } + String error = spManager.checkSpeciesMapError(); + return error; } public void showPopup(MouseEvent e, int row) { @@ -259,4 +305,15 @@ public class DatablockSynchPanel extends TethysGUIPanel { } } + + @Override + protected void exportButtonPressed(ActionEvent e) { + exportData(); + } + + @Override + protected void optionsButtonPressed(ActionEvent e) { + // TODO Auto-generated method stub + + } } diff --git a/src/tethys/swing/DeploymentExportPanel.java b/src/tethys/swing/DeploymentExportPanel.java index 4147cb38..fd299636 100644 --- a/src/tethys/swing/DeploymentExportPanel.java +++ b/src/tethys/swing/DeploymentExportPanel.java @@ -34,6 +34,7 @@ import tethys.TethysState; import tethys.TethysState.StateType; import tethys.dbxml.DBXMLConnect; import tethys.deployment.DeploymentHandler; +import tethys.deployment.RecordingList; import tethys.deployment.RecordingPeriod; import tethys.niluswraps.PDeployment; @@ -241,7 +242,9 @@ public class DeploymentExportPanel extends TethysGUIPanel implements DeploymentT if (selectedDeployments == null || selectedDeployments.size() == 0) { return; }; - getTethysControl().getDeploymentHandler().exportDeployments(selectedDeployments); + // need to turn that list back into a RecordingList object. + RecordingList tempList = new RecordingList("eport list", selectedDeployments); + getTethysControl().getDeploymentHandler().exportDeployments(tempList); } diff --git a/src/tethys/swing/DeploymentsPanel.java b/src/tethys/swing/DeploymentsPanel.java index 7ebb6dec..4dc277f6 100644 --- a/src/tethys/swing/DeploymentsPanel.java +++ b/src/tethys/swing/DeploymentsPanel.java @@ -16,81 +16,38 @@ import javax.swing.border.TitledBorder; import PamView.panel.PamPanel; import tethys.TethysControl; import tethys.TethysState; +import tethys.calibration.CalibrationHandler; import tethys.deployment.DeploymentHandler; +import tethys.deployment.RecordingList; import tethys.deployment.RecordingPeriod; -public class DeploymentsPanel extends TethysGUIPanel implements DeploymentTableObserver { +public class DeploymentsPanel extends TethysExportPanel implements DeploymentTableObserver { - private JPanel mainPanel; +// private JPanel mainPanel; private PAMGuardDeploymentsTable pamDeploymentsTable; private DeploymentExportPanel exportPanel; - private JButton exportButton, optionsButton; -// private TethysDeploymentsTable tethysDeploymentsTable; - private JLabel exportWarning; + private JLabel effortName; public DeploymentsPanel(TethysControl tethysControl) { - super(tethysControl); + super(tethysControl, tethysControl.getDeploymentHandler(), true); DeploymentHandler deploymentHandler = tethysControl.getDeploymentHandler(); pamDeploymentsTable = new PAMGuardDeploymentsTable(tethysControl); exportPanel = new DeploymentExportPanel(tethysControl, pamDeploymentsTable); pamDeploymentsTable.addObserver(exportPanel); -// tethysDeploymentsTable = new TethysDeploymentsTable(tethysControl); - mainPanel = new PamPanel(new BorderLayout()); + + JPanel mainPanel = getMainPanel(); mainPanel.setBorder(new TitledBorder("Recording periods and deployment information")); pamDeploymentsTable.addObserver(this); pamDeploymentsTable.addObserver(deploymentHandler); -// JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); -// splitPane.add(pamDeploymentsTable.getComponent()); -// splitPane.add(tethysDeploymentsTable.getComponent()); -// mainPanel.add(splitPane,BorderLayout.CENTER); -// SwingUtilities.invokeLater(new Runnable() { -// -// @Override -// public void run() { -// splitPane.setDividerLocation(0.6); -// } -// }); - JPanel ctrlPanel = new PamPanel(new BorderLayout()); - JPanel ctrlButtons = new JPanel(); - ctrlButtons.setLayout(new BoxLayout(ctrlButtons, BoxLayout.X_AXIS)); - optionsButton = new JButton("Options ..."); - exportButton = new JButton("Export ..."); - ctrlButtons.add(optionsButton); - ctrlButtons.add(exportButton); - ctrlPanel.add(BorderLayout.WEST, ctrlButtons); - optionsButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - getTethysControl().getDeploymentHandler().showOptions(null); - } - }); - - exportButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - exportDeployments(); - } - }); - exportWarning = new JLabel(" "); - ctrlPanel.add(BorderLayout.CENTER, exportWarning); - - mainPanel.add(BorderLayout.CENTER, pamDeploymentsTable.getComponent()); - mainPanel.add(BorderLayout.NORTH, ctrlPanel); -// mainPanel.add(BorderLayout.EAST, exportPanel.getComponent()); - exportButton.setEnabled(false); - } - - protected void exportDeployments() { - getTethysControl().getDeploymentHandler().exportDeployments(); - } - - @Override - public JComponent getComponent() { - return mainPanel; + effortName = new JLabel(" "); + JPanel centralPanel = new JPanel(new BorderLayout()); + centralPanel.add(BorderLayout.NORTH, effortName); + centralPanel.add(BorderLayout.CENTER,pamDeploymentsTable.getComponent()); + mainPanel.add(BorderLayout.CENTER, centralPanel); } @Override @@ -98,12 +55,21 @@ public class DeploymentsPanel extends TethysGUIPanel implements DeploymentTableO enableExportButton(); } - - private void enableExportButton() { + if (!getTethysControl().isServerOk()) { + disableExport("Tethys server not running"); + return; + } + + CalibrationHandler calHandler = getTethysControl().getCalibrationHandler(); + if (calHandler.haveAllChannelCalibrations() == false) { + disableExport("Calibration data for each channel must be exported before creating Deployment documents"); + return; + } + ArrayList selected = pamDeploymentsTable.getSelectedPeriods(); - if (selected == null) { - exportButton.setEnabled(false); + if (selected == null || selected.size() == 0) { + disableExport("You must select one or more deployment periods to export"); return; } boolean existing = false; @@ -118,17 +84,35 @@ public class DeploymentsPanel extends TethysGUIPanel implements DeploymentTableO } String warning = null; if (existing) { - warning = " One or more deployment documents already exist. These must be deleted prior to exporting new documents"; - exportWarning.setText(warning); + warning = "One or more deployment documents already exist. These must be deleted prior to exporting new documents"; + disableExport(warning); + return; } - - exportButton.setEnabled(selected.size()>0 & existing == false && getTethysControl().isServerOk()); + + enableExport(true); } @Override public void updateState(TethysState tethysState) { super.updateState(tethysState); enableExportButton(); + RecordingList recordingList = pamDeploymentsTable.getMasterList(); + if (recordingList == null) { + effortName.setText(" No available effort data"); + } + else { + effortName.setText(" Effort from " + recordingList.getSourceName()); + } + } + + @Override + protected void exportButtonPressed(ActionEvent e) { + getTethysControl().getDeploymentHandler().exportDeployments(); + } + + @Override + protected void optionsButtonPressed(ActionEvent e) { + getTethysControl().getDeploymentHandler().showOptions(null); } diff --git a/src/tethys/swing/FancyClientButton.java b/src/tethys/swing/FancyClientButton.java index 266e9811..61ca0cca 100644 --- a/src/tethys/swing/FancyClientButton.java +++ b/src/tethys/swing/FancyClientButton.java @@ -1,12 +1,15 @@ package tethys.swing; import java.awt.BorderLayout; +import java.awt.Desktop; import java.awt.FlowLayout; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.io.File; +import java.io.IOException; import javax.swing.AbstractButton; import javax.swing.BoxLayout; @@ -30,14 +33,14 @@ import tethys.dbxml.DBXMLConnect; */ public class FancyClientButton extends JPanel { + private TethysControl tethysControl; + private JButton clientButton; private JButton dropButton; private JPopupMenu collectionsMenu; - private TethysControl tethysControl; private JCheckBoxMenuItem showBrowser; private AbstractButton showPAMGuard; - public FancyClientButton(TethysControl tethysControl) { this.tethysControl = tethysControl; setLayout(new GridBagLayout()); @@ -105,6 +108,16 @@ public class FancyClientButton extends JPanel { menuItem.addActionListener(new OpenCollection(collections[i])); collectionsMenu.add(menuItem); } + collectionsMenu.addSeparator(); + JMenuItem tmpItem = new JMenuItem("Open temp folder"); + collectionsMenu.add(tmpItem); + tmpItem.setToolTipText("Open folder used for temporary document files during export in Windows Explorer"); + tmpItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + openTempFolder(); + } + }); dropButton.addActionListener(new ActionListener() { @Override @@ -115,6 +128,19 @@ public class FancyClientButton extends JPanel { enableItems(); } + protected void openTempFolder() { + File tempFolder = tethysControl.getDbxmlConnect().checkTempFolder(); + if (tempFolder == null) { + return; + } + try { + Desktop.getDesktop().open(tempFolder); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + protected void enableItems() { boolean isP = tethysControl.getTethysExportParams().listDocsInPamguard; showBrowser.setSelected(!isP); diff --git a/src/tethys/swing/PAMGuardDeploymentsTable.java b/src/tethys/swing/PAMGuardDeploymentsTable.java index 1b870921..6d3819d8 100644 --- a/src/tethys/swing/PAMGuardDeploymentsTable.java +++ b/src/tethys/swing/PAMGuardDeploymentsTable.java @@ -32,6 +32,7 @@ import tethys.TethysState.StateType; import tethys.dbxml.TethysException; import tethys.deployment.DeploymentHandler; import tethys.deployment.DeploymentOverview; +import tethys.deployment.RecordingList; import tethys.deployment.RecordingPeriod; import tethys.niluswraps.PDeployment; @@ -55,6 +56,8 @@ public class PAMGuardDeploymentsTable extends TethysGUIPanel { private ArrayList observers = new ArrayList<>(); +private RecordingList masterList; + public PAMGuardDeploymentsTable(TethysControl tethysControl) { super(tethysControl); // deploymentHandler = new DeploymentHandler(getTethysControl()); @@ -74,6 +77,10 @@ public class PAMGuardDeploymentsTable extends TethysGUIPanel { public JComponent getComponent() { return mainPanel; } + + public RecordingList getMasterList() { + return masterList; + } private class TableMouse extends MouseAdapter { @@ -95,7 +102,7 @@ public class PAMGuardDeploymentsTable extends TethysGUIPanel { public void mouseClicked(MouseEvent e) { int aRow = table.getSelectedRow(); int col = table.getSelectedColumn(); - ArrayList periods = deploymentOverview.getRecordingPeriods(); + ArrayList periods = getMasterList().getEffortPeriods(); if (aRow >= 0 && aRow < periods.size() && col == TableModel.SELECTCOLUMN) { periods.get(aRow).toggleSelected(); notifyObservers(); @@ -118,7 +125,8 @@ public class PAMGuardDeploymentsTable extends TethysGUIPanel { } // make a list of RecordingPeriods which don't currently have a Deployment document ArrayList newPeriods = new ArrayList<>(); - ArrayList allPeriods = deploymentOverview.getRecordingPeriods(); + ArrayList allPeriods = getMasterList().getEffortPeriods(); +// ArrayList allPeriods = deploymentOverview.getRecordingPeriods(); ArrayList matchedDeployments = new ArrayList<>(); for (int i = 0; i < selRows.length; i++) { PDeployment tethysDeployment = allPeriods.get(selRows[i]).getMatchedTethysDeployment(); @@ -155,14 +163,6 @@ public class PAMGuardDeploymentsTable extends TethysGUIPanel { } if (matchedDeployments.size() == 1) { - menuItem = new JMenuItem("Delete deployment document " + matchedDeployments.get(0)); - menuItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - deleteDeployment(matchedDeployments.get(0)); - } - }); - popMenu.add(menuItem); menuItem = new JMenuItem("Display deployment document " + matchedDeployments.get(0)); menuItem.addActionListener(new ActionListener() { @Override @@ -179,7 +179,17 @@ public class PAMGuardDeploymentsTable extends TethysGUIPanel { } }); popMenu.add(menuItem); + + popMenu.addSeparator(); + menuItem = new JMenuItem("Delete deployment document " + matchedDeployments.get(0)); + menuItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + deleteDeployment(matchedDeployments.get(0)); + } + }); + popMenu.add(menuItem); } else if (matchedDeployments.size() > 1){ @@ -200,7 +210,8 @@ public class PAMGuardDeploymentsTable extends TethysGUIPanel { } protected void selectAll(boolean select) { - ArrayList recordingPeriods = deploymentOverview.getRecordingPeriods(); + ArrayList recordingPeriods = getMasterList().getEffortPeriods(); +// ArrayList recordingPeriods = deploymentOverview.getRecordingPeriods(); for (int i = 0; i < recordingPeriods.size(); i++) { recordingPeriods.get(i).setSelected(select); } @@ -329,7 +340,7 @@ public class PAMGuardDeploymentsTable extends TethysGUIPanel { if (deploymentOverview == null) { return null; } - ArrayList allPeriods = deploymentOverview.getRecordingPeriods(); + ArrayList allPeriods = getMasterList().getEffortPeriods(); ArrayList selPeriods = new ArrayList(); int n = allPeriods.size(); for (int i = 0; i < n; i++) { @@ -348,6 +359,7 @@ public class PAMGuardDeploymentsTable extends TethysGUIPanel { private void updateDeployments() { DeploymentHandler deploymentHandler = getTethysControl().getDeploymentHandler(); deploymentOverview = deploymentHandler.getDeploymentOverview(); + masterList = deploymentOverview.getMasterList(getTethysControl()); if (deploymentOverview == null) { return; } @@ -373,7 +385,7 @@ public class PAMGuardDeploymentsTable extends TethysGUIPanel { return 0; } else { - return deploymentOverview.getRecordingPeriods().size(); + return getMasterList().size(); } } @@ -398,13 +410,14 @@ public class PAMGuardDeploymentsTable extends TethysGUIPanel { @Override public Object getValueAt(int rowIndex, int columnIndex) { - RecordingPeriod period = deploymentOverview.getRecordingPeriods().get(rowIndex); + RecordingList masterList = getMasterList(); + RecordingPeriod period = masterList.getEffortPeriods().get(rowIndex); // DeploymentRecoveryPair deplInfo = deploymentInfo.get(rowIndex); if (columnIndex == 6) { - return deploymentOverview.getDutyCycleInfo(); + return masterList.assessDutyCycle(); } if (columnIndex == 4 && rowIndex > 0) { - RecordingPeriod prevPeriod = deploymentOverview.getRecordingPeriods().get(rowIndex-1); + RecordingPeriod prevPeriod = masterList.getEffortPeriods().get(rowIndex-1); long gap = period.getRecordStart() - prevPeriod.getRecordStop(); return PamCalendar.formatDuration(gap); } diff --git a/src/tethys/swing/TethysConnectionPanel.java b/src/tethys/swing/TethysConnectionPanel.java index d96b7a54..5a7cc90b 100644 --- a/src/tethys/swing/TethysConnectionPanel.java +++ b/src/tethys/swing/TethysConnectionPanel.java @@ -277,12 +277,17 @@ public class TethysConnectionPanel extends TethysGUIPanel { @Override public void updateState(TethysState tethysState) { super.updateState(tethysState); - if (tethysState.stateType == StateType.UPDATESERVER) { + switch (tethysState.stateType) { + case UPDATESERVER: fillServerControl(); updateProjectList(); - } - if (tethysState.stateType == StateType.NEWPROJECTSELECTION) { + break; + case NEWPROJECTSELECTION: updateProjectList(); + break; + case UPDATEMETADATA: + updateInstrumentsList(); + break; } } diff --git a/src/tethys/swing/TethysDeploymentsTable.java b/src/tethys/swing/TethysDeploymentsTable.java index 08781605..21c45bbe 100644 --- a/src/tethys/swing/TethysDeploymentsTable.java +++ b/src/tethys/swing/TethysDeploymentsTable.java @@ -21,6 +21,7 @@ import tethys.TethysControl; import tethys.TethysMenuActions; import tethys.TethysState; import tethys.deployment.DeploymentOverview; +import tethys.deployment.RecordingList; import tethys.niluswraps.PDeployment; public class TethysDeploymentsTable extends TethysGUIPanel { @@ -121,8 +122,7 @@ public class TethysDeploymentsTable extends TethysGUIPanel { @Override public String getColumnName(int column) { return columnNames[column]; - } - + } public String getMatchText(PDeployment deployment) { // TODO Auto-generated method stub @@ -132,8 +132,9 @@ public class TethysDeploymentsTable extends TethysGUIPanel { if (deploymentOverview == null) { return "No PAMGuard data"; } - Long depStart = deploymentOverview.getFirstStart(); - Long depEnd = deploymentOverview.getLastEnd(); + RecordingList masterList = deploymentOverview.getMasterList(getTethysControl()); + Long depStart = masterList.getStart(); + Long depEnd = masterList.getEnd(); if (depStart == null) { return "No PAMGuard recordings"; } diff --git a/src/tethys/swing/TethysExportPanel.java b/src/tethys/swing/TethysExportPanel.java new file mode 100644 index 00000000..6fdfaa6e --- /dev/null +++ b/src/tethys/swing/TethysExportPanel.java @@ -0,0 +1,227 @@ +package tethys.swing; + +import java.awt.BorderLayout; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import PamView.dialog.NorthPanel; +import PamView.dialog.SettingsButton; +import PamView.help.PamHelp; +import tethys.Collection; +import tethys.CollectionHandler; +import tethys.TethysControl; + +/** + * Common panel used by Calibrations, Deployments and Detections to show an export button and other + * common components, such as help, a table, etc.. + * @author dg50 + * + */ +abstract public class TethysExportPanel extends TethysGUIPanel { + + private TippedButton exportButton; + + private JButton optionsButton, helpButton; + + private JPanel mainPanel, northPanel; + + private JLabel message; + + private CollectionHandler collectionHandler; + + private String helpPoint; + + private boolean showOptions; + + public TethysExportPanel(TethysControl tethysControl, CollectionHandler collectionHandler, boolean showOptions) { + super(tethysControl); + this.collectionHandler = collectionHandler; + this.showOptions = showOptions; + this.helpPoint = collectionHandler.getHelpPoint(); + + mainPanel = new JPanel(new BorderLayout()); + northPanel = new JPanel(new BorderLayout()); + JPanel nwPanel = new JPanel(); + nwPanel.setLayout(new BoxLayout(nwPanel, BoxLayout.X_AXIS)); + JPanel nePanel = new JPanel(); + nePanel.setLayout(new BoxLayout(nePanel, BoxLayout.X_AXIS)); + northPanel.add(BorderLayout.CENTER, nwPanel); + northPanel.add(BorderLayout.EAST, nePanel); + mainPanel.add(BorderLayout.NORTH, northPanel); + + optionsButton = new SettingsButton(); + exportButton = new TippedButton("Export ...", "Export " + collectionHandler.collectionName() + " to Tethys"); + helpButton = new JButton("?"); + helpButton.setToolTipText("Show context sensitive help"); + JLabel space = new JLabel(" "); + message = new JLabel (" "); + + nwPanel.add(optionsButton); + nwPanel.add(exportButton); + nwPanel.add(space); + nwPanel.add(message); + nePanel.add(helpButton); + + showAndHide(); + + optionsButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + optionsButtonPressed(e); + } + }); + exportButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + exportButtonPressed(e); + } + }); + helpButton.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + helpButtonPressed(e); + } + }); + } + + /** + * Show the help. + * @param e + */ + protected void helpButtonPressed(ActionEvent e) { + if (helpPoint == null) { + return; + } + PamHelp.getInstance().displayContextSensitiveHelp(helpPoint); + } + + /** + * Export button has been pressed. + * @param e + */ + protected abstract void exportButtonPressed(ActionEvent e); + + /** + * Options button has been pressed. + * @param e + */ + protected abstract void optionsButtonPressed(ActionEvent e); + + private void showAndHide() { + optionsButton.setVisible(showOptions); + helpButton.setVisible(helpPoint != null); + } + + @Override + public JComponent getComponent() { + return mainPanel; + } + + /** + * @return the helpPoint + */ + public String getHelpPoint() { + return helpPoint; + } + + /** + * @param helpPoint the helpPoint to set + */ + public void setHelpPoint(String helpPoint) { + this.helpPoint = helpPoint; + showAndHide(); + } + + /** + * @return the showOptions + */ + public boolean isShowOptions() { + return showOptions; + } + + /** + * @param showOptions the showOptions to set + */ + public void setShowOptions(boolean showOptions) { + this.showOptions = showOptions; + showAndHide(); + } + + /** + * @return the exportButton + */ + public TippedButton getExportButton() { + return exportButton; + } + + /** + * @return the optionsButton + */ + public JButton getOptionsButton() { + return optionsButton; + } + + /** + * @return the helpButton + */ + public JButton getHelpButton() { + return helpButton; + } + + /** + * @return the mainPanel + */ + public JPanel getMainPanel() { + return mainPanel; + } + + /** + * @return the northPanel + */ + public JPanel getNorthPanel() { + return northPanel; + } + + /** + * @return the message + */ + public JLabel getMessage() { + return message; + } + + /** + * @return the collectionHandler + */ + public CollectionHandler getCollectionHandler() { + return collectionHandler; + } + + /** + * Enable or disable export button, leaving tool tips alone + * @param enable + */ + public void enableExport(boolean enable) { + exportButton.setEnabled(enable); + if (enable) { + message.setText(null); + } + } + + /** + * Disable the export button and set the tooltip. + * @param disabledTip + */ + public void disableExport(String disabledTip) { + exportButton.disable(disabledTip); + message.setText(disabledTip); + } + +} diff --git a/src/tethys/swing/TippedButton.java b/src/tethys/swing/TippedButton.java new file mode 100644 index 00000000..46c369e2 --- /dev/null +++ b/src/tethys/swing/TippedButton.java @@ -0,0 +1,77 @@ +package tethys.swing; + +import javax.swing.JButton; + +public class TippedButton extends JButton { + + private static final long serialVersionUID = 1L; + private String enabledTip; + private String disabledTip; + + /** + * Create a button with standard tips which will be used for enabled state + * @param text + * @param enabledTip + */ + public TippedButton(String text, String enabledTip) { + this(text, enabledTip, null); + } + + /** + * Create a button with standard tips which will be used for enabled and disabled state + * @param text + * @param enabledTip + * @param disabledTip + */ + public TippedButton(String text, String enabledTip, String disabledTip) { + super(text); + this.enabledTip = enabledTip; + this.disabledTip = disabledTip; + setToolTipText(enabledTip); + } + + @Override + public void setEnabled(boolean enable) { + super.setEnabled(enable); + setToolTipText(enable ? enabledTip : disabledTip); + } + + /* + * Call to disable the button and at the same time + * set a tooltip giving the reason. + */ + public void disable(String newTip) { + disabledTip = newTip; + setEnabled(false); + } + + /** + * @return the enabledTip + */ + public String getEnabledTip() { + return enabledTip; + } + + /** + * @param enabledTip the enabledTip to set + */ + public void setEnabledTip(String enabledTip) { + this.enabledTip = enabledTip; + } + + /** + * @return the disabledTip + */ + public String getDisabledTip() { + return disabledTip; + } + + /** + * @param disabledTip the disabledTip to set + */ + public void setDisabledTip(String disabledTip) { + this.disabledTip = disabledTip; + } + + +} diff --git a/src/tethys/swing/XMLStringView.java b/src/tethys/swing/XMLStringView.java index 26098fe3..6886c493 100644 --- a/src/tethys/swing/XMLStringView.java +++ b/src/tethys/swing/XMLStringView.java @@ -28,6 +28,9 @@ public class XMLStringView extends PamDialog { getCancelButton().setVisible(false); setModal(false); + + getOkButton().setText("Close"); + getOkButton().setToolTipText("Close window"); } public static void showDialog(Window parent, String collection, String documentId, String xmlString) { @@ -38,7 +41,7 @@ public class XMLStringView extends PamDialog { @Override public boolean getParams() { - return false; + return true; } @Override diff --git a/src/tethys/swing/export/DeploymentPeriodPanel.java b/src/tethys/swing/export/DeploymentPeriodPanel.java index 26b3e9b3..c3733fd9 100644 --- a/src/tethys/swing/export/DeploymentPeriodPanel.java +++ b/src/tethys/swing/export/DeploymentPeriodPanel.java @@ -19,6 +19,7 @@ import metadata.PamguardMetaData; import nilus.Deployment; import nilus.DeploymentRecoveryDetails; import tethys.TethysTimeFuncs; +import tethys.tooltips.TethysTips; public class DeploymentPeriodPanel { @@ -68,6 +69,9 @@ public class DeploymentPeriodPanel { enableControls(); } }); + + deploymentStart.setToolTipText(TethysTips.findTip(DeploymentRecoveryDetails.class, "TimeStamp")); + deploymentEnd.setToolTipText(TethysTips.findTip(DeploymentRecoveryDetails.class, "TimeStamp")); } protected void enableControls() { diff --git a/src/tethys/swing/export/DescriptionTypePanel.java b/src/tethys/swing/export/DescriptionTypePanel.java index 64178883..0220468c 100644 --- a/src/tethys/swing/export/DescriptionTypePanel.java +++ b/src/tethys/swing/export/DescriptionTypePanel.java @@ -19,6 +19,7 @@ import PamView.PamGui; import PamView.dialog.PamDialog; import nilus.DescriptionType; import tethys.niluswraps.WrappedDescriptionType; +import tethys.tooltips.TethysTips; /** * Panel containing the three test entry fields for nilus.DescriptionType @@ -73,6 +74,10 @@ public class DescriptionTypePanel { addScrollablePanel(tObjectives, "Objectives"); addScrollablePanel(tAbstract, "Abstract"); addScrollablePanel(tMethod, "Method"); + + tObjectives.setToolTipText(TethysTips.Detections_Description_Objectives); + tAbstract.setToolTipText(TethysTips.Detections_Description_Abstract); + tMethod.setToolTipText(TethysTips.Detections_Description_Method); } private void addScrollablePanel(JTextArea textArea, String title) { diff --git a/src/tethys/swing/export/ExportWorkerCard.java b/src/tethys/swing/export/ExportWorkerCard.java index 6c512c24..27475bad 100644 --- a/src/tethys/swing/export/ExportWorkerCard.java +++ b/src/tethys/swing/export/ExportWorkerCard.java @@ -147,7 +147,7 @@ public class ExportWorkerCard extends ExportWizardCard implements DetectionExpor totExpected *= progress.exportCount/(progress.exportCount+progress.skipCount); } projectedCount.setText(String.format("%d", totExpected)); - long perc = (progress.exportCount+progress.skipCount) * 100 / progress.totalUnits; + long perc = (progress.doneMapPoints) * 100 / progress.nMapPoints; progressBar.setValue((int) perc); } switch (progress.state) { @@ -176,6 +176,18 @@ public class ExportWorkerCard extends ExportWizardCard implements DetectionExpor boolean stopped = state == DetectionExportProgress.STATE_CANCELED || state == DetectionExportProgress.STATE_COMPLETE; export.setEnabled(stopped); cancel.setEnabled(!stopped); + detectionsExportWizard.getCancelButton().setEnabled(stopped); + detectionsExportWizard.getPreviousButton().setEnabled(stopped); + } + + @Override + public void setVisible(boolean visible) { + /** + * setVisible is called by the wizard for each component, so + * we can see here if we're on this or not. + */ + super.setVisible(visible); + detectionsExportWizard.getOkButton().setEnabled(!visible); } } diff --git a/src/tethys/swing/export/ResponsiblePartyPanel.java b/src/tethys/swing/export/ResponsiblePartyPanel.java index 93b26816..4a42f959 100644 --- a/src/tethys/swing/export/ResponsiblePartyPanel.java +++ b/src/tethys/swing/export/ResponsiblePartyPanel.java @@ -10,6 +10,7 @@ import javax.swing.border.TitledBorder; import PamView.dialog.PamGridBagContraints; import nilus.ContactInfo.Address; +import tethys.tooltips.TethysTips; import nilus.ResponsibleParty; /** @@ -56,6 +57,11 @@ public class ResponsiblePartyPanel { c.gridx = 0; c.gridy++; + name.setToolTipText("Person responsible for data"); + organisation.setToolTipText("Responsible organization"); + position.setToolTipText("Persons role in organization"); + email.setToolTipText("email address or other contact details"); + } public JPanel getMainPanel() { diff --git a/src/tethys/tooltips/TethysTips.java b/src/tethys/tooltips/TethysTips.java new file mode 100644 index 00000000..8aff6fb2 --- /dev/null +++ b/src/tethys/tooltips/TethysTips.java @@ -0,0 +1,403 @@ +package tethys.tooltips; + +import java.lang.reflect.Field; + +import nilus.Deployment; + +/** + * Class to make it easy to find tooltips for a given nilus class and field nams. The constatns + * could be used directly, or can be found using findTip(Class, String) using the class type + * and field name for any nilus object. + * Tips were generates from a set of csv files extracted from the xml schema using Matlab code, then formatted + * in Matlab and pasted into this class, so will need to rerun that process should the xml schemas be updated. + * + * @author dg50 + * + */ +public class TethysTips { + +// public static void main(String[] args) { +// Class cls = Deployment.Data.class; +// String field = "Audio"; +// String foundTip = findTip(cls, field); +// System.out.println(foundTip); +// } + + /** + * find the tooltip for a given class and field within that class. + * @param aClass + * @param field + * @return found tip or null + */ + public static String findTip(Class aClass, String fieldName) { + Package pack = aClass.getPackage(); + String packName = pack.toString(); + String clsName = aClass.getCanonicalName(); + if (clsName.startsWith("nilus.") == false) { + return null; + } + clsName = clsName.substring(6); + clsName = clsName.replace('.', '_'); + String varName = clsName + "_" + fieldName; + // now try to find that field in this class and get it's value. + Field field = null; + try { + field = TethysTips.class.getDeclaredField(varName); + } catch (NoSuchFieldException | SecurityException e) { + return null; + } + if (field == null) { + return null; + } + Object tip = null; + try { + tip = field.get(null); + } catch (IllegalArgumentException | IllegalAccessException e) { + return null; + } + if (tip == null) { + return null; + } + + return tip.toString(); + } + + // Annotations taken from schemata_csv + public static final String Calibration_Id = "Identifier of instrument, preamplifier, or hydrophone. Corresponds to elements in Deployment: Deployment/Instrument/Id, Deployment/Sensors/Audio/HydrophoneId, Deployment/Sensors/Audio[i]/PreampId. As instruments may be calibrated multiple times, it is not an error for duplicate Id values to appear. It is recommended that the three different types of identifiers (instrument, hydrophone, preamp) be distinct, but the Type element may be used to distinguish them if they are not."; + public static final String Calibration_TimeStamp = "Date and time of calibration"; + public static final String Calibration_Type = "hydrophone, preamplifier, or end-to-end Indicates type of calibration"; + public static final String Calibration_Process = "Process used to calibrate instrument."; + public static final String Calibration_ResponsibleParty = "Who conducted/managed the calibration?"; + public static final String Calibration_IntensityReference_uPa = "Reference intensity in µ Pascals for dB measurements. Commonly used: underwater acoustics: 1 terrestrial acoustics: 20"; + public static final String Calibration_Sensitivity_dBV = "Optional measurement of transducer sensitivity at 1 kHz."; + public static final String Calibration_Sensitivity_V = "Optional measurement of Sensitivity_dBV on a linear scale, provided by many transducer manufacturers."; + public static final String Calibration_Sensitivity_dBFS = "Optional measurement for digital transducers. Digital transducers do not output voltage measurements. In this case, the 1 kHz sensitivity measurement is measured relative to peak output of the full-scale signal instead of RMS. It should be noted that for sinusoidal signals, the RMS sensitivity will be 3 dB lower (Lewis, 2012). Lewis, J. (2012). \"Understanding Microphone Sensitivity,\" Analog Dialogue 46(2). 14-16."; + public static final String Calibration_FrequencyResponse = "Lists of frequencies (Hz) and responses (dB). Lists must be of equal length."; + public static final String Calibration_MetadataInfo = "Information about who is responsible for this metadata record, when it was created, and how often it is updated."; + public static final String Calibration_Process_Method = "Text based description of algorithm or citation"; + public static final String Calibration_Process_Software = "Name of software that implements the algorithm or supports human analysts. This might be the name of a plug-in or extension module that is part of a larger program or system."; + public static final String Calibration_Process_Version = "Software version identifier"; + public static final String Calibration_Process_Parameters = "Structured tags to describe parameters used in algorithm execution."; + public static final String Calibration_Process_SupportSoftware = "Software required in addition to the algorithm itself, e.g. Matlab, Ishmael, PAMGUARD, Triton, etc."; + public static final String Calibration_Process_SupportSoftware_Version = "Software version identifier."; + public static final String Calibration_Process_SupportSoftware_Parameters = "Structured tags to describe parameters used in algorithm execution."; + public static final String Calibration_ResponsibleParty_contactInfo_onlineResource = "We do not fully conform to the onlineResources of ISO 19115"; + public static final String Calibration_QualityAssurance_Quality = "Measurement is: unverified, valid, invalid"; + public static final String Calibration_QualityAssurance_AlternateCalibration = "Provide an alternative calibration Id that should be used (if available) when the Quality value is invalid."; + public static final String Calibration_MetadataInfo_Contact = "based on ISO 19115"; + public static final String Calibration_MetadataInfo_Date = "Last update."; + public static final String Calibration_MetadataInfo_UpdateFrequency = "How often are these data updated? as-needed, unplanned, or yearly"; + public static final String Calibration_MetadataInfo_Contact_contactInfo_onlineResource = "We do not fully conform to the onlineResources of ISO 19115"; + + // Annotations taken from schemata_csv + public static final String Deployment_Id = "Character sequence that uniquely identifies this deployment."; + public static final String Deployment_Description = "Objectives, abstract and high-level methods."; + public static final String Deployment_Project = "Name of project associated with this deployment. Can be related to a geographic region, funding source, etc."; + public static final String Deployment_DeploymentId = "A number related to either the Nth deployment operation in a series of deployments or the Nth deployment at a specific site. This is different from Id which is a unqiue identifier for the deployment. If a vessel deployed 5 instruments, they might all have the same DeploymentId. While not enforced, it is expected that the combination of Project, DeploymentId, and (Site or Cruise) to be unique."; + public static final String Deployment_DeploymentAlias = "Alternative deployment description."; + public static final String Deployment_Site = "Name of a location where instruments are frequently deployed. Can be something as simple as a letter or a geographic name. Strongly recommended for long-term time series recorded at a specific point."; + public static final String Deployment_SiteAliases = "Alternative names for the deployment location"; + public static final String Deployment_Cruise = "Name of deployment cruise."; + public static final String Deployment_Platform = "On what platform is the instrument deployed? (e.g. mooring, tag)"; + public static final String Deployment_Region = "Name of geographic region."; + public static final String Deployment_Instrument = "Instrument type and identifier."; + public static final String Deployment_SamplingDetails = "Information about recordings on each channel. Sample rate, quantization bits, etc."; + public static final String Deployment_Data = "Data from instrument, a URI is provided when not present (typical for audio)."; + public static final String Deployment_DeploymentDetails = "Instrument deployment location, time, etc."; + public static final String Deployment_RecoveryDetails = "Instrument recovery, location, time, etc."; + public static final String Deployment_Sensors = "Sensors on instrument."; + public static final String Deployment_MetadataInfo = "Party responsible for this record. Some applications may make this mandatory."; + public static final String Deployment_Description_Objectives = "What are the objectives of this effort? Examples: Beamform to increase SNR for detection. Detect every click of a rare species. Verify data quality."; + public static final String Deployment_Description_Abstract = "Overview of effort."; + public static final String Deployment_Description_Method = "High-level description of the method used."; + public static final String Deployment_Instrument_Type = "Instrument type, e.g. HARP, EAR, Popup, DMON, Rock Hopper, etc."; + public static final String Deployment_Instrument_InstrumentId = "Instrument identifier, e.g. serial number"; + public static final String Deployment_Instrument_GeometryType = "Sensor attachment \"rigid\" - relative geometry is fixed, \"cabled\" - relative geometry may be expected to deform depending on movement, currents, etc."; + public static final String Deployment_SamplingDetails_Channel_ChannelNumber = "Channels and sensors are bound together from Start to End. While not enforced, we assume channels are numbered from 1 to N."; + public static final String Deployment_SamplingDetails_Channel_SensorNumber = "Audio sensor index within the Sensors element. This allows us to associate a channel with a physical hydrophone."; + public static final String Deployment_SamplingDetails_Channel_Sampling = "Sampling rate and quantization may change over time."; + public static final String Deployment_SamplingDetails_Channel_Gain = "Initial gain setting (assumed 0 if not populated) and any subsequent changes."; + public static final String Deployment_SamplingDetails_Channel_DutyCycle = "Duty cycle is represented by the recording duration and the interval from the start of one recording session to the next. A duration of 3 m and an interval of 5 m would represent a 60% duty cycle, 3 m on, 2 m off."; + public static final String Deployment_SamplingDetails_Channel_Sampling_Regimen = "Sampling regimen may change over time. Each entry shows the start of a sampling configuration."; + public static final String Deployment_SamplingDetails_Channel_Sampling_Regimen_SampleRate_kHz = "Number of samples per second in kHz, e.g. 192 is 192,000 samples/s"; + public static final String Deployment_SamplingDetails_Channel_Sampling_Regimen_SampleBits = "Number of bits per sample."; + public static final String Deployment_SamplingDetails_Channel_Gain_Regimen_Gain_rel = "Only used if gain is not calibrated. Relative gain may be a number on a dial."; + public static final String Deployment_SamplingDetails_Channel_DutyCycle_Regimen = "Duty cycling regimen may change over time. Each entry shows the start of a duty cycle configuration. The abscence of entries indicates continuous sampling as would having equal values in RecordingDuration_m and RecordingInterval_m."; + public static final String Deployment_SamplingDetails_Channel_DutyCycle_Regimen_TimeStamp = "Indicates when the duty cycle becomes active. It remains active until the next Regimen entry."; + public static final String Deployment_SamplingDetails_Channel_DutyCycle_Regimen_RecordingDuration_s = "The amount of time in minutes during each recording interval when the data logger is recoring. Use the attribute Offset_s when the recording does not begin at the beginning of each recording interval."; + public static final String Deployment_SamplingDetails_Channel_DutyCycle_Regimen_RecordingInterval_s = "Time between consecutive recordings. If RecordingDuration_s is 1800 s and RecordingInterval_s is 3600 s, then we record for the 30 min of each hour."; + public static final String Deployment_QualityAssurance_Description = "Text based description of process."; + public static final String Deployment_QualityAssurance_ResponsibleParty = "based on ISO 19115"; + public static final String Deployment_QualityAssurance_Quality = "If no quality assurance, create an entry of Category unverified spanning the acoustic record."; + public static final String Deployment_QualityAssurance_Description_Objectives = "What are the objectives of this effort? Examples: Beamform to increase SNR for detection. Detect every click of a rare species. Verify data quality."; + public static final String Deployment_QualityAssurance_Description_Abstract = "Overview of effort."; + public static final String Deployment_QualityAssurance_Description_Method = "High-level description of the method used."; + public static final String Deployment_QualityAssurance_ResponsibleParty_contactInfo_onlineResource = "We do not fully conform to the onlineResources of ISO 19115"; + public static final String Deployment_QualityAssurance_Quality_Category = "categories: unverified, good, compromised, unusable"; + public static final String Deployment_QualityAssurance_Quality_FrequencyRange = "QA metric applies to what frequency range?"; + public static final String Deployment_QualityAssurance_Quality_Comment = "Additional qualitative information"; + public static final String Deployment_Data_Audio = "Information about audio data."; + public static final String Deployment_Data_Tracks = "A set of measurements about a ship/instrument's track line."; + public static final String Deployment_Data_Audio_URI = "Uniform Resource Indicator that points to audio content. Examples: digital object identifier, web address, or even a simple string describing the storage location."; + public static final String Deployment_Data_Audio_Processed = "Pointer to location of data that has been processed (e.g. checked for quality, decimated, etc.)"; + public static final String Deployment_Data_Audio_Raw = "Pointer to raw data from the instrument."; + public static final String Deployment_Data_Tracks_Track = "A set of sorted (by time) points associated with one or more tracklines."; + public static final String Deployment_Data_Tracks_TrackEffort = "Not all measurements are associated with an instrument/ship's planned trackline (e.g. when in chase mode or transiting between tracklines). Specify times for track effort here if needed."; + public static final String Deployment_Data_Tracks_URI = "Pointer to trackline information."; + public static final String Deployment_Data_Tracks_Track_TrackId = "Optional trackline number. If unimportant, everything can be put in one Points element."; + public static final String Deployment_Data_Tracks_Track_Point = "Timestamped measurements: long/lat, bearing, etc. Points should be sorted by timestamp."; + public static final String Deployment_Data_Tracks_Track_Point_Bearing_DegN = "Bearing in degrees [0, 360) relative to true or magnetic north (as specified by north attribute, default magnetic)"; + public static final String Deployment_Data_Tracks_Track_Point_Speed_kn = "Speed in knots"; + public static final String Deployment_Data_Tracks_Track_Point_Pitch_deg = "Instrument pitch [0, 360) degrees"; + public static final String Deployment_Data_Tracks_Track_Point_Roll_deg = "Instrument roll [0, 360) degrees"; + public static final String Deployment_Data_Tracks_Track_Point_Elevation_m = "Instrument elevation (meters) relative to average sea level."; + public static final String Deployment_Data_Tracks_Track_Point_GroundElevation_m = "Ground or seabed elevation (meters) relative to average sea level."; + public static final String Deployment_Data_Tracks_Track_Point_Longitude = "Expressed in degrees East [0, 360)"; + public static final String Deployment_Data_Tracks_Track_Point_Latitude = "Expressed in degrees North [-90, 90]"; + public static final String Deployment_Data_Tracks_TrackEffort_OnPath_FocalArea = "This element is used to provide names that specify a focal area in which the study was conducted, such as a National Marine Sanctuary."; + public static final String Deployment_Data_Tracks_TrackEffort_OffPath_FocalArea = "This element is used to provide names that specify a focal area in which the study was conducted, such as a National Marine Sanctuary."; + public static final String Deployment_DeploymentDetails_Longitude = "Expressed in degrees East [0, 360)"; + public static final String Deployment_DeploymentDetails_Latitude = "Expressed in degrees North [-90, 90]"; + public static final String Deployment_DeploymentDetails_ElevationInstrument_m = "The elevation at which this instrument is positioned."; + public static final String Deployment_DeploymentDetails_DepthInstrument_m = "Not usually required. This field is designed to record depth with respect to the ground or seabed. Uses for this field include mines and alpine lakes."; + public static final String Deployment_DeploymentDetails_Elevation_m = "Elevation of ground/sea bed"; + public static final String Deployment_DeploymentDetails_TimeStamp = "Time at which instrument was deployed/recovered. Lost instruments: set recovery time to deployment time."; + public static final String Deployment_DeploymentDetails_AudioTimeStamp = "Recording start or end - May differ from deployment time."; + public static final String Deployment_DeploymentDetails_ResponsibleParty = "based on ISO 19115"; + public static final String Deployment_DeploymentDetails_ResponsibleParty_contactInfo_onlineResource = "We do not fully conform to the onlineResources of ISO 19115"; + public static final String Deployment_RecoveryDetails_Longitude = "Expressed in degrees East [0, 360)"; + public static final String Deployment_RecoveryDetails_Latitude = "Expressed in degrees North [-90, 90]"; + public static final String Deployment_RecoveryDetails_ElevationInstrument_m = "The elevation at which this instrument is positioned."; + public static final String Deployment_RecoveryDetails_DepthInstrument_m = "Not usually required. This field is designed to record depth with respect to the ground or seabed. Uses for this field include mines and alpine lakes."; + public static final String Deployment_RecoveryDetails_Elevation_m = "Elevation of ground/sea bed"; + public static final String Deployment_RecoveryDetails_TimeStamp = "Time at which instrument was deployed/recovered. Lost instruments: set recovery time to deployment time."; + public static final String Deployment_RecoveryDetails_AudioTimeStamp = "Recording start or end - May differ from deployment time."; + public static final String Deployment_RecoveryDetails_ResponsibleParty = "based on ISO 19115"; + public static final String Deployment_RecoveryDetails_ResponsibleParty_contactInfo_onlineResource = "We do not fully conform to the onlineResources of ISO 19115"; + public static final String Deployment_Sensors_Audio_Number = "Sensor index. May be used to associate the sensor with other parts of the schema. For example, for Audio sensors, the Channel/SensorNumber can be set to a specific Sensor/Audio/Number, permitting us to determine information about the a hydrophone assembly."; + public static final String Deployment_Sensors_Audio_SensorId = "A value that uniquely identifies this sensor, e.g. a serial number."; + public static final String Deployment_Sensors_Audio_Geometry = "Geometry relative to platform"; + public static final String Deployment_Sensors_Audio_Name = "Optional sensor name"; + public static final String Deployment_Sensors_Audio_Description = "Optional description of sensor."; + public static final String Deployment_Sensors_Audio_HydrophoneId = "Optional hydrophone identifier."; + public static final String Deployment_Sensors_Audio_PreampId = "Optional preamplifier identifier."; + public static final String Deployment_Sensors_Depth_Number = "Sensor index. May be used to associate the sensor with other parts of the schema. For example, for Audio sensors, the Channel/SensorNumber can be set to a specific Sensor/Audio/Number, permitting us to determine information about the a hydrophone assembly."; + public static final String Deployment_Sensors_Depth_SensorId = "A value that uniquely identifies this sensor, e.g. a serial number."; + public static final String Deployment_Sensors_Depth_Geometry = "Geometry relative to platform"; + public static final String Deployment_Sensors_Depth_Name = "Optional sensor name"; + public static final String Deployment_Sensors_Depth_Description = "Optional description of sensor."; + public static final String Deployment_Sensors_Sensor_Number = "Sensor index. May be used to associate the sensor with other parts of the schema. For example, for Audio sensors, the Channel/SensorNumber can be set to a specific Sensor/Audio/Number, permitting us to determine information about the a hydrophone assembly."; + public static final String Deployment_Sensors_Sensor_SensorId = "A value that uniquely identifies this sensor, e.g. a serial number."; + public static final String Deployment_Sensors_Sensor_Geometry = "Geometry relative to platform"; + public static final String Deployment_Sensors_Sensor_Name = "Optional sensor name"; + public static final String Deployment_Sensors_Sensor_Description = "Optional description of sensor."; + public static final String Deployment_Sensors_Sensor_Type = "Description of data gathered by this sensor, e.g., temperature"; + public static final String Deployment_Sensors_Sensor_Properties = "List of property elements describing the sensor. These may be arbitrary. Example: Properties can have child Units with value °C. Children may be nested."; + public static final String Deployment_MetadataInfo_Contact = "based on ISO 19115"; + public static final String Deployment_MetadataInfo_Date = "Last update."; + public static final String Deployment_MetadataInfo_UpdateFrequency = "How often are these data updated? as-needed, unplanned, or yearly"; + public static final String Deployment_MetadataInfo_Contact_contactInfo_onlineResource = "We do not fully conform to the onlineResources of ISO 19115"; + + + // Annotations taken from schemata_csv + public static final String Detections_Id = "Identification string that is unique to all documents of this type (currently optional, will be required in the future)"; + public static final String Detections_Description = "Objectives, abstract and high-level methods."; + public static final String Detections_DataSource = "Acoustic data identifier."; + public static final String Detections_Algorithm = "Detailed methods."; + public static final String Detections_QualityAssurance = "Description of quality assurance checks (if any)."; + public static final String Detections_UserId = "User that submitted the document."; + public static final String Detections_Effort = "Span and scope of detection effort."; + public static final String Detections_OnEffort = "Collection of individual detections."; + public static final String Detections_OffEffort = "Collection of off-effort (ad-hoc) detections. Each detection has the same format as the OnEffort ones."; + public static final String Detections_MetadataInfo = "Party responsible for this record. Some applications may make this mandatory."; + public static final String Detections_Description_Objectives = "What are the objectives of this effort? Examples: Beamform to increase SNR for detection. Detect every click of a rare species. Verify data quality."; + public static final String Detections_Description_Abstract = "Overview of effort."; + public static final String Detections_Description_Method = "High-level description of the method used."; + public static final String Detections_DataSource_EnsembleId = "Serves as a foreign key into the ensembles collection and must match an Id element in an ensemble document. Ensembles are used to group instruments together for a common purpose (e.g. large aperture array)."; + public static final String Detections_DataSource_DeploymentId = "Serves as a foreign key into the Deployments collection and must match an Id element in a deployment document."; + public static final String Detections_Algorithm_Method = "Text based description of algorithm or citation"; + public static final String Detections_Algorithm_Software = "Name of software that implements the algorithm or supports human analysts. This might be the name of a plug-in or extension module that is part of a larger program or system."; + public static final String Detections_Algorithm_Version = "Software version identifier"; + public static final String Detections_Algorithm_Parameters = "Structured tags to describe parameters used in algorithm execution."; + public static final String Detections_Algorithm_SupportSoftware = "Software required in addition to the algorithm itself, e.g. Matlab, Ishmael, PAMGUARD, Triton, etc."; + public static final String Detections_Algorithm_SupportSoftware_Version = "Software version identifier."; + public static final String Detections_Algorithm_SupportSoftware_Parameters = "Structured tags to describe parameters used in algorithm execution."; + public static final String Detections_QualityAssurance_Description = "Text based description of process."; + public static final String Detections_QualityAssurance_ResponsibleParty = "based on ISO 19115"; + public static final String Detections_QualityAssurance_Description_Objectives = "What are the objectives of this effort? Examples: Beamform to increase SNR for detection. Detect every click of a rare species. Verify data quality."; + public static final String Detections_QualityAssurance_Description_Abstract = "Overview of effort."; + public static final String Detections_QualityAssurance_Description_Method = "High-level description of the method used."; + public static final String Detections_QualityAssurance_ResponsibleParty_contactInfo_onlineResource = "We do not fully conform to the onlineResources of ISO 19115"; + public static final String Detections_Effort_Start = "Timestamp indicating the start of systematic effort to find the species and phenomena listed in the Effort/Kind entries."; + public static final String Detections_Effort_End = "Timestamp indicating end of systematic effort."; + public static final String Detections_Effort_dBReferenceIntensity_uPa = "All dB measurements are made relative to this value in uPa. Typical values are 1 for underwater acoustics and 20 for terrestrial acoustics."; + public static final String Detections_Effort_AnalysisGaps_Aperiodic = "Used to describe meaningful gaps in the analysis effort. Problems with the data should not be addressed here, but rather in Deployment/QualityAssurance/Quality. Note that tools may not take Gaps into account when reporting effort statistics."; + public static final String Detections_Effort_AnalysisGaps_Periodic_Regimen = "Peridoic analysis regimen may change over time. Each entry shows the start of an analysis regimen. The abscence of entries indicates continuous analysis as would having equal values in AnalysisDuration_s and AnalysisInterval_s. The time offsets in these fields are with respect to actual time. Duty cycled data are not taken into account in their specification. As an example, if we analyzed the first 30 min of each hour and the deployment's recording duty cycle were 15 min of recording every 30 min, this analysis duration would only result in 15 min of analysis every hour."; + public static final String Detections_Effort_AnalysisGaps_Periodic_Regimen_TimeStamp = "Indicates when the regimen becomes active. It remains active until the next Regimen entry."; + public static final String Detections_Effort_AnalysisGaps_Periodic_Regimen_AnalysisDuration_s = "When analysis starts, the data are analyzed for this many seconds. Optional attribute Offset_s may be used to denote the number of seconds after the timestamp that analysis started. If Offset_s is not present, analysis starts at the Timestamp."; + public static final String Detections_Effort_AnalysisGaps_Periodic_Regimen_AnalysisInterval_s = "Time between consecutive effort. If AnalysisDuration_s is 1800 s and AnalysisInterval_s is 3600 s, then we perform analysis on the first 30 min of each hour starting at TimeStamp."; + public static final String Detections_Effort_AnalysisGaps_Aperiodic_Start = "Timestamp indicating the start of a gap in the systematic effort to find the species and phenomena listed in the Effort/Kind entries."; + public static final String Detections_Effort_AnalysisGaps_Aperiodic_End = "Timestamp indicating end of systematic effort gap."; + public static final String Detections_Effort_AnalysisGaps_Aperiodic_Reason = "Reason for gap in analysis."; + public static final String Detections_Effort_Kind_SpeciesId = "Integrated Taxonomic Information System species identifier http://www.itis.gov/ for positive numbers. Negative numbers are used for physical phenomena."; + public static final String Detections_Effort_Kind_Call = "Name that describes call."; + public static final String Detections_Effort_Kind_Granularity = "Type of detections: call - individual call, encounter - set of calls, binned - presence detected within period specified by bin size attribute in Effort. grouped – A set of individual detections of any granularity that have been grouped together. Examples include situations such as song or other groupings (e.g. detections of the same animals picked up on multiple instruments). Grouped detections may specify the individual detections regardless of their granularity that are part of the group. This is different from granularities encounter and binned where one might expect multiple calls to occur, but the individual detections are not recorded."; + public static final String Detections_Effort_Kind_Parameters_Subtype = "subcategory of call"; + public static final String Detections_Effort_Kind_Parameters_FrequencyMeasurements_Hz = "Specifies a list of frequencies at which measurements are made. Each detection for this Kind should have a list of FrequencyMeasurements where each item corresponds to a frequency in this list. Useful for studying ambient sound or soundscapes. Be sure to declare Effort/ReferenceIntensity_uPa."; + public static final String Detections_OnEffort_Detection_Input_file = "Optional name of audio file (or indirect representation) from which this detection was generated."; + public static final String Detections_OnEffort_Detection_Start = "Time at which event started. For many detectors, this may not the actual starting time of the event."; + public static final String Detections_OnEffort_Detection_End = "Optional end time of event."; + public static final String Detections_OnEffort_Detection_Count = "An optional count of the number of times a call occurred within a bin or across an encounter."; + public static final String Detections_OnEffort_Detection_Event = "Optional tag for identifying this event uniquely within the stream. For human analysts, it is typical to use the time at which the detection was made in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ). When present, the combination of the event and attributes that uniquely identify the set of detections (or document name) must be uniqe."; + public static final String Detections_OnEffort_Detection_UnitId = "Specifies ensemble unit (when using an ensemble source)."; + public static final String Detections_OnEffort_Detection_SpeciesId = "Integrated Taxonomic Information System species identifier http://www.itis.gov/ for positive numbers. Negative numbers are used for physical phenomena."; + public static final String Detections_OnEffort_Detection_Call = "In most cases, the call field should be present. May be omitted if the goal is species detection only, or repeated for multiple types of calls when the granularity effort is not \"call\"."; + public static final String Detections_OnEffort_Detection_Image = "Name of image file (spectrogram, etc.)"; + public static final String Detections_OnEffort_Detection_Audio = "Name of audio file (short snippet)"; + public static final String Detections_OnEffort_Detection_Parameters_Subtype = "subcategory of call"; + public static final String Detections_OnEffort_Detection_Parameters_Score = "Measure from detector, e.g. likelihood ratio, projection, etc."; + public static final String Detections_OnEffort_Detection_Parameters_Confidence = "Measure of confidence in detection. Range: [0, 1]"; + public static final String Detections_OnEffort_Detection_Parameters_QualityAssurance = "Detection is: unverified, valid, invalid"; + public static final String Detections_OnEffort_Detection_Parameters_ReceivedLevel_dB = "dB relative to reference intensity defined in Effort/ReferenceIntenstiy_uPa"; + public static final String Detections_OnEffort_Detection_Parameters_FrequencyMeasurements_dB = "List of received levels at various frequencies relative to the reference value defiend in Effort/ReferenceIntensity_uPa. The frequency measurements should be consistent for each species and call type and must correspond to a a list of frequencies defined in Effort/Kind/SubType."; + public static final String Detections_OnEffort_Detection_Parameters_Peaks_Hz = "Typically used for spectra of short echolocation bursts, notes the spectral peaks in a list sorted from low to high frequency."; + public static final String Detections_OnEffort_Detection_Parameters_Duration_s = "When the call granularity is binned or encounter, this may be used to describe the mean duration of calls during the bout. As an example, at SIO we use this to track the mean duration of binned anthropogenic sources such as explosions."; + public static final String Detections_OnEffort_Detection_Parameters_Sideband_Hz = "Signal sideband frequencies in a list sorted from low to high frequency."; + public static final String Detections_OnEffort_Detection_Parameters_EventRef = "Reference to other detections for hierarchical organization."; + public static final String Detections_OnEffort_Detection_Parameters_UserDefined = "Study specific parameters"; + public static final String Detections_OnEffort_Detection_Parameters_Tonal_Offset_s = "List of offsets from start in seconds"; + public static final String Detections_OnEffort_Detection_Parameters_Tonal_Hz = "Frequency measurement for each Offset_s (Hz). List must be of same length as Offset_s"; + public static final String Detections_OnEffort_Detection_Parameters_Tonal_dB = "Optional intensity measurment (dB) for each Offset_s (dB). List must be of the same length as Offset_s"; + public static final String Detections_OffEffort_Detection_Input_file = "Optional name of audio file (or indirect representation) from which this detection was generated."; + public static final String Detections_OffEffort_Detection_Start = "Time at which event started. For many detectors, this may not the actual starting time of the event."; + public static final String Detections_OffEffort_Detection_End = "Optional end time of event."; + public static final String Detections_OffEffort_Detection_Count = "An optional count of the number of times a call occurred within a bin or across an encounter."; + public static final String Detections_OffEffort_Detection_Event = "Optional tag for identifying this event uniquely within the stream. For human analysts, it is typical to use the time at which the detection was made in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ). When present, the combination of the event and attributes that uniquely identify the set of detections (or document name) must be uniqe."; + public static final String Detections_OffEffort_Detection_UnitId = "Specifies ensemble unit (when using an ensemble source)."; + public static final String Detections_OffEffort_Detection_SpeciesId = "Integrated Taxonomic Information System species identifier http://www.itis.gov/ for positive numbers. Negative numbers are used for physical phenomena."; + public static final String Detections_OffEffort_Detection_Call = "In most cases, the call field should be present. May be omitted if the goal is species detection only, or repeated for multiple types of calls when the granularity effort is not \"call\"."; + public static final String Detections_OffEffort_Detection_Image = "Name of image file (spectrogram, etc.)"; + public static final String Detections_OffEffort_Detection_Audio = "Name of audio file (short snippet)"; + public static final String Detections_OffEffort_Detection_Parameters_Subtype = "subcategory of call"; + public static final String Detections_OffEffort_Detection_Parameters_Score = "Measure from detector, e.g. likelihood ratio, projection, etc."; + public static final String Detections_OffEffort_Detection_Parameters_Confidence = "Measure of confidence in detection. Range: [0, 1]"; + public static final String Detections_OffEffort_Detection_Parameters_QualityAssurance = "Detection is: unverified, valid, invalid"; + public static final String Detections_OffEffort_Detection_Parameters_ReceivedLevel_dB = "dB relative to reference intensity defined in Effort/ReferenceIntenstiy_uPa"; + public static final String Detections_OffEffort_Detection_Parameters_FrequencyMeasurements_dB = "List of received levels at various frequencies relative to the reference value defiend in Effort/ReferenceIntensity_uPa. The frequency measurements should be consistent for each species and call type and must correspond to a a list of frequencies defined in Effort/Kind/SubType."; + public static final String Detections_OffEffort_Detection_Parameters_Peaks_Hz = "Typically used for spectra of short echolocation bursts, notes the spectral peaks in a list sorted from low to high frequency."; + public static final String Detections_OffEffort_Detection_Parameters_Duration_s = "When the call granularity is binned or encounter, this may be used to describe the mean duration of calls during the bout. As an example, at SIO we use this to track the mean duration of binned anthropogenic sources such as explosions."; + public static final String Detections_OffEffort_Detection_Parameters_Sideband_Hz = "Signal sideband frequencies in a list sorted from low to high frequency."; + public static final String Detections_OffEffort_Detection_Parameters_EventRef = "Reference to other detections for hierarchical organization."; + public static final String Detections_OffEffort_Detection_Parameters_UserDefined = "Study specific parameters"; + public static final String Detections_OffEffort_Detection_Parameters_Tonal_Offset_s = "List of offsets from start in seconds"; + public static final String Detections_OffEffort_Detection_Parameters_Tonal_Hz = "Frequency measurement for each Offset_s (Hz). List must be of same length as Offset_s"; + public static final String Detections_OffEffort_Detection_Parameters_Tonal_dB = "Optional intensity measurment (dB) for each Offset_s (dB). List must be of the same length as Offset_s"; + public static final String Detections_MetadataInfo_Contact = "based on ISO 19115"; + public static final String Detections_MetadataInfo_Date = "Last update."; + public static final String Detections_MetadataInfo_UpdateFrequency = "How often are these data updated? as-needed, unplanned, or yearly"; + public static final String Detections_MetadataInfo_Contact_contactInfo_onlineResource = "We do not fully conform to the onlineResources of ISO 19115"; + + + // Annotations taken from schemata_csv + public static final String Ensemble_Id = "Ensemble name (unique identifier)."; + public static final String Ensemble_Unit = "Associates a virtual unit of an ensemble with an actual deployment."; + public static final String Ensemble_ZeroPosition = "Provides a zero point to which relative localizations can be referenced."; + public static final String Ensemble_Unit_UnitId = "A unique unit number that identifies this instrument within the ensemble."; + public static final String Ensemble_Unit_DeploymentId = "Reference to a deployment document Id field. Uniquely identifies the deployment."; + public static final String Ensemble_ZeroPosition_Longitude = "Expressed in degrees East [0, 360)"; + public static final String Ensemble_ZeroPosition_Latitude = "Expressed in degrees North [-90, 90]"; + + + // Annotations taken from schemata_csv + public static final String Localize_Id = "Identification string that is unique to all documents of this type"; + public static final String Localize_Description = "Text based description of process."; + public static final String Localize_DataSource = "Indicates the deployment or ensemble from which the process (e.g. detector) derived information."; + public static final String Localize_Algorithm = "Description of an algorithm or process."; + public static final String Localize_QualityAssurance = "Description of quality assurance checks (if any)."; + public static final String Localize_ResponsibleParty = "Person/organization responsible for generating metadata"; + public static final String Localize_UserId = "User that submitted the document."; + public static final String Localize_IntermediateData = "Derived data that is used for the localizations that the user wishes to retain."; + public static final String Localize_MetadataInfo = "Party responsible for this record. Some applications may make this mandatory."; + public static final String Localize_Description_Objectives = "What are the objectives of this effort? Examples: Beamform to increase SNR for detection. Detect every click of a rare species. Verify data quality."; + public static final String Localize_Description_Abstract = "Overview of effort."; + public static final String Localize_Description_Method = "High-level description of the method used."; + public static final String Localize_DataSource_EnsembleId = "Serves as a foreign key into the ensembles collection and must match an Id element in an ensemble document. Ensembles are used to group instruments together for a common purpose (e.g. large aperture array)."; + public static final String Localize_DataSource_DeploymentId = "Serves as a foreign key into the Deployments collection and must match an Id element in a deployment document."; + public static final String Localize_Algorithm_Method = "Text based description of algorithm or citation"; + public static final String Localize_Algorithm_Software = "Name of software that implements the algorithm or supports human analysts. This might be the name of a plug-in or extension module that is part of a larger program or system."; + public static final String Localize_Algorithm_Version = "Software version identifier"; + public static final String Localize_Algorithm_Parameters = "Structured tags to describe parameters used in algorithm execution."; + public static final String Localize_Algorithm_SupportSoftware = "Software required in addition to the algorithm itself, e.g. Matlab, Ishmael, PAMGUARD, Triton, etc."; + public static final String Localize_Algorithm_SupportSoftware_Version = "Software version identifier."; + public static final String Localize_Algorithm_SupportSoftware_Parameters = "Structured tags to describe parameters used in algorithm execution."; + public static final String Localize_QualityAssurance_Description = "Text based description of process."; + public static final String Localize_QualityAssurance_ResponsibleParty = "based on ISO 19115"; + public static final String Localize_QualityAssurance_Description_Objectives = "What are the objectives of this effort? Examples: Beamform to increase SNR for detection. Detect every click of a rare species. Verify data quality."; + public static final String Localize_QualityAssurance_Description_Abstract = "Overview of effort."; + public static final String Localize_QualityAssurance_Description_Method = "High-level description of the method used."; + public static final String Localize_QualityAssurance_ResponsibleParty_contactInfo_onlineResource = "We do not fully conform to the onlineResources of ISO 19115"; + public static final String Localize_ResponsibleParty_contactInfo_onlineResource = "We do not fully conform to the onlineResources of ISO 19115"; + public static final String Localize_Effort_Start = "Time at which we started looking for location information."; + public static final String Localize_Effort_End = "Time at which we stopped looking for location information."; + public static final String Localize_Effort_CoordinateSystem = "What type of localization information is produced?"; + public static final String Localize_Effort_LocalizationType = "Type of localization effort: Bearing, Ranging, Point, Track"; + public static final String Localize_Effort_CoordinateSystem_Type = "How are positions represented? WGS84: global positioning system lat/long, cartesian: m relative to a point, UTM: universal trans Mercatur map-projection, Bearing: Polar measurements of angle and possibly distance."; + public static final String Localize_Effort_CoordinateSystem_Relative = "For bearings, this gives the direction vector for the zero bearing. Angles are measured counter-clockwise to this vector. For cartesian coordinates, this provides the zero point relative to the deployment position or the ReferencePoint of an ensemble."; + public static final String Localize_Effort_CoordinateSystem_UTM = "Parameters for Universal Trans Mercatur projections. NEED TO INVESTIGATE FURTHER AS TO WHETHER THIS OR ANY OTHER PROJECTION IS WORTH ADDING"; + public static final String Localize_Effort_CoordinateSystem_Relative_Bearing = "Designed to be a subset of OpenGML DirectionVectorType: http://schemas.opengis.net/gml/3.1.1/base/direction.xsd Unlike the OpenGML, direction may not be specified as a vector, and the verticalAngle is optional."; + public static final String Localize_Effort_CoordinateSystem_Relative_Bearing_HorizontalAngle = "Angle between a reference vector in the horizontal plane [0, 360]"; + public static final String Localize_Effort_CoordinateSystem_Relative_Bearing_VerticalAngle = "Angle between a reference vector in the vertical plane: [-90, 90]"; + public static final String Localize_Effort_CoordinateSystem_UTM_Zone = "NS zone [1-60]. Each zone covers 80°S to 84°N in 6° width zones. Zone 1 180 is 180-186° E with increasing zone #s corresponding to 6° eastward increments."; + public static final String Localize_Effort_ReferencedDocuments_Document_Type = "What type of document is being referenced? Detections or Localizations"; + public static final String Localize_Effort_ReferencedDocuments_Document_Id = "Unique identifier string for the document being referenced."; + public static final String Localize_Effort_ReferencedDocuments_Document_Index = "All localizations that wish to reference other detections or localizations from the referenced document should use this index value."; + public static final String Localize_IntermediateData_Correlations_Correlation_Primary = "Primary hydropphone"; + public static final String Localize_IntermediateData_Correlations_Correlation_Secondary = "Secondary hydropphone"; + public static final String Localize_IntermediateData_Correlations_Correlation_Correlations = "Correlation between detections on primary hydrophone and signals on secondary hydrophones. Each column j is the set of lags corresponding to the j'th detection on the primary hydrophone."; + public static final String Localize_Localizations_Localization_Event = "Optional tag typically in ISO datetime format YYYY-MM-DDTHH:MM:SSZ identifying this event uniquely within the stream. For human analysts, it is typical to use the time at which the detection was made. When present, the combination of the event and attributes that uniquely identify the set of detections (or document name) must be uniqe."; + public static final String Localize_Localizations_Localization_TimeStamp = "Time of localization in reference time frame (e.g. time of arrival at primary hydrophone)"; + public static final String Localize_Localizations_Localization_SpeciesId = "Species can be identified by the detections from the detections that are referenced. As these references are not mandatory, the optional SpeciesID can be used to identify the species that produced the localized source."; + public static final String Localize_Localizations_Localization_QualityAssurance = "Detection is: unverified, valid, invalid"; + public static final String Localize_Localizations_Localization_Bearing = "Direction towards acoustic source."; + public static final String Localize_Localizations_Localization_Ranging = "Range and direction towards acoustic source. Combine with bearing, rename angular Rename StdError to Error specify in methods/algorrithm"; + public static final String Localize_Localizations_Localization_WGS84 = "Longitude, latitude and possibly elevation of surce."; + public static final String Localize_Localizations_Localization_Cartesian = "Relative distance to source from receiver zero point."; + public static final String Localize_Localizations_Localization_Track = "Series of associated positions and timestamps"; + public static final String Localize_Localizations_Localization_References_TimeReferenceEnsembleUnit = "STILL NEEDED? Time references which unit of the ensemble (see TimeReferenceChannel) when ensembles are used."; + public static final String Localize_Localizations_Localization_References_TimeReferenceChannel = "STILL NEEDED? Events are detected at different times on different channels, making it necessary to provide the instrument and channel on which the timestamp references."; + public static final String Localize_Localizations_Localization_References_Reference = "Detections/localization used in constructing this localization."; + public static final String Localize_Localizations_Localization_References_Reference_Index = "Must match instance of Index in ReferencedDocuments. This permits identification of a specific document."; + public static final String Localize_Localizations_Localization_References_Reference_EventRef = "Event identifier that uniquely identifies a detection or localization within a referenced document."; + public static final String Localize_Localizations_Localization_Bearing_HorizontalAngle = "Angle between a reference vector in the horizontal plane [0, 360]"; + public static final String Localize_Localizations_Localization_Bearing_VerticalAngle = "Angle between a reference vector in the vertical plane: [-90, 90]"; + public static final String Localize_Localizations_Localization_Bearing_Ambiguous = "Left right horizontal ambiguity about the bearing reference vector exists?"; + public static final String Localize_Localizations_Localization_Bearing_StdError = "Standard error in degrees for the measurement."; + public static final String Localize_Localizations_Localization_Bearing_StdError_HorizontalAngle = "Angle between a reference vector in the horizontal plane [0, 360]"; + public static final String Localize_Localizations_Localization_Bearing_StdError_VerticalAngle = "Angle between a reference vector in the vertical plane: [-90, 90]"; + public static final String Localize_Localizations_Localization_Ranging_HorizontalAngle = "Angle between a reference vector in the horizontal plane [0, 360]"; + public static final String Localize_Localizations_Localization_Ranging_VerticalAngle = "Angle between a reference vector in the vertical plane: [-90, 90]"; + public static final String Localize_Localizations_Localization_Ranging_Range_m = "Distance to localized animal/object/phenomenon in meters."; + public static final String Localize_Localizations_Localization_Ranging_Ambiguous = "Left right horizontal ambiguity about the bearing reference vector exists?"; + public static final String Localize_Localizations_Localization_Ranging_StdError_HorizontalAngle = "Angle between a reference vector in the horizontal plane [0, 360]"; + public static final String Localize_Localizations_Localization_Ranging_StdError_VerticalAngle = "Angle between a reference vector in the vertical plane: [-90, 90]"; + public static final String Localize_Localizations_Localization_Ranging_StdError_Range_m = "Distance to localized animal/object/phenomenon in meters."; + public static final String Localize_Localizations_Localization_WGS84_Longitude = "Expressed in degrees East [0, 360)"; + public static final String Localize_Localizations_Localization_WGS84_Latitude = "Expressed in degrees North [-90, 90]"; + public static final String Localize_Localizations_Localization_WGS84_AlternatePosition = "Add LongLat3 and StdError Cartesian"; + public static final String Localize_Localizations_Localization_WGS84_StdError_Longitude = "Expressed in degrees East [0, 360)"; + public static final String Localize_Localizations_Localization_WGS84_StdError_Latitude = "Expressed in degrees North [-90, 90]"; + public static final String Localize_Localizations_Localization_Cartesian_BearingIDs = "If multiple bearings were used to create this localization, their ids can be provided."; + public static final String Localize_Localizations_Localization_Cartesian_Longitude = "Expressed in degrees East [0, 360)"; + public static final String Localize_Localizations_Localization_Cartesian_Latitude = "Expressed in degrees North [-90, 90]"; + public static final String Localize_Localizations_Localization_Cartesian_BearingIDs_EventRef = "Reference to individual bearing within this XML document."; + public static final String Localize_Localizations_Localization_Track_WGS84 = "Series of points or list of values for each type long/lat/depth/elevation/timestamp parameters for each call: receivedLevel_dB sourceLevel_dB ambient_dB"; + public static final String Localize_Localizations_Localization_Track_Cartesian = "Todo: define Cartesian list"; + public static final String Localize_Localizations_Localization_Track_WGS84_Bounds = "Bounding box for tempo-spatial data from northwest to southeast quadrant. If elevation/depth information is available, separate depth boundaries are given as well. Note that longitudes are always degrees east. When a track crosses 0° east, the northwest longitude will be > than the souteast longitude (e.g. a 2° path from 359°E to 1°E)"; + public static final String Localize_Localizations_Localization_Track_WGS84_Bounds_NorthWest_Longitude = "Longitude is expressed in degrees East [0,360)"; + public static final String Localize_Localizations_Localization_Track_WGS84_Bounds_NorthWest_Latitude = "Expressed in degrees North [-90,90]"; + public static final String Localize_Localizations_Localization_Track_WGS84_Bounds_SouthEast_Longitude = "Longitude is expressed in degrees East [0,360)"; + public static final String Localize_Localizations_Localization_Track_WGS84_Bounds_SouthEast_Latitude = "Expressed in degrees North [-90,90]"; + public static final String Localize_MetadataInfo_Contact = "based on ISO 19115"; + public static final String Localize_MetadataInfo_Date = "Last update."; + public static final String Localize_MetadataInfo_UpdateFrequency = "How often are these data updated? as-needed, unplanned, or yearly"; + public static final String Localize_MetadataInfo_Contact_contactInfo_onlineResource = "We do not fully conform to the onlineResources of ISO 19115";} From 3e2de8f5dc4e3daef2e0867038e07725e3623b67 Mon Sep 17 00:00:00 2001 From: Douglas Gillespie <50671166+douggillespie@users.noreply.github.com> Date: Fri, 29 Mar 2024 19:40:09 +0000 Subject: [PATCH 3/3] Small fixes Deal with null in xml settings output Deal with -Infinity going into constrainAngle - however many times you add 360 to -Inf, it remains -Inf, so the function was never returning. --- src/PamController/PamguardVersionInfo.java | 2 +- .../settings/output/xml/PamguardXMLWriter.java | 3 +++ src/PamUtils/PamUtils.java | 12 ++++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/PamController/PamguardVersionInfo.java b/src/PamController/PamguardVersionInfo.java index f579eca6..178b203c 100644 --- a/src/PamController/PamguardVersionInfo.java +++ b/src/PamController/PamguardVersionInfo.java @@ -36,7 +36,7 @@ public class PamguardVersionInfo { /** * Release date */ - static public final String date = "22 March 2024"; + static public final String date = "29 March 2024"; // /** // * Release type - Beta or Core diff --git a/src/PamController/settings/output/xml/PamguardXMLWriter.java b/src/PamController/settings/output/xml/PamguardXMLWriter.java index f74edce0..3f9aa8fd 100644 --- a/src/PamController/settings/output/xml/PamguardXMLWriter.java +++ b/src/PamController/settings/output/xml/PamguardXMLWriter.java @@ -570,6 +570,9 @@ public class PamguardXMLWriter implements PamSettings { */ private Element writeSettings(Document doc, PamSettings pamSettings, Object data, ArrayList objectHierarchy) { + if (data == null) { + return null; + } Element el = doc.createElement("SETTINGS"); el.setAttribute("Type", pamSettings.getUnitType()); el.setAttribute("Name", pamSettings.getUnitName()); diff --git a/src/PamUtils/PamUtils.java b/src/PamUtils/PamUtils.java index 85ce33ba..ce27bcab 100644 --- a/src/PamUtils/PamUtils.java +++ b/src/PamUtils/PamUtils.java @@ -354,6 +354,9 @@ public class PamUtils { * @return output angle (degrees) */ static public double constrainedAngle(double angle) { + if (Double.isInfinite(angle) || Double.isNaN(angle)) { + return angle; + } while (angle >= 360) { angle -= 360; } @@ -369,6 +372,9 @@ public class PamUtils { * @return output angle (radians) */ static public double constrainedAngleR(double angle) { + if (Double.isInfinite(angle) || Double.isNaN(angle)) { + return angle; + } while (angle >= 2*Math.PI) { angle -= 2*Math.PI; } @@ -384,6 +390,9 @@ public class PamUtils { * @return output angle (degrees) */ static public double constrainedAngle(double angle, double maxAngle) { + if (Double.isInfinite(angle) || Double.isNaN(angle)) { + return angle; + } while (angle >= maxAngle) { angle -= 360; } @@ -401,6 +410,9 @@ public class PamUtils { * @return output angle (radians) */ static public double constrainedAngleR(double angle, double maxAngle) { + if (Double.isInfinite(angle) || Double.isNaN(angle)) { + return angle; + } while (angle > maxAngle) { angle -= 2*Math.PI; }