diff --git a/src/PamModel/PamModel.java b/src/PamModel/PamModel.java index 005c16d0..8506197d 100644 --- a/src/PamModel/PamModel.java +++ b/src/PamModel/PamModel.java @@ -473,8 +473,7 @@ final public class PamModel implements PamModelInterface, PamSettings { mi.setToolTipText("Interface to Tethys Database"); mi.setModulesMenuGroup(utilitiesGroup); mi.setMaxNumber(1); - } - + } /* diff --git a/src/generalDatabase/SQLLogging.java b/src/generalDatabase/SQLLogging.java index 154e1df1..1a7dd2e6 100644 --- a/src/generalDatabase/SQLLogging.java +++ b/src/generalDatabase/SQLLogging.java @@ -551,6 +551,71 @@ public abstract class SQLLogging { // } return resultSet; } + + /** + * Find the data point which is closest in time to that given, or null + * returning whatever type of data unit this deals with. + * @param timeMillis + * @return + */ + public PamDataUnit findClosestDataPoint(PamConnection con, long timeMillis) { + + PamCursor pamCursor = loggingCursorFinder.getCursor(con, pamTableDefinition); + + // can't really do any math with the string based dates, so will have to query from + // a few s before the time we want. + PamDataUnit[] beforeNafter = new PamDataUnit[2]; + + SQLTypes sqlTypes = con.getSqlTypes(); + + for (int i = 0; i < 2; i++) { + String clause; + + if (i == 0) { + clause = String.format("WHERE UTC <= %s ORDER BY UTC DESC", sqlTypes.formatDBDateTimeQueryString(timeMillis)); + } + else { + clause = String.format("WHERE UTC >= %s ORDER BY UTC ASC", sqlTypes.formatDBDateTimeQueryString(timeMillis)); + } + + ResultSet result = pamCursor.openReadOnlyCursor(con, clause); + if (result==null) { + return null; + } + + PamTableItem tableItem; + try { + if (result.next()) { + // for (int i = 0; i < pamTableDefinition.getTableItemCount(); i++) { + // tableItem = pamTableDefinition.getTableItem(i); + // tableItem.setValue(result.getObject(i + 1)); + // } + // return true; + boolean ok = transferDataFromResult(con.getSqlTypes(), result); + result.close(); + beforeNafter[i] = createDataUnit(sqlTypes, lastTime, lastLoadIndex); + } + } catch (SQLException ex) { + ex.printStackTrace(); + continue; + } + } + // now pick the closest + if (beforeNafter[0] == null) { + return beforeNafter[1]; + } + if (beforeNafter[1] == null) { + return beforeNafter[0]; + } + long t1 = timeMillis-beforeNafter[0].getTimeMilliseconds(); + long t2 = beforeNafter[1].getTimeMilliseconds()-timeMillis; + if (t1 < t2) { + return beforeNafter[0]; + } + else { + return beforeNafter[1]; + } + } /** * Called when a new database is connected to read the last values back in diff --git a/src/tethys/TethysLocationFuncs.java b/src/tethys/TethysLocationFuncs.java new file mode 100644 index 00000000..fb624fbc --- /dev/null +++ b/src/tethys/TethysLocationFuncs.java @@ -0,0 +1,85 @@ +package tethys; + +import Array.ArrayManager; +import Array.HydrophoneLocator; +import Array.PamArray; +import Array.Streamer; +import GPS.GPSControl; +import GPS.GpsData; +import GPS.GpsDataUnit; +import PamUtils.LatLong; +import PamUtils.PamUtils; +import PamguardMVC.PamDataUnit; +import generalDatabase.DBControlUnit; +import generalDatabase.PamConnection; +import nilus.Deployment; +import nilus.DeploymentRecoveryDetails; + +/** + * Function(s) to get location information for Tethys in the required format. + * @author dg50 + * + */ +public class TethysLocationFuncs { + + + /** + * Get everything we need for a deployment document including the track # + * and the deployment / recovery information. Basically this means we + * have to load the GPS data, then potentially filter it. Slight risk this + * may all be too much for memory, but give it a go by loading GPS data for + * the deployment times. + * @param deployment + */ + public static void getTrackAndPositionData(Deployment deployment) { + long start = TethysTimeFuncs.millisFromGregorianXML(deployment.getDeploymentDetails().getAudioTimeStamp()); + long end = TethysTimeFuncs.millisFromGregorianXML(deployment.getRecoveryDetails().getAudioTimeStamp()); + /* + * Need to load data for GPS, Hydrophones and Streamers datablocks for this time period. Can then use + * the snapshot geomentry classes to do the rest from the array manager ? + */ + boolean ok = true; + ok &= addPositionData(deployment.getDeploymentDetails()); + ok &= addPositionData(deployment.getRecoveryDetails()); + + } + + /** + * Add position data to DeploymentRecoveryDetails. + * @param drd + * @return + */ + public static boolean addPositionData(DeploymentRecoveryDetails drd) { + long timeMillis = TethysTimeFuncs.millisFromGregorianXML(drd.getAudioTimeStamp()); + LatLong pos = getLatLongData(timeMillis); + if (pos == null) { + return false; + } + drd.setLongitude(PamUtils.constrainedAngle(pos.getLongitude(), 360)); + drd.setLatitude(pos.getLatitude()); + drd.setElevationInstrumentM(pos.getHeight()); + drd.setDepthInstrumentM(-pos.getHeight()); + return true; + } + + public static LatLong getLatLongData(long timeMillis) { + // check the array time. + PamArray array = ArrayManager.getArrayManager().getCurrentArray(); + Streamer aStreamer = array.getStreamer(0); + GPSControl gpsControl = GPSControl.getGpsControl(); + PamConnection con = DBControlUnit.findConnection(); + if (gpsControl != null) { +// check GPS data are loaded for times around this. + GpsDataUnit gpsData = (GpsDataUnit) gpsControl.getGpsDataBlock().getLogging().findClosestDataPoint(con, timeMillis); + if (gpsData != null) { + return gpsData.getGpsData(); + } + } + HydrophoneLocator hydrophoneLocator = aStreamer.getHydrophoneLocator(); + if (hydrophoneLocator == null) { + return null; + } + return hydrophoneLocator.getStreamerLatLong(timeMillis); + } + +} diff --git a/src/tethys/TethysTimeFuncs.java b/src/tethys/TethysTimeFuncs.java new file mode 100644 index 00000000..53f6a666 --- /dev/null +++ b/src/tethys/TethysTimeFuncs.java @@ -0,0 +1,39 @@ +package tethys; + +import java.util.GregorianCalendar; + +import javax.xml.datatype.DatatypeConfigurationException; +import javax.xml.datatype.DatatypeFactory; +import javax.xml.datatype.XMLGregorianCalendar; + +public class TethysTimeFuncs { + + /* + * Copied from http://www.java2s.com/Code/Java/Development-Class/ConvertsagiventimeinmillisecondsintoaXMLGregorianCalendarobject.htm + */ + public static XMLGregorianCalendar xmlGregCalFromMillis(long millis) { + try { + final GregorianCalendar calendar = new GregorianCalendar(); + calendar.setTimeInMillis(millis); + return DatatypeFactory.newInstance().newXMLGregorianCalendar( + calendar); + } + catch (final DatatypeConfigurationException ex) { + System.out.println("Unable to convert date '%s' to an XMLGregorianCalendar object"); + return null; + } + } + + /** + * Convert a Gregorian calendar value back to milliseconds. + * @param xmlGregorian + * @return + */ + public static Long millisFromGregorianXML(XMLGregorianCalendar xmlGregorian) { + if (xmlGregorian == null) { + return null; + } + GregorianCalendar gc2 = xmlGregorian.toGregorianCalendar(); + return gc2.getTimeInMillis(); + } +} diff --git a/src/tethys/output/TethysExporter.java b/src/tethys/output/TethysExporter.java index 7542be23..96dc5c71 100644 --- a/src/tethys/output/TethysExporter.java +++ b/src/tethys/output/TethysExporter.java @@ -37,8 +37,11 @@ import generalDatabase.DBSchemaWriter; import generalDatabase.SQLLogging; import metadata.MetaDataContol; import metadata.deployment.DeploymentData; +import nilus.Deployment; import nilus.DeploymentRecoveryDetails; import tethys.TethysControl; +import tethys.TethysLocationFuncs; +import tethys.TethysTimeFuncs; import tethys.dbxml.DBXMLConnect; import tethys.pamdata.TethysDataProvider; import tethys.pamdata.TethysSchema; @@ -145,77 +148,83 @@ public class TethysExporter { /* * A load of notes Katie put in ....654654654 */ - // 1. grab DeploymentRecoveryPair that has deployment details and recovery - // details - // a. this is based on start and end times - // Douglas calculates out dutycycles to only grab the - // 2. loop through the pairs to populate the extra information - // one pair is one deployment - // see below for matching - - // id => unique - // project => project in pamguard - // deploymentId == id - // deploymentAlias => blank - // site => UI addition in pamguard, not done, can be blank - // siteAlias => blank - // cruise => UI addition, optional - // Platform=> UI addition in pamguard - // region => UI addition - // Instrument/Type => UI, array manager details (hydrophone names area) - // Instrument/Id => UI, array manager details - // Instrument/Geometry => in pamguard array manager - // SamplingDetails/Channel - // ChannelNumber => in pamguard, hyrdrophone array - // SensorNumber => in pamguard, - // Start => same as timestamp deployment detail - // End => same as timestamp recovery detail - // Sampling/Regimen (change sample rate, pamgauard doesnt handle, only on, get - // channel info in that loop) - // TimeStamp => start time - // SampleRate_kHz => - // SampleBits => - // Gain (another func call to get gain info) - // DutyCycles => needs to be calculated, not fields in pamguard, have fun - // Douglas - // QualityAssurance => not in pamguard, UI, maybe deployment notes, optional - // Data/Audio (static) - // URI => folder where audio is saved - // Data/Tracks - // Track => GPS datatable (granularity filter) - // TrackId => not unique between deployments, - // TrackEffort - // OnPath => scattered throughout pamguard - // URI => option, check with Shannon on how they are doing deployments - // Sensors/Audio (per hydrophone not quad array) streamer info + individual - // hydrophone data together - // pamguard hydrophone data - // number => hydrophoneId - // sensorId => sensor serial number - // Geometry => array geometry field goes to - // Sensors/Depth - // optional - // Sensors/Sensor - // Number => hydrophoneId in pamguard - // SensorId => addition to UI - // Geometry => array geometry fields - // Type => Hydrophone Type - - // get list of deployment recovery details (start, stop times and lat/long) - // deployment details and recovery details are same structure - // per pair, go through a loop to fill in each deployment -// ArrayList deployRecover = getSamplingDetails(); -// if (deployRecover == null) { -// return false; -// } -// -// for (DeploymentRecoveryPair drd : deployRecover) { -// -// -// -// -// } + //1. grab DeploymentRecoveryPair that has deployment details and recovery details + //a. this is based on start and end times + //Douglas calculates out dutycycles to only grab the + + //2. loop through the pairs to populate the extra information + //one pair is one deployment + //see below for matching + + + //id => unique + //project => project in pamguard + //deploymentId == id + //deploymentAlias => blank + //site => UI addition in pamguard, not done, can be blank + //siteAlias => blank + //cruise => UI addition, optional + //Platform=> UI addition in pamguard + //region => UI addition + //Instrument/Type => UI, array manager details (hydrophone names area) + //Instrument/Id => UI, array manager details + //Instrument/Geometry => in pamguard array manager + //SamplingDetails/Channel + //ChannelNumber => in pamguard, hyrdrophone array + //SensorNumber => in pamguard, + //Start => same as timestamp deployment detail + //End => same as timestamp recovery detail + //Sampling/Regimen (change sample rate, pamgauard doesnt handle, only on, get channel info in that loop) + //TimeStamp => start time + //SampleRate_kHz => + //SampleBits => + //Gain (another func call to get gain info) + //DutyCycles => needs to be calculated, not fields in pamguard, have fun Douglas + //QualityAssurance => not in pamguard, UI, maybe deployment notes, optional + //Data/Audio (static) + //URI => folder where audio is saved + //Data/Tracks + //Track => GPS datatable (granularity filter) + //TrackId => not unique between deployments, + //TrackEffort + //OnPath => scattered throughout pamguard + //URI => option, check with Shannon on how they are doing deployments + //Sensors/Audio (per hydrophone not quad array) streamer info + individual hydrophone data together + //pamguard hydrophone data + //number => hydrophoneId + //sensorId => sensor serial number + //Geometry => array geometry field goes to + //Sensors/Depth + //optional + //Sensors/Sensor + //Number => hydrophoneId in pamguard + //SensorId => addition to UI + //Geometry => array geometry fields + //Type => Hydrophone Type + + + + + + //get list of deployment recovery details (start, stop times and lat/long) + //deployment details and recovery details are same structure + //per pair, go through a loop to fill in each deployment + ArrayList deployRecover = getSamplingDetails(); + if (deployRecover == null) { + return false; + } + + /* + * This will become the main loop over deployment documents + */ + int i = 0; + for (DeploymentRecoveryPair drd : deployRecover) { + + Deployment deployment = createDeploymentDocument(i++, drd); + + + } /* * Call some general export function @@ -241,6 +250,18 @@ public class TethysExporter { return true; } + + private Deployment createDeploymentDocument(int i, DeploymentRecoveryPair drd) { + Deployment deployment = new Deployment(); + deployment.setDeploymentDetails(drd.deploymentDetails); + deployment.setRecoveryDetails(drd.recoveryDetails); + + TethysLocationFuncs.getTrackAndPositionData(deployment); + + + + return deployment; + } /** * find Deployment data. This is stored in a separate PAMGuard module, which may @@ -348,9 +369,9 @@ public class TethysExporter { // just load everything. Probably OK for the acqusition, but will bring down daqInfoDataBlock.loadViewerData(0, Long.MAX_VALUE, null); ArrayList allStatusData = daqInfoDataBlock.getDataCopy(); + long dataStart = Long.MAX_VALUE; + long dataEnd = Long.MIN_VALUE; if (allStatusData != null && allStatusData.size() > 0) { - long dataStart = Long.MAX_VALUE; - long dataEnd = Long.MIN_VALUE; // find the number of times it started and stopped .... int nStart = 0, nStop = 0, nFile = 0; for (DaqStatusDataUnit daqStatus : allStatusData) { @@ -392,8 +413,22 @@ public class TethysExporter { //// for () // } - return null; + DeploymentRecoveryPair pair = new DeploymentRecoveryPair(); + DeploymentRecoveryDetails deployment = new DeploymentRecoveryDetails(); + DeploymentRecoveryDetails recovery = new DeploymentRecoveryDetails(); + pair.deploymentDetails = deployment; + pair.recoveryDetails = recovery; + + deployment.setTimeStamp(TethysTimeFuncs.xmlGregCalFromMillis(dataStart)); + deployment.setAudioTimeStamp(TethysTimeFuncs.xmlGregCalFromMillis(dataStart)); + recovery.setTimeStamp(TethysTimeFuncs.xmlGregCalFromMillis(dataEnd)); + recovery.setAudioTimeStamp(TethysTimeFuncs.xmlGregCalFromMillis(dataEnd)); + + ArrayList drPairs = new ArrayList<>(); + drPairs.add(pair); + return drPairs; + } /**