From 58dd967d8fa61276b09d2ec21570d3e2cec9e58c Mon Sep 17 00:00:00 2001 From: Lewin Probst <info@emirror.de> Date: Fri, 1 May 2020 16:48:38 +0200 Subject: [PATCH] Introduced profile2rfb converter. The nemo2profile library has been integrated to enable this feature. --- VERSION | 2 +- .../+controller/+file/+profile2rfb/close.m | 27 + .../+controller/+file/+profile2rfb/open.m | 18 + lib/+artoa/+controller/+main/open.m | 1 + lib/+artoa/+gui/+file/convertProfilesToRfb.m | 202 ++++ lib/+artoa/+gui/main.m | 15 + .../+nemo/+analysis/create_csv_table.m | 127 +++ .../+nemo/+analysis/create_html_summaries.m | 197 ++++ .../+convert/combined_float2nemostruct.m | 970 ++++++++++++++++++ .../nemo2profilelib/+nemo/+convert/float.m | 969 +++++++++++++++++ .../+nemo/+convert/optimare2nemostruct.m | 48 + .../nemo2profilelib/+nemo/+float/collect.m | 192 ++++ .../+nemo/+float/extract_startup_message.m | 60 ++ .../+nemo/+float/fetch_process_mails.m | 202 ++++ .../nemo2profilelib/+nemo/+float/find.m | 49 + .../+nemo/+float/get_changed_profiles.m | 57 + .../+nemo/+float/process_mission_parameter.m | 127 +++ .../+nemo/+float/save_startup_message.m | 107 ++ .../+nemo/+float/sort_by_profile_number.m | 34 + .../+nemo/+float/sort_profile_data.m | 26 + .../nemo2profilelib/+nemo/+ftp/upload.m | 137 +++ .../+nemo/+load/additional_content.m | 29 + .../nemo2profilelib/+nemo/+load/config.m | 110 ++ lib/vendor/nemo2profilelib/+nemo/+load/data.m | 87 ++ .../+nemo/+load/float_metadata.m | 34 + .../+nemo/+load/float_transmission_info.m | 48 + .../+nemo/+load/floats2nemostruct.m | 56 + .../+nemo/+load/latest_profile.m | 46 + .../nemo2profilelib/+nemo/+load/moorings.m | 103 ++ .../nemo2profilelib/+nemo/+load/rfb_config.m | 101 ++ .../+nemo/+load/single_float.m | 40 + .../nemo2profilelib/+nemo/+load/xml_file.m | 61 ++ .../nemo2profilelib/+nemo/+mail/connect.m | 62 ++ .../+nemo/+mail/export_attachment.m | 50 + .../nemo2profilelib/+nemo/+mail/extract.m | 63 ++ .../+nemo/+mail/fetch_messages.m | 69 ++ .../+nemo/+mail/fetch_unread.m | 60 ++ .../+nemo/+mail/process_inline.m | 84 ++ .../+nemo/+optimare/+cnv/devicedata.m | 65 ++ .../+nemo/+optimare/+cnv/phext_t2.m | 285 +++++ .../+nemo/+optimare/+cnv/press.m | 30 + .../+nemo/+optimare/+cnv/salt.m | 19 + .../+nemo/+optimare/+cnv/temp.m | 31 + .../+nemo/+optimare/decode_nemo.m | 316 ++++++ .../+nemo/+optimare/process_moms.m | 340 ++++++ .../+nemo/+optimare/readSBDmsg.m | 55 + .../+nemo/+optimare/read_sensors.m | 75 ++ .../+nemo/+optimare/stat2str.m | 70 ++ .../+nemo/+plot/+templates/create_south.m | 157 +++ .../+nemo/+plot/arrival_of_profiles.m | 261 +++++ .../+plot/arrival_of_profiles_overview.m | 232 +++++ .../nemo2profilelib/+nemo/+plot/ascent.m | 207 ++++ .../+nemo/+plot/categorized_timings.m | 327 ++++++ .../+nemo/+plot/float_to_template_south.m | 137 +++ .../nemo2profilelib/+nemo/+plot/floats.m | 128 +++ .../nemo2profilelib/+nemo/+plot/rafos.m | 186 ++++ .../nemo2profilelib/+nemo/+plot/temp_sal.m | 191 ++++ .../+nemo/+plot/temp_sal_ptemp.m | 211 ++++ .../+nemo/+plot/timing_information.m | 507 +++++++++ .../add_software_profile_times2rtc.m | 100 ++ .../+datetime/create_best_guess.m | 148 +++ .../+position/create_best_guess.m | 53 + .../+position/estimate_best_guess.m | 142 +++ .../+position/estimate_unknowns.m | 185 ++++ .../+nemo/+postprocessing/delete_profile.m | 68 ++ .../+nemo/+postprocessing/quality_check.m | 105 ++ .../+nemo/+postprocessing/sort_profile_data.m | 27 + .../+profile/calc_rtc_software_difference.m | 40 + .../nemo2profilelib/+nemo/+profile/compare.m | 79 ++ .../nemo2profilelib/+nemo/+profile/find.m | 19 + .../nemo2profilelib/+nemo/+profile/get_gps.m | 32 + .../+nemo/+profile/get_iridium.m | 32 + .../+profile/get_rtc_datevec_of_ctd_sample.m | 84 ++ .../+nemo/+profile/get_status.m | 13 + .../+nemo/+profile/has_best_guess.m | 36 + .../nemo2profilelib/+nemo/+profile/has_gps.m | 31 + .../+nemo/+profile/has_iridium.m | 31 + .../+nemo/+profile/has_no_gps_but_iridium.m | 52 + .../+nemo/+profile/has_no_iridium_but_gps.m | 31 + .../+nemo/+profile/has_status.m | 36 + .../+profile/has_status_and_coordinates.m | 56 + .../+nemo/+profile/has_status_and_gps.m | 42 + .../nemo2profilelib/+nemo/+profile/load.m | 165 +++ .../nemo2profilelib/+nemo/+profile/map_to.m | 75 ++ .../+nemo/+profile/positions_differ_by.m | 38 + .../nemo2profilelib/+nemo/+profile/save.m | 179 ++++ .../+nemo/+profile/sort_profile_data.m | 27 + .../+nemo/+profile/verify_iridium.m | 38 + .../nemo2profilelib/+nemo/+rfb/create.m | 239 +++++ .../+nemo/+rfb/create_and_save_collection.m | 49 + lib/vendor/nemo2profilelib/+nemo/+rfb/save.m | 174 ++++ .../nemo2profilelib/+nemo/+sbd/decode_float.m | 41 + .../+nemo/+test/best_guess_nan.m | 23 + .../+nemo/+test/compare_nemo_data.m | 16 + .../+nemo/+test/mail_development.m | 24 + .../+nemo/+test/process_nemo.m | 82 ++ .../+nemo/+test/sbd2nemostruct.m | 7 + .../+nemo/find_float_in_metadata.m | 50 + .../+nemo/get_data_with_setting.m | 35 + .../+nemo/get_profiles_in_polygon.m | 88 ++ .../+nemo/get_profiles_in_timerange.m | 87 ++ lib/vendor/nemo2profilelib/+nemo/save_data.m | 101 ++ .../+nemo/save_startup_messages.m | 29 + .../+nemo/sort_data_by_float_serial.m | 36 + .../+nemo/sync_metadata_to_nemodata.m | 16 + .../+nemo/update_nemo_data_mat.m | 44 + lib/vendor/nemo2profilelib/+utils/suplabel.m | 101 ++ lib/vendor/nemo2profilelib/+utils/xml_read.m | 550 ++++++++++ lib/vendor/nemo2profilelib/+utils/xml_write.m | 447 ++++++++ lib/vendor/nemo2profilelib/exclude_elements.m | 38 + lib/vendor/nemo2profilelib/exclude_files.m | 30 + 111 files changed, 12770 insertions(+), 1 deletion(-) create mode 100644 lib/+artoa/+controller/+file/+profile2rfb/close.m create mode 100644 lib/+artoa/+controller/+file/+profile2rfb/open.m create mode 100644 lib/+artoa/+gui/+file/convertProfilesToRfb.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+analysis/create_csv_table.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+analysis/create_html_summaries.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+convert/combined_float2nemostruct.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+convert/float.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+convert/optimare2nemostruct.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+float/collect.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+float/extract_startup_message.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+float/fetch_process_mails.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+float/find.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+float/get_changed_profiles.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+float/process_mission_parameter.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+float/save_startup_message.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+float/sort_by_profile_number.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+float/sort_profile_data.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+ftp/upload.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+load/additional_content.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+load/config.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+load/data.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+load/float_metadata.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+load/float_transmission_info.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+load/floats2nemostruct.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+load/latest_profile.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+load/moorings.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+load/rfb_config.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+load/single_float.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+load/xml_file.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+mail/connect.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+mail/export_attachment.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+mail/extract.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+mail/fetch_messages.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+mail/fetch_unread.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+mail/process_inline.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/devicedata.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/phext_t2.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/press.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/salt.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/temp.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+optimare/decode_nemo.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+optimare/process_moms.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+optimare/readSBDmsg.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+optimare/read_sensors.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+optimare/stat2str.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+plot/+templates/create_south.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+plot/arrival_of_profiles.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+plot/arrival_of_profiles_overview.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+plot/ascent.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+plot/categorized_timings.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+plot/float_to_template_south.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+plot/floats.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+plot/rafos.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+plot/temp_sal.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+plot/temp_sal_ptemp.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+plot/timing_information.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+postprocessing/+datetime/add_software_profile_times2rtc.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+postprocessing/+datetime/create_best_guess.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+postprocessing/+position/create_best_guess.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+postprocessing/+position/estimate_best_guess.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+postprocessing/+position/estimate_unknowns.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+postprocessing/delete_profile.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+postprocessing/quality_check.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+postprocessing/sort_profile_data.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+profile/calc_rtc_software_difference.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+profile/compare.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+profile/find.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+profile/get_gps.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+profile/get_iridium.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+profile/get_rtc_datevec_of_ctd_sample.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+profile/get_status.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+profile/has_best_guess.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+profile/has_gps.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+profile/has_iridium.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+profile/has_no_gps_but_iridium.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+profile/has_no_iridium_but_gps.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+profile/has_status.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+profile/has_status_and_coordinates.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+profile/has_status_and_gps.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+profile/load.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+profile/map_to.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+profile/positions_differ_by.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+profile/save.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+profile/sort_profile_data.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+profile/verify_iridium.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+rfb/create.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+rfb/create_and_save_collection.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+rfb/save.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+sbd/decode_float.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+test/best_guess_nan.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+test/compare_nemo_data.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+test/mail_development.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+test/process_nemo.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/+test/sbd2nemostruct.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/find_float_in_metadata.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/get_data_with_setting.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/get_profiles_in_polygon.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/get_profiles_in_timerange.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/save_data.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/save_startup_messages.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/sort_data_by_float_serial.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/sync_metadata_to_nemodata.m create mode 100644 lib/vendor/nemo2profilelib/+nemo/update_nemo_data_mat.m create mode 100644 lib/vendor/nemo2profilelib/+utils/suplabel.m create mode 100644 lib/vendor/nemo2profilelib/+utils/xml_read.m create mode 100644 lib/vendor/nemo2profilelib/+utils/xml_write.m create mode 100644 lib/vendor/nemo2profilelib/exclude_elements.m create mode 100644 lib/vendor/nemo2profilelib/exclude_files.m diff --git a/VERSION b/VERSION index 0947c33..66321c0 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -188 \ No newline at end of file +189 \ No newline at end of file diff --git a/lib/+artoa/+controller/+file/+profile2rfb/close.m b/lib/+artoa/+controller/+file/+profile2rfb/close.m new file mode 100644 index 0000000..945ffe8 --- /dev/null +++ b/lib/+artoa/+controller/+file/+profile2rfb/close.m @@ -0,0 +1,27 @@ +function [ ] = close(pSource, ~) +%CLOSE Cleans up all variables that are used by the pSource GUI. +% Changes to the data is not being modified. + +global artoaGui; + +%% Setup variables +figureName = 'convertProfilesToRfb'; + +%% Close the figure +if nargin > 1 && isvalid(pSource) + delete(pSource); +elseif isfield(artoaGui.figures, figureName) && isvalid(artoaGui.figures.(figureName)) + delete(artoaGui.figures.(figureName)); +end + +%% Clean up variables + +if artoa.data.hasMember(artoaGui, {figureName}) + artoaGui = rmfield(artoaGui, figureName); +end + +if artoa.data.hasMember(artoaGui, {'figures', figureName}) + artoaGui.figures = rmfield(artoaGui.figures, figureName); +end + +end \ No newline at end of file diff --git a/lib/+artoa/+controller/+file/+profile2rfb/open.m b/lib/+artoa/+controller/+file/+profile2rfb/open.m new file mode 100644 index 0000000..77a59bc --- /dev/null +++ b/lib/+artoa/+controller/+file/+profile2rfb/open.m @@ -0,0 +1,18 @@ +function [] = open(~, ~) +%OPEN Summary of this function goes here +% Detailed explanation goes here + +global artoaGui; + +%% Check if the gui is already opened + +if isfield(artoaGui.figures, 'convertProfilesToRfb') + figure(artoaGui.figures.convertProfilesToRfb); + return +end + +%% Open the gui +artoa.gui.file.convertProfilesToRfb(); + +end + diff --git a/lib/+artoa/+controller/+main/open.m b/lib/+artoa/+controller/+main/open.m index 3410aa5..881d1d0 100644 --- a/lib/+artoa/+controller/+main/open.m +++ b/lib/+artoa/+controller/+main/open.m @@ -30,6 +30,7 @@ callbacks.openEditOffsets = @artoa.controller.edit.offsets.open; callbacks.openTrackParameter = @artoa.controller.track.parameter.open; callbacks.openTrackTrajectoryOutput = @artoa.controller.track.trajectoryOutput.open; callbacks.switchHideDeletedDataPoints = @artoa.controller.switchHideDeletedPoints; +callbacks.convertProfilesToRfb = @artoa.controller.file.profile2rfb.open; %% Prepare callbacks for track parameter callbacks.checkboxDopplerCorrection = @artoa.controller.track.parameter.checkboxDopplerCorrection; diff --git a/lib/+artoa/+gui/+file/convertProfilesToRfb.m b/lib/+artoa/+gui/+file/convertProfilesToRfb.m new file mode 100644 index 0000000..2783704 --- /dev/null +++ b/lib/+artoa/+gui/+file/convertProfilesToRfb.m @@ -0,0 +1,202 @@ +function [] = convertProfilesToRfb() +%CONVERTPROFILESTORFB Defines the profile to rfb conversion tool. + +global artoaGui; + +%% Initialize required variables + +windowTitle = 'ARTOA4 - Profile to RFB converter'; + + +%% Initialize gui + +artoaGui.figures.convertProfilesToRfb = figure( ... + 'Name', windowTitle, ... + 'NumberTitle', 'off' ... +); + + +artoaGui.convertProfilesToRfb = struct(); + +set( ... + artoaGui.figures.convertProfilesToRfb, ... + 'CloseRequestFcn', ... + @artoa.controller.file.profile2rfb.close, ... + 'Units', 'normalized' ... +); + +%% Generate Controls + +left = .05; +width = .9; + +%% Source frame +artoaGui.convertProfilesToRfb.frameSource = uipanel( ... + 'Parent', artoaGui.figures.convertProfilesToRfb, ... + 'Title', 'Source folder (*.profiles)', ... + 'Units', 'normalized', ... + 'BackgroundColor', 'white', ... + 'Position', [left .65 width .3] ... +); + +artoaGui.convertProfilesToRfb.buttonSelectFolder = uicontrol( ... + 'Parent', artoaGui.convertProfilesToRfb.frameSource, ... + 'String', 'Pick a folder', ... + 'Style', 'PushButton', ... + 'FontSize', 8, ... + 'Units', 'normalized', ... + 'Position', [.7 .5 .25 .3], ... + 'Callback', @selectSourceFolder ... +); + +artoaGui.convertProfilesToRfb.textSourceFolder = uicontrol( ... + 'Parent', artoaGui.convertProfilesToRfb.frameSource, ... + 'Style', 'edit', ... + 'FontSize', 8, ... + 'Units', 'normalized', ... + 'Enable', 'off', ... + 'Position', [.05 .5 .6 .3] ... +); + +artoaGui.convertProfilesToRfb.buttonSelectMetadata = uicontrol( ... + 'Parent', artoaGui.convertProfilesToRfb.frameSource, ... + 'String', 'Pick metadata file', ... + 'Style', 'PushButton', ... + 'FontSize', 8, ... + 'Units', 'normalized', ... + 'Position', [.7 .1 .25 .3], ... + 'Callback', @selectSourceMetadata ... +); + +artoaGui.convertProfilesToRfb.textSourceMetadata = uicontrol( ... + 'Parent', artoaGui.convertProfilesToRfb.frameSource, ... + 'Style', 'edit', ... + 'FontSize', 8, ... + 'Units', 'normalized', ... + 'Enable', 'off', ... + 'Position', [.05 .1 .6 .3] ... +); + +%% Target frame +artoaGui.convertProfilesToRfb.frameTarget = uipanel( ... + 'Parent', artoaGui.figures.convertProfilesToRfb, ... + 'Title', 'Target file (*.rfb)', ... + 'Units', 'normalized', ... + 'BackgroundColor', 'white', ... + 'Position', [left .4 width .2] ... +); + +artoaGui.convertProfilesToRfb.buttonSelectTargetFile = uicontrol( ... + 'Parent', artoaGui.convertProfilesToRfb.frameTarget, ... + 'String', 'Select a file', ... + 'Style', 'PushButton', ... + 'FontSize', 8, ... + 'Units', 'normalized', ... + 'Position', [.7 .4 .25 .4], ... + 'Callback', @selectTargetFile ... +); + +artoaGui.convertProfilesToRfb.textTargetFile = uicontrol( ... + 'Parent', artoaGui.convertProfilesToRfb.frameTarget, ... + 'Style', 'edit', ... + 'FontSize', 8, ... + 'Units', 'normalized', ... + 'Enable', 'off', ... + 'Position', [.05 .4 .6 .4] ... +); + +%% Execute frame +artoaGui.convertProfilesToRfb.frameExecute = uipanel( ... + 'Parent', artoaGui.figures.convertProfilesToRfb, ... + 'Title', 'Convert', ... + 'Units', 'normalized', ... + 'BackgroundColor', 'white', ... + 'Position', [left .1 width .2] ... +); + +artoaGui.convertProfilesToRfb.textStatus = uicontrol( ... + 'Parent', artoaGui.convertProfilesToRfb.frameExecute, ... + 'String', 'Please input source and target...', ... + 'Style', 'Text', ... + 'FontSize', 8, ... + 'Units', 'normalized', ... + 'Position', [.1 .4 .35 .4] ... +); + +artoaGui.convertProfilesToRfb.buttonExecuteConversion = uicontrol( ... + 'Parent', artoaGui.convertProfilesToRfb.frameExecute, ... + 'String', 'Start conversion', ... + 'Style', 'PushButton', ... + 'FontSize', 8, ... + 'Units', 'normalized', ... + 'Enable', 'off', ... + 'Position', [.5 .4 .3 .4], ... + 'Callback', @convert ... +); + +%% Helper functions + + function selectSourceFolder(~, ~) + dirname = uigetdir(pwd, 'Select source folder'); + if (dirname == 0) + return + end + artoaGui.convertProfilesToRfb.textSourceFolder.String = dirname; + checkInputs(); + end + + function selectSourceMetadata(~, ~) + [filename, path] = uigetfile('*.xml', 'Select metadata file'); + if (filename == 0) + return + end + artoaGui.convertProfilesToRfb.textSourceMetadata.String = ... + fullfile(path, filename); + checkInputs(); + end + + function selectTargetFile(~, ~) + [filename, path] = uiputfile('*.rfb', 'Select target file'); + if (filename == 0) + return + end + artoaGui.convertProfilesToRfb.textTargetFile.String = ... + fullfile(path, filename); + checkInputs(); + end + + function checkInputs(~, ~) + if all(strcmp({ ... + artoaGui.convertProfilesToRfb.textSourceFolder.String, ... + artoaGui.convertProfilesToRfb.textTargetFile.String, ... + artoaGui.convertProfilesToRfb.textSourceMetadata.String ... + }, '') == 0) + artoaGui.convertProfilesToRfb.buttonExecuteConversion.Enable = 'on'; + setStatus('Ready for conversion!'); + else + artoaGui.convertProfilesToRfb.buttonExecuteConversion.Enable = 'off'; + setStatus('Please input source and target...'); + end + end + + function setStatus(pStatusMessage) + artoaGui.convertProfilesToRfb.textStatus.String = pStatusMessage; + end + + function convert(~, ~) + setStatus('Loading files...'); + metadata = artoaGui.convertProfilesToRfb.textSourceMetadata.String; + inputFolder = artoaGui.convertProfilesToRfb.textSourceFolder.String; + target = artoaGui.convertProfilesToRfb.textTargetFile.String; + float = nemo.load.single_float(inputFolder, filesep); + setStatus('Creating rfb structure...'); + [rfb, ~] = nemo.rfb.create(float, nemo.load.float_metadata(metadata)); + setStatus('Save to target...'); + if ~nemo.rfb.save(rfb, target, true) + setStatus('Something failed during conversion!'); + end + setStatus('...success!'); + end + +end + diff --git a/lib/+artoa/+gui/main.m b/lib/+artoa/+gui/main.m index 2a3c4d9..3168510 100644 --- a/lib/+artoa/+gui/main.m +++ b/lib/+artoa/+gui/main.m @@ -15,6 +15,7 @@ availableCallbacks = { ... 'saveInterim', ... 'saveOptimumTables', ... 'loadArtoaIni', ... + 'convertProfilesToRfb', ... 'quit', ... 'openEditTemperature', ... 'openEditPressure', ... @@ -135,6 +136,20 @@ uimenu( ... 'Callback', pCallbacks.saveOptimumTables ... ); +% CONVERT +artoaGui.main.menus.fileConvert = uimenu( ... + artoaGui.main.menus.file, ... + 'Label', 'Convert', ... + 'Enable', 'on' ... +); + +uimenu( ... + artoaGui.main.menus.fileConvert, ... + 'Label', '*.profile to .rfb', ... + 'Callback', pCallbacks.convertProfilesToRfb ... +); + +% RELOAD ARTOA INI artoaGui.main.menus.fileReloadArtoaIni = uimenu( ... artoaGui.main.menus.file, ... 'Label', 'Reload artoa.ini', ... diff --git a/lib/vendor/nemo2profilelib/+nemo/+analysis/create_csv_table.m b/lib/vendor/nemo2profilelib/+nemo/+analysis/create_csv_table.m new file mode 100644 index 0000000..4bb30a8 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+analysis/create_csv_table.m @@ -0,0 +1,127 @@ +function [ boolean ] = create_csv_table( p_metadata, p_config ) +%CREATE_CSV_TABLE Creates a csv file containing table with information of all floats. +% +% Parameters: +% p_metadata: Struct containing all floats (load with nemo.load.data) +% p_config: Struct containing global configuration file (load with +% nemo.load.config()). +% +% Returns: +% boolean: False if something went wrong during the creation of the table. + +%% Initialize return variable +boolean = false; + +%% Parameter check + +if (~iscell(p_metadata) || ~isstruct(p_config)) + disp([mfilename ': Wrong parameter. Please check your input!']); + return +end + +%% Collect all required information + +serial = []; +imei = []; +wmoid = []; +deployment_datetime = []; +deployment_lat = []; +deployment_lon = []; +profile_count = []; +latest_transmission = []; + +float_count = length(p_metadata); + +for o_float = 1:float_count % collect all required information of every float + % load the latest float + [current_profile, current_profile_count] = nemo.load.latest_profile( ... + p_config, ... + p_metadata{o_float}.GENERAL_INFO.SERIAL ... + ); + + % get all numbers of nemo floats + serial(end+1, 1) = p_metadata{o_float}.GENERAL_INFO.SERIAL; + imei(end+1, 1) = p_metadata{o_float}.GENERAL_INFO.IMEI; + wmoid(end+1, 1) = p_metadata{o_float}.GENERAL_INFO.WMO_ID; + % deploment info + deployment_datetime(end+1, :) = datevec( ... + [ ... + p_metadata{o_float}.DEPLOYMENT_INFO.DEPLOYMENT_DATE ... + p_metadata{o_float}.DEPLOYMENT_INFO.DEPLOYMENT_TIME ... + ], 'dd.mm.yyyyHH:MM'); + + +% +% [ ... +% p_metadata{o_float}.DEPLOYMENT_INFO.deployment_date(3), ... +% current_profile.DEPLOYMENT_INFO.deployment_date(2), ... +% current_profile.DEPLOYMENT_INFO.deployment_date(1), ... +% current_profile.DEPLOYMENT_INFO.deployment_time, ... +% 0]; + deployment_lat(end+1, 1) = p_metadata{o_float}.DEPLOYMENT_INFO.DEPLOYMENT_LAT; + deployment_lon(end+1, 1) = p_metadata{o_float}.DEPLOYMENT_INFO.DEPLOYMENT_LON; + % number of received profiles + profile_count(end+1, 1) = current_profile_count; + % last float transmittion + if ~isempty(current_profile) && ~isempty(current_profile.IRIDIUM_DATA) + latest_transmission(end+1, :) = current_profile.IRIDIUM_DATA(4:9); + else + latest_transmission(end+1, :) = nan(1, 6); + end + +end + +% create filename +filename = fullfile( ... + p_config.ANALYSIS_RESULTS.output_path, ... + p_config.ANALYSIS_RESULTS.compact_info_filename ... + ); + +% create folder if necessary +if ~exist(p_config.ANALYSIS_RESULTS.output_path, 'dir') + mkdir(p_config.ANALYSIS_RESULTS.output_path); +end + +% create header of table +table_header = { ... + 'serial', 'imei', 'wmo_id', 'deployment_time', 'deployment_lat', 'deployment_lon', ... + 'latest_transmission', 'profile_count' ... +}; + +% create matlab table with the collected information +compact_info = table(serial, imei, wmoid, datetime(deployment_datetime, 'Format', 'yyyy-MM-dd HH:m'), deployment_lat, deployment_lon, ... + datetime(latest_transmission, 'Format', 'yyyy-MM-dd HH:mm'), profile_count, 'VariableNames', table_header); + +% write data to file +writetable(compact_info, filename, 'Delimiter', ','); +try +% copy template file to csv folder +copyfile(p_config.ANALYSIS_RESULTS.compact_info_html_template, [ ... + p_config.ANALYSIS_RESULTS.output_path ... + 'index.html' ... +]); +% update its timestamp via java +import java.io.File java.text.SimpleDateFormat +f = File([ p_config.ANALYSIS_RESULTS.output_path 'index.html' ]); +sdf = SimpleDateFormat('HH:mm dd/MM/yyyy'); +today = datetime('now'); +newDate = sdf.parse([ ... + int2str(today.Hour) ':' int2str(today.Minute) ' ' ... + int2str(today.Day) '/' int2str(today.Month) '/' int2str(today.Year) ... +]); +f.setLastModified(newDate.getTime); +clear f; +% copy javascript folder +copyfile(p_config.ANALYSIS_RESULTS.input_javascript_folder, ... + [p_config.ANALYSIS_RESULTS.output_path 'js' p_config.GENERAL.folder_separator] ... +); +catch ex +warning('Could not copy templates or JavaScript folder. If you experience Problems try deleting those files/folders and rerun!'); +boolean = false; +return; +end + +% everything went fine! :) +boolean = true; + +end diff --git a/lib/vendor/nemo2profilelib/+nemo/+analysis/create_html_summaries.m b/lib/vendor/nemo2profilelib/+nemo/+analysis/create_html_summaries.m new file mode 100644 index 0000000..af40e37 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+analysis/create_html_summaries.m @@ -0,0 +1,197 @@ +function [ boolean ] = create_html_summaries( p_nemo_data, p_config ) +%CREATE_HTML_SUMMARY Generates a summary of every float given in the nemo data. +% Uses the nemo configuration for linking the html summaries to the +% overview table generated by create_csv_table +% +% Parameters: +% p_nemo_data: The nemo data where the summaries will be +% generated from. +% p_config: The nemo configuration +% +% Returns: +% boolean: Always true. + +path = fullfile(p_config.ANALYSIS_RESULTS.output_path, 'html'); + +if (~exist(path, 'dir')) + mkdir(path); +end + +for i = 1:length(p_nemo_data) + + f = p_nemo_data{i}; + float_number = f{1}.FLOAT_IDENTIFICATION.float_serial_number; + html = ['<!DOCTYPE html><head><title>Summary of Float ' ... + num2str(float_number) ... + '</title><style>' ... + 'body, a{font-family: Arial;color: #4B4B4D;}' ... + 'th{line-height: 60px;background: #00ACE5;color: #003E6E;font-size: 30px;}' ... + 'td{line-height: 40px;border: thin solid black; padding-left: 5px;}' ... + '.value{width: 70%;}' ... + '.name{width: 30%;background: #BCBDBF;font-weight: bold;}' ... + 'table{border-collapse: collapse;border: thin solid black;width: 100%;}' ... + 'img{width: 100%;font-weight: bold;font-size: 30px;}' ... + '.overlay{z-index: 2;}' ... + '#show-floattable{padding:20px;background: #00ACE5;color: #003E6E;font-size: 18px;position: fixed;right: 20px;top: 10px;cursor: pointer;}' ... + '</style>' ... + '</head><body>' ... + '<a id="show-floattable" class="overlay" onclick="(function(){history.back();})()">Show all floats</a>' ... + '<table>']; + + + %% Add latest update time + html = [ html '<tr><td class="name">Updated on:</td><td class="value">' datestr(datetime('now', 'TimeZone', 'Europe/London')) '</td></tr>']; + + + %% Add float identification table + html = [ html '<th colspan="2">FLOAT_IDENTIFICATION</th>' ]; + + fnames = fieldnames(f{1}.FLOAT_IDENTIFICATION); + for j = 1:length(fnames) + + fieldvalue = f{1}.FLOAT_IDENTIFICATION.(fnames{j}); + + switch(class(fieldvalue)) + case {'double', 'logical'} + fieldvalue = num2str(fieldvalue); + end + html = [ html ... + '<tr><td class="name">' fnames{j} ... + '</td><td class="value">' fieldvalue ... + '</td></tr>' ... + ]; + end + + %% Add float deployment table + html = [ html '<th colspan="2">DEPLOYMENT_INFO</th>' ]; + + fnames = fieldnames(f{1}.DEPLOYMENT_INFO); + for j = 1:length(fnames) + + fieldvalue = f{1}.DEPLOYMENT_INFO.(fnames{j}); + + switch(class(fieldvalue)) + case {'double', 'logical'} + fieldvalue = num2str(fieldvalue); + end + html = [ html ... + '<tr><td class="name">' fnames{j} ... + '</td><td class="value">' fieldvalue ... + '</td></tr>' ... + ]; + end + + %% Add float deployment table + html = [ html '<th colspan="2">OVERALL_MISSION_INFORMATION</th>' ]; + + fnames = fieldnames(f{1}.OVERALL_MISSION_INFORMATION); + for j = 1:length(fnames) + + fieldvalue = f{1}.OVERALL_MISSION_INFORMATION.(fnames{j}); + + switch(class(fieldvalue)) + case {'double', 'logical'} + fieldvalue = num2str(fieldvalue); + end + html = [ html ... + '<tr><td class="name">' fnames{j} ... + '</td><td class="value">' fieldvalue ... + '</td></tr>' ... + ]; + end + + %% Add plots + + html = [ html '<th colspan="2">PLOTS</th>' ]; + + html = [ html '<tr><td class="name">Arrival of profiles</td>' ... + '<td class="value"><a href="../../plots/' ... + p_config.PLOT_ARRIVAL_OF_PROFILES.storage_directory_fig ... + p_config.PLOT_ARRIVAL_OF_PROFILES.figure_filename ... + num2str(float_number) '.fig">' ... + '<img alt="No image available!" src="../../plots/' ... + p_config.PLOT_ARRIVAL_OF_PROFILES.storage_directory_image ... + p_config.PLOT_ARRIVAL_OF_PROFILES.image_filename ... + num2str(float_number) '.png"></a>' ... + '</td></tr>']; + html = [ html '<tr><td class="name">Ascent</td>' ... + '<td class="value"><a href="../../plots/' ... + p_config.PLOT_ASCENT.storage_directory_fig ... + p_config.PLOT_ASCENT.figure_filename ... + num2str(float_number) '.fig">' ... + '<img alt="No image available!" src="../../plots/' ... + p_config.PLOT_ASCENT.storage_directory_image ... + p_config.PLOT_ASCENT.image_filename ... + num2str(float_number) '.png"></a>' ... + '</td></tr>']; + html = [ html '<tr><td class="name">Categorized timings</td>' ... + '<td class="value"><a href="../../plots/' ... + p_config.PLOT_CATEGORIZED_TIMINGS.storage_directory_fig ... + p_config.PLOT_CATEGORIZED_TIMINGS.figure_filename ... + num2str(float_number) '.fig">' ... + '<img alt="No image available!" src="../../plots/' ... + p_config.PLOT_CATEGORIZED_TIMINGS.storage_directory_image ... + p_config.PLOT_CATEGORIZED_TIMINGS.image_filename ... + num2str(float_number) '.png"></a>' ... + '</td></tr>']; + html = [ html '<tr><td class="name">Timing information</td>' ... + '<td class="value"><a href="../../plots/' ... + p_config.PLOT_TIMING_INFORMATION.storage_directory_fig ... + p_config.PLOT_TIMING_INFORMATION.figure_filename ... + num2str(float_number) '.fig">' ... + '<img alt="No image available!" src="../../plots/' ... + p_config.PLOT_TIMING_INFORMATION.storage_directory_image ... + p_config.PLOT_TIMING_INFORMATION.image_filename ... + num2str(float_number) '.png"></a>' ... + '</td></tr>']; + html = [ html '<tr><td class="name">RAFOS</td>' ... + '<td class="value"><a href="../../plots/' ... + p_config.PLOT_RAFOS.storage_directory_fig ... + p_config.PLOT_RAFOS.figure_filename ... + num2str(float_number) '.fig">' ... + '<img alt="No image available!" src="../../plots/' ... + p_config.PLOT_RAFOS.storage_directory_image ... + p_config.PLOT_RAFOS.image_filename ... + num2str(float_number) '.png"></a>' ... + '</td></tr>']; + html = [ html '<tr><td class="name">Single floats map</td>' ... + '<td class="value"><a href="../../plots/' ... + p_config.PLOT_SINGLE_FLOAT_SOUTH.storage_directory_fig ... + num2str(float_number) '.fig">' ... + '<img alt="No image available!" src="../../plots/' ... + p_config.PLOT_SINGLE_FLOAT_SOUTH.storage_directory_image ... + num2str(float_number) '.png"></a>' ... + '</td></tr>']; + html = [ html '<tr><td class="name">Temperature, Salinity</td>' ... + '<td class="value"><a href="../../plots/' ... + p_config.PLOT_TEMP_SAL.storage_directory_fig ... + p_config.PLOT_TEMP_SAL.figure_filename ... + num2str(float_number) '.fig">' ... + '<img alt="No image available!" src="../../plots/' ... + p_config.PLOT_TEMP_SAL.storage_directory_image ... + p_config.PLOT_TEMP_SAL.image_filename ... + num2str(float_number) '.png"></a>' ... + '</td></tr>']; + html = [ html '<tr><td class="name">Temperature, Salinity, Potential Temperature</td>' ... + '<td class="value"><a href="../../plots/' ... + p_config.PLOT_TEMP_SAL_PTEMP.storage_directory_fig ... + p_config.PLOT_TEMP_SAL_PTEMP.figure_filename ... + num2str(float_number) '.fig">' ... + '<img alt="No image available!" src="../../plots/' ... + p_config.PLOT_TEMP_SAL_PTEMP.storage_directory_image ... + p_config.PLOT_TEMP_SAL_PTEMP.image_filename ... + num2str(float_number) '.png"></a>' ... + '</td></tr>']; + + html = [ html '</table></body></html>' ]; + + %% Write html file + + fid = fopen(fullfile(path, [num2str(float_number) '.html']), 'w'); + fwrite(fid, html); + fclose(fid); + +end + +boolean = true; +end diff --git a/lib/vendor/nemo2profilelib/+nemo/+convert/combined_float2nemostruct.m b/lib/vendor/nemo2profilelib/+nemo/+convert/combined_float2nemostruct.m new file mode 100644 index 0000000..408bf57 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+convert/combined_float2nemostruct.m @@ -0,0 +1,970 @@ +function [ nemo_float ] = combined_float2nemostruct( p_combined_float ) +%FLOAT Converts the given float in optimare structure to the structre of AWI. +% This is only a temporary function and will be deprecated as soon as the +% decoding process has been rewritten. During the decoding, optimare created a +% variable structure that differs from the one AWI is using. This function +% transfers all variables into the AWI format. In addition to that, it also +% checks for incomplete profiles and always saves the profile with the biggest +% size of CTD data. +% +% Parameters: +% p_float: Structure that was created during the decoding process. +% +% Returns: +% nemo_float: Structure that is being used by AWI. False +% if there is no data, no profiles or if something went wrong +% during conversion. + +%% Initialize return variables +nemo_float = {}; + +%% Convert information to AWI structure + +if ~isfield(p_combined_float, 'DECODED_SBD') + % no data available + nemo_float = false; + return +end +% get fieldnames of every decoded profile +profile_fieldnames = fieldnames(p_combined_float.DECODED_SBD); + +% sort iridium messages by profile number +[profile_numbers_sorted, sorted_indices] = sort_iridium_messages(profile_fieldnames); + +if ~sorted_indices + % something went wrong, for example only incomplete iridium messages available + nemo_float = false; + return +end + +% get profiles with most CTD data if multiple ones have been transmitted +best_ctd_indices = filter_multiple_profiles(sorted_indices); + +% count valid profiles +valid_profile_count = length(best_ctd_indices); + + +%% Get format data that is always the same +deployment_info_struct = deployment_info(); +rafos_values_format_struct = rafos_values_format(); +profile_header_struct = profile_header(); +quality_control_header_struct = qualtiy_control_header(); +profile_data_header_struct = profile_data_header(); +surface_gps_data_format_struct = surface_gps_data_format(); +iridium_positions_format_struct = iridium_positions_format(); +iridium_data_format_struct = iridium_data_format(); + +% create variable, counting available valid profiles +% differs to valid_profile_count because only "Profile Message" is being accepted +valid_profiles = 0; + +for o_profile = 1:valid_profile_count + % get index of current profile, may not be same as o_profile because optimare + % structure is not sorted by profile numbers + current_index = best_ctd_indices(o_profile); + + if ~strcmp( ... + p_combined_float.DECODED_SBD.(profile_fieldnames{current_index}).DATATYPE, ... + 'Profile Message' ... + ) + % everything that is not a profile or a test message is being ignored + continue + else + % increase number of valid profiles + valid_profiles = valid_profiles + 1; + end + try % converting the input structure + + %% FLOAT_IDENTIFICATION + nemo_float{1, valid_profiles}.FLOAT_IDENTIFICATION = ... + float_identification(profile_fieldnames{current_index}); + %% OVERALL_MISSION_INFORMATION + nemo_float{1, valid_profiles}.OVERALL_MISSION_INFORMATION = ... + overall_mission_information(profile_fieldnames{current_index}); + %% DEPLOYMENT_INFO + nemo_float{1, valid_profiles}.DEPLOYMENT_INFO = ... + deployment_info_struct; + %% PROFILE_TECHNICAL_DATA + nemo_float{1, valid_profiles}.PROFILE_TECHNICAL_DATA = ... + profile_technical_data(profile_fieldnames{current_index}); + %% BOTTOM_VALUES_DURING_DRIFT + nemo_float{1, valid_profiles}.BOTTOM_VALUES_DURING_DRIFT = ... + bottom_values_during_drift(profile_fieldnames{current_index}); + %% RAFOS_VALUES_FORMAT + nemo_float{1, valid_profiles}.RAFOS_VALUES_FORMAT = ... + rafos_values_format_struct; + %% RAFOS_VALUES + nemo_float{1, valid_profiles}.RAFOS_VALUES = ... + rafos_values(profile_fieldnames{current_index}); + %% PROFILE_HEADER + nemo_float{1, valid_profiles}.PROFILE_HEADER = ... + profile_header_struct; + %% QUALITY_CONTROL_HEADER + nemo_float{1, valid_profiles}.QUALITY_CONTROL_HEADER = ... + quality_control_header_struct; + %% PROFILE_DATA_HEADER + nemo_float{1, valid_profiles}.PROFILE_DATA_HEADER = ... + profile_data_header_struct; + %% PROFILE_DATA + nemo_float{1, valid_profiles}.PROFILE_DATA = ... + profile_data(profile_fieldnames{current_index}); + + % if there is no profile data, clean up and go to next profile + if isempty(nemo_float{1, valid_profiles}.PROFILE_DATA) + % skip profiles without data + nemo_float{1, valid_profiles} = []; + valid_profiles = valid_profiles - 1; + continue + end + + %% SURFACE_GPS_DATA_FORMAT + nemo_float{1, valid_profiles}.SURFACE_GPS_DATA_FORMAT = ... + surface_gps_data_format_struct; + %% SURFACE_GPS_DATA + nemo_float{1, valid_profiles}.SURFACE_GPS_DATA = ... + surface_gps_data(profile_fieldnames{current_index}); + %% IRIDIUM_POSITIONS_FORMAT + nemo_float{1, valid_profiles}.IRIDIUM_POSITIONS_FORMAT = ... + iridium_positions_format_struct; + %% IRIDIUM_POSITIONS & IRIDIUM_DATA + [ iridium_positions, iridium_best ] = ... + iridium_positions_and_data(profile_fieldnames{current_index}); + nemo_float{1, valid_profiles}.IRIDIUM_POSITIONS = iridium_positions; + clear iridium_positions + %% IRIDIUM_DATA_FORMAT + nemo_float{1, valid_profiles}.IRIDIUM_DATA_FORMAT = ... + iridium_data_format_struct; + nemo_float{1, valid_profiles}.IRIDIUM_DATA = iridium_best; + clear iridium_best + + + catch exception + % something went wrong, so complain and return false + warning([mfilename ': ' exception.message]); + nemo_float = false; + return; + end + + +end + +% if there is no profile data at all, return false +if valid_profiles == 0 + nemo_float = false; + return; +end + +%% Sort float +nemo_float = nemo.float.sort_profile_data(nemo_float); +nemo_float = nemo.float.sort_by_profile_number(nemo_float); + +%% Exit this function + +return; % end of combined_float2nemostruct + +%% Helper functions + + function [ value ] = store_if_available(p_search, p_needle) + % if the given needle is a member of search, return the containing value. + if isfield(p_search, p_needle) + value = p_search.(p_needle); + switch class(value) + case 'char' + value = strtrim(value); + end + return + else + value = NaN; + return + end + end + + +%% FLOAT_IDENTIFICATION + + function [ awi_struct ] = float_identification(p_profile_fieldname) + + awi_struct = {}; + + + if isfield( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname),'DEVICE') ... + && strcmp(strtrim( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname).DEVICE),'NEMO Standard' ... + ) + awi_struct.transmission_type = 'IRIDIUM SBD'; + else + awi_struct.transmission_type = NaN; + end + + if isfield(p_combined_float.GENERAL_INFO, 'IMEI') + awi_struct.transmission_id_number_dec = ... + p_combined_float.GENERAL_INFO.IMEI; + else + awi_struct.transmission_id_number_dec = NaN; + end + + awi_struct.transmission_id_number_hex = NaN; + + awi_struct.wmo_id_number = store_if_available( ... + p_combined_float.GENERAL_INFO, 'WMO_ID'); + + awi_struct.wmo_instrument_type = store_if_available( ... + p_combined_float.GENERAL_INFO, 'WMO_INSTRUMENT_TYPE'); + + + + awi_struct.wmo_recorder_type = store_if_available( ... + p_combined_float.GENERAL_INFO, 'WMO_RECORD_TYPE'); + + awi_struct.instrument_type = store_if_available( ... + p_combined_float.GENERAL_INFO, 'INSTRUMENT_TYPE'); + + awi_struct.float_manufacturer = store_if_available( ... + p_combined_float.GENERAL_INFO, 'FLOAT_MANUFACTURER'); + + awi_struct.float_serial_number = store_if_available( ... + p_combined_float.GENERAL_INFO, 'SERIAL'); + + awi_struct.ice_detection_software = store_if_available( ... + p_combined_float.SENSOR_INFO, 'ICE_DETECTION'); + + + awi_struct.float_provider = store_if_available( ... + p_combined_float.PROVIDER_DEPLOYER_INFO, 'PROVIDER_NAME'); + + + awi_struct.float_provider_institution = store_if_available( ... + p_combined_float.PROVIDER_DEPLOYER_INFO, 'PROVIDER_INSTITUTE'); + + awi_struct.originating_country = store_if_available( ... + p_combined_float.PROVIDER_DEPLOYER_INFO, 'PROVIDER_COUNTRY'); + + + end + +%% OVERALL_MISSION_INFORMATION + + function [ awi_struct ] = overall_mission_information(p_profile_fieldname) + + awi_struct = {}; + + if isfield(p_combined_float.DECODED_SBD.(p_profile_fieldname), 'TIME_CYCLE') + awi_struct.total_cycle = [ ... + p_combined_float.DECODED_SBD.(p_profile_fieldname).TIME_CYCLE/(60*24) ... + 0 0]; + else + awi_struct.total_cycle = NaN; + end + + awi_struct.down_time = NaN; + awi_struct.up_time = NaN; + awi_struct.transmission_repetition_rate = NaN; + awi_struct.clock_drift = NaN; + awi_struct.nominal_drift_depth = NaN; + awi_struct.max_departure_from_drift_depth = NaN; + + awi_struct.nominal_profile_depth = store_if_available( ... + p_combined_float.MISSION_PARAMETERS, 'PROFILE_PRESSURE'); + + if isfield(p_combined_float.DEPLOYMENT_INFO, 'START_DATE') + tmp_mat = strsplit(p_combined_float.DEPLOYMENT_INFO.START_DATE, '.'); + awi_struct.start_date = [ ... + str2double(tmp_mat{1}) ... + str2double(tmp_mat{2}) ... + str2double(tmp_mat{3})]; + clear tmp_mat + else + awi_struct.start_date = NaN; + end + + if isfield(p_combined_float.DEPLOYMENT_INFO, 'START_TIME') + tmp_mat = strsplit(p_combined_float.DEPLOYMENT_INFO.START_TIME, ':'); + awi_struct.start_time = [ ... + str2double(tmp_mat{1}) ... + str2double(tmp_mat{2})]; + clear tmp_mat + else + awi_struct.start_time = NaN; + end + + awi_struct.RAFOS_clock_offset_at_start = NaN; + awi_struct.start_surface_pressure = NaN; + + awi_struct.ice_detection_temperature_threshold = store_if_available( ... + p_combined_float.MISSION_PARAMETERS, 'ICE_TEMPERATURE'); + + + end + +%% DEPLOYMENT_INFO + function [ awi_struct ] = deployment_info() + + awi_struct.float_deployer = store_if_available( ... + p_combined_float.PROVIDER_DEPLOYER_INFO, 'DEPLOYER_NAME'); + + awi_struct.float_deployer_institution = store_if_available( ... + p_combined_float.PROVIDER_DEPLOYER_INFO, 'DEPLOYER_INSTITUTE'); + + awi_struct.deployment_platform_type = store_if_available( ... + p_combined_float.DEPLOYMENT_INFO, 'DEPLOYMENT_PLATFORM'); + + awi_struct.deployment_platform_code = store_if_available( ... + p_combined_float.DEPLOYMENT_INFO, 'DEPLOYMENT_PLATFORM_SIGN'); + + if isfield(p_combined_float.DEPLOYMENT_INFO, 'DEPLOYMENT_PLATFORM_EXPEDITION') + tmp_string = strsplit( ... + p_combined_float.DEPLOYMENT_INFO.DEPLOYMENT_PLATFORM_EXPEDITION, ... + '/'); + if length(tmp_string) == 2 + awi_struct.deployment_platform_cruise_name = tmp_string{1}; + try + awi_struct.deployment_platform_leg = str2double(tmp_string{2}); + catch + awi_struct.deployment_platform_leg = tmp_string{2}; + end + else + awi_struct.deployment_platform_cruise_name = ... + p_combined_float.DEPLOYMENT_INFO.DEPLOYMENT_PLATFORM_EXPEDITION; + awi_struct.deployment_platform_leg = 'none'; + end + clear tmp_string + else + awi_struct.deployment_platform_cruise_name = NaN; + awi_struct.deployment_platform_leg = 'none'; + end + + awi_struct.deployment_ctd_station = NaN; + + if isfield(p_combined_float.DEPLOYMENT_INFO, 'DEPLOYMENT_DATE') + tmp_string = strsplit( ... + p_combined_float.DEPLOYMENT_INFO.DEPLOYMENT_DATE, ... + '.'); + awi_struct.deployment_date = [ str2double(tmp_string{1}) ... + str2double(tmp_string{2}) ... + str2double(tmp_string{3}) ]; + clear tmp_string + else + awi_struct.deployment_date = NaN; + end + + if isfield(p_combined_float.DEPLOYMENT_INFO, 'DEPLOYMENT_TIME') + tmp_string = strsplit( ... + p_combined_float.DEPLOYMENT_INFO.DEPLOYMENT_TIME, ... + ':'); + awi_struct.deployment_time = [ str2double(tmp_string{1}) ... + str2double(tmp_string{2}) ]; + clear tmp_string + else + awi_struct.deployment_time = NaN; + end + + if isfield(p_combined_float.DEPLOYMENT_INFO, 'DEPLOYMENT_LAT') ... + && isfield(p_combined_float.DEPLOYMENT_INFO, 'DEPLOYMENT_LON') + awi_struct.deployment_position = [ ... + p_combined_float.DEPLOYMENT_INFO.DEPLOYMENT_LAT ... + p_combined_float.DEPLOYMENT_INFO.DEPLOYMENT_LON]; + else + awi_struct.deployment_position = NaN; + end + + awi_struct.deployment_speed = NaN; + + awi_struct.deployment_sea_state = store_if_available( ... + p_combined_float.DEPLOYMENT_INFO, 'DEPLOYMENT_SEA_STATE'); + + awi_struct.deployment_wave_height = NaN; + + awi_struct.deployment_ice_coverage = store_if_available( ... + p_combined_float.DEPLOYMENT_INFO, 'DEPLOYMENT_SEA_ICE'); + + awi_struct.delay_of_first_down_time = NaN; + + end + +%% PROFILE_TECHNICAL_DATA + + function [ awi_struct ] = profile_technical_data(p_profile_fieldname) + + awi_struct = {}; + + awi_struct.xmit_profile_number = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'PROFILE_NUMBER'); + + awi_struct.xmit_serial_number = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'SERIAL'); + + awi_struct.xmit_upcast_status = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'STATUS_UPCAST'); + + awi_struct.xmit_older_profiles_not_send = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'PROFILE_OLDER'); + + awi_struct.xmit_motor_errors_count = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'ERROR_MOTOR'); + + awi_struct.xmit_sbe42_ctd_errors = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'ERROR_CTD'); + + awi_struct.xmit_o2_optode_errors = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'ERROR_OPTODE'); + + awi_struct.xmit_rafos_errors = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'ERROR_RAFOS'); + + awi_struct.xmit_cpu_battery_voltage = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'BATT_CPU'); + + awi_struct.xmit_pump_battery_voltage = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'BATT_PMP'); + + awi_struct.xmit_motor_current = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'CURR_MOTOR'); + + awi_struct.xmit_motor_current_mean = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'CURR_MOTOR_MEAN'); + + awi_struct.xmit_internal_pressure_surface = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'PRES_TUBE_SURFACE'); + + awi_struct.xmit_internal_pressure_depth = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'PRES_TUBE_DEPTH'); + + awi_struct.xmit_pressure_offset = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'PRES_OFFSET'); + + awi_struct.xmit_surface_pressure = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'PRES_SURFACE'); + + awi_struct.xmit_parking_pressure_median = median(store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'DS2_PRES')); + + awi_struct.xmit_depth_pressure = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'PRES_DEPTH'); + + awi_struct.xmit_depth_pressure_max = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'PRES_MAX'); + + awi_struct.xmit_profile_recovery = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'RECOVERY_START'); + + awi_struct.xmit_piston_counts_surface = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'PIST_SURFACE'); + + awi_struct.xmit_piston_counts_parking = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'PIST_PARKING'); + + awi_struct.xmit_piston_counts_calc = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'PIST_CALC'); + + awi_struct.xmit_piston_counts_eop = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'PIST_EOP'); + + awi_struct.xmit_descent_start_time = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'MCNT_DESCENT'); + + awi_struct.xmit_parking_start_time = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'MCNT_PARKING'); + + awi_struct.xmit_upcast_start_time = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'MCNT_SAG'); + + awi_struct.xmit_ascent_start_time = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'MCNT_ASCENT'); + + awi_struct.xmit_ascent_end_time = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'MCNT_ASCENT_END'); + + awi_struct.xmit_surface_start_time = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'MCNT_SURFACE'); + + if isfield(p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'DATA_RECORDS2') ... + && length(p_combined_float.DECODED_SBD.(p_profile_fieldname).DATA_RECORDS2) == 5 + awi_struct.xmit_profile_length_1 = ... + p_combined_float.DECODED_SBD.(p_profile_fieldname).DATA_RECORDS2(1); + awi_struct.xmit_profile_length_2 = ... + p_combined_float.DECODED_SBD.(p_profile_fieldname).DATA_RECORDS2(2); + awi_struct.xmit_profile_length_3 = ... + p_combined_float.DECODED_SBD.(p_profile_fieldname).DATA_RECORDS2(3); + awi_struct.xmit_profile_length_4 = ... + p_combined_float.DECODED_SBD.(p_profile_fieldname).DATA_RECORDS2(4); + awi_struct.xmit_profile_length_5 = ... + p_combined_float.DECODED_SBD.(p_profile_fieldname).DATA_RECORDS2(5); + else + awi_struct.xmit_profile_length_1 = NaN; + awi_struct.xmit_profile_length_2 = NaN; + awi_struct.xmit_profile_length_3 = NaN; + awi_struct.xmit_profile_length_4 = NaN; + awi_struct.xmit_profile_length_5 = NaN; + end + + awi_struct.xmit_surface_detection_GPS = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'ICEGPS_TIME'); + + awi_struct.xmit_surface_detection_Iridium = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'ICEIRDM_TIME'); + + awi_struct.xmit_airpump_runtime = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'AIRPUMP_RUNTIME'); + + awi_struct.xmit_ice_detection_temp_median = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), ... + 'ICE_TEMPMEDIAN'); + + end + +%% BOTTOM_VALUES_DURING_DRIFT + function [ awi_struct ] = bottom_values_during_drift(p_profile_fieldname) + + awi_struct = {}; + + awi_struct.xmit_bottom_pressure = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), 'DS2_PRES'); + + awi_struct.xmit_bottom_salt = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), 'DS2_SAL'); + + awi_struct.xmit_bottom_temperature = store_if_available( ... + p_combined_float.DECODED_SBD.(p_profile_fieldname), 'DS2_TEMP'); + + end + +%% RAFOS_VALUES_FORMAT + + function [ awi_struct ] = rafos_values_format() + + awi_struct = { ... + 'status'; 'rtc_year'; 'rtc_month'; 'rtc_day'; 'rtc_hour'; ... + 'rtc_minute'; 'rtc_second'; '1STCORHEIGHT'; '1STTRAVELTIME'; ... + '2NDCORHEIGHT'; '2NDTRAVELTIME'; '3RDCORHEIGHT'; '3RDTRAVELTIME'; ... + '4THCORHEIGHT'; '4THTRAVELTIME'; '5THCORHEIGHT'; '5THTRAVELTIME'; ... + '6THCORHEIGHT'; '6THTRAVELTIME'; 'pressure'; 'salinity'; 'temp' ... + }'; + + end + +%% RAFOS_VALUES + + function [ awi_struct ] = rafos_values(p_profile_fieldname) + + p = p_combined_float.DECODED_SBD.(p_profile_fieldname); + if isfield(p, 'DS3_DATE_year') && isfield(p, 'DS3_DATE_month') ... + && isfield(p, 'DS3_DATE_day') && isfield(p, 'DS3_TIME') ... + && isfield(p, 'DS3_STATUS') && isfield(p, 'DS3_RAFOSAMP') ... + && isfield(p, 'DS3_RAFOSRANK') && isfield(p, 'DS3_PRES') ... + && isfield(p, 'DS3_SAL') && isfield(p, 'DS3_TEMP') + + y = (1/(4836-4464) .* p.DS3_DATE_year) + 2000; + mon = p.DS3_DATE_month; + d = p.DS3_DATE_day; + + h = floor(p.DS3_TIME./3600); + m = floor((p.DS3_TIME - (h*3600))/60); + s = uint8(mod(p.DS3_TIME,60)); + + prefix = [p.DS3_STATUS', y', mon', d', h', m', double(s')]; + + awi_struct = [ ... + prefix, ... + p.DS3_RAFOSAMP(:, 1), p.DS3_RAFOSRANK(:, 1), ... + p.DS3_RAFOSAMP(:, 2), p.DS3_RAFOSRANK(:, 2), ... + p.DS3_RAFOSAMP(:, 3), p.DS3_RAFOSRANK(:, 3), ... + p.DS3_RAFOSAMP(:, 4), p.DS3_RAFOSRANK(:, 4), ... + p.DS3_RAFOSAMP(:, 5), p.DS3_RAFOSRANK(:, 5), ... + p.DS3_RAFOSAMP(:, 6), p.DS3_RAFOSRANK(:, 6), ... + p.DS3_PRES', p.DS3_SAL', p.DS3_TEMP' ... + ]; + else + awi_struct = []; + end + + + end + +%% PROFILE_HEADER + + function [ awi_struct ] = profile_header() + + awi_struct = {}; + + awi_struct.number_of_columns = 13; + awi_struct.column_1 = 'line_number'; + awi_struct.column_2 = 'hour'; + awi_struct.column_3 = 'minute'; + awi_struct.column_4 = 'second'; + awi_struct.column_5 = 'temperature [deg C]'; + awi_struct.column_6 = 'temperature flag'; + awi_struct.column_7 = 'pressure [dbar]'; + awi_struct.column_8 = 'pressure flag'; + awi_struct.column_9 = 'salinity [psu]'; + awi_struct.column_10 = 'salinity flag'; + awi_struct.column_11 = 'temperature raw'; + awi_struct.column_12 = 'pressure raw'; + awi_struct.column_13 = 'salinity raw'; + awi_struct.temperature_conversion_equation = 'T=cnts/1000, if(cnts>62535),cnts=cnts-65536'; + awi_struct.pressure_conversion_equation = 'P=cnts/10'; + awi_struct.salinity_conversion_equation = 'S=cnts/1000'; + + end + +%% QUALITY_CONTROL_HEADER + + function [ awi_struct ] = qualtiy_control_header() + + awi_struct = {}; + + awi_struct.overall_flag_for_T = 0; + awi_struct.overall_flag_for_P = 0; + awi_struct.overall_flag_for_S = 0; + awi_struct.maximum_speed_check_flag = 0; + awi_struct.maximum_analysis_check_T_flag = 0; + awi_struct.maximum_climatology_T_flag = 0; + awi_struct.maximum_gross_check_T_flag = 0; + awi_struct.maximum_vertical_gradient_T_flag = 0; + awi_struct.maximum_spike_check_T_flag = 0; + awi_struct.maximum_analysis_check_S_flag = 0; + awi_struct.maximum_climatology_S_flag = 0; + awi_struct.maximum_gross_check_S_flag = 0; + awi_struct.maximum_vertical_gradient_S_flag = 0; + awi_struct.maximum_spike_check_S_flag = 0; + awi_struct.sigma_used_for_T_climatology = 10; + awi_struct.sigma_used_for_T_analysis = 10; + awi_struct.sigma_used_for_S_climatology = 10; + awi_struct.sigma_used_for_S_analysis = 10; + + end + +%% PROFILE_DATA_HEADER + + function [ awi_struct ] = profile_data_header() + + awi_struct = { ... + '#'; 'hour'; 'minute'; 'second'; 'temp'; 'temp_flag'; 'pressure'; ... + 'pres_flag'; 'salinity'; 'sal_flag'; 'temp_raw'; 'pres_raw'; ... + 'sal_raw'; 'light_442_nm'; 'light_550_nm'; 'light_676_nm' ... + }'; + + end + +%% PROFILE_DATA + + function [ awi_struct ] = profile_data(p_profile_fieldname) + + p = p_combined_float.DECODED_SBD.(p_profile_fieldname); + if isfield(p, 'DS4_TIME') && isfield(p, 'DS4_TEMP') ... + && isfield(p, 'DS4_PRES') && isfield(p, 'DS4_SAL') + + h = floor(p.DS4_TIME./3600); + m = floor((p.DS4_TIME - (h*3600))/60); + s = uint8(mod(p.DS4_TIME, 60)); + profile_numbers = 1:length(h); + flags = zeros(1, length(h)); + + prefix = [profile_numbers', h', m', double(s')]; + + % somtimes LICH1-3 has different length than TEMP, PRES & SAL + if ~isfield(p, 'DS4_LICH1') || ~isfield(p, 'DS4_LICH2') ... + || ~isfield(p, 'DS4_LICH3') || (length(p.DS4_LICH1) ~= length(p.DS4_TEMP)) + LICH1 = flags; + LICH2 = flags; + LICH3 = flags; + else + LICH1 = p.DS4_LICH1; + LICH2 = p.DS4_LICH2; + LICH3 = p.DS4_LICH3; + end + + awi_struct = [ ... + prefix, ... + p.DS4_TEMP', flags', ... + p.DS4_PRES', flags', ... + p.DS4_SAL', flags', ... + p.DS4_TEMP', p.DS4_PRES', p.DS4_SAL', ... + LICH1', LICH2', LICH3' ... + ]; + else + awi_struct = []; + end + + + end + +%% SURFACE_GPS_DATA_FORMAT + + function [ awi_struct ] = surface_gps_data_format() + + awi_struct = { ... + '#'; 'rtc_year'; 'rtc_month'; 'rtc_day'; 'rtc_hour'; 'rtc_minute'; ... + 'rtc_second'; 'GPS_year'; 'GPS_month'; 'GPS_day'; 'GPS_hour'; ... + 'GPS_minute'; 'GPS_second'; 'lat'; 'lon' ... + }'; + + end + +%% SURFACE_GPS_DATA + + function [ awi_struct ] = surface_gps_data(p_profile_fieldname) + + p = p_combined_float.DECODED_SBD.(p_profile_fieldname); + if isfield(p, 'DS5_GPS') && isfield(p, 'DS5_RTC') ... + && isfield(p, 'DS5_LAT') && isfield(p, 'DS5_LON') + + gps_datevec = datevec(p.DS5_GPS); + % check if gps date is correct + if gps_datevec(1) == 1999 && gps_datevec(2) == 12 ... + && gps_datevec(3) == 31 + gps_datevec = [NaN, NaN, NaN, NaN, NaN, NaN]; + end + + awi_struct = [ ... + 1, ... + datevec(p.DS5_RTC), ... + gps_datevec, ... + p.DS5_LAT, ... + p.DS5_LON ... + ]; + else + awi_struct = []; + end + + + end + +%% IRIDIUM_POSITIONS_FORMAT + + function [ awi_struct ] = iridium_positions_format() + + awi_struct = { ... + 'sbd_lat'; 'sbd_lon'; 'sbd_cep'; ... + 'yyyy'; 'mm'; 'dd'; ... + 'hh'; 'mm'; 'ss' + }'; + + end + +%% IRIDIUM_POSITIONS + + function [ iridium_positions, iridium_best ] = ... + iridium_positions_and_data(p_profile_fieldname) + + iridium_positions = []; + iridium_best = []; + + p = p_combined_float.DECODED_SBD.(p_profile_fieldname); + sbd_info = p_combined_float.SBD_MESSAGES_INFO; + + if (isa(p_combined_float.TRANSMISSION_INFO, 'struct')) + p_combined_float.TRANSMISSION_INFO = struct2table(p_combined_float.TRANSMISSION_INFO); + end + + % BUGFIX, delete all rows with empty profilenumber_lsb values + sbd_info = sbd_info(~cellfun('isempty', {sbd_info.profilenumber_lsb})); + % get all profile numbers of the transmitted iridium messsages + sbd_info = struct2table(sbd_info); + % be sure that it is sorted! + sbd_info = sortrows(sbd_info,'momsn','ascend'); + % get all corresponding messages + iridium_messages = sbd_info(sbd_info.profilenumber_lsb == p.PROFILE_NUMBER, :); + iridium_messages = sortrows(iridium_messages, 'momsn', 'ascend'); + iridium_messages_count = size(iridium_messages, 1); + + % filter incomplete transmissions + first_sbd_index = 1; + tmp_index = 0; + sbd_total_num = iridium_messages.total_num(first_sbd_index); + for i = 1:iridium_messages_count + current_elementary = iridium_messages.elementary_num(i); + if (current_elementary ~= iridium_messages.elementary_num(first_sbd_index) + tmp_index) + first_sbd_index = i; + sbd_total_num = iridium_messages.total_num(first_sbd_index); + tmp_index = 1; + else + tmp_index = tmp_index + 1; + end + if (tmp_index == sbd_total_num) + %tmp_index = tmp_index - 1; + break; + end + end + + iridium_messages = iridium_messages(first_sbd_index:first_sbd_index + tmp_index - 1, :); + + clear iridium_messages_count; + + xmit_info = table(); + + iridium_messages_count = size(iridium_messages, 1); + for o_iridium = 1:iridium_messages_count + current_momsn = iridium_messages(o_iridium, :).momsn; + current_xmit_info = p_combined_float.TRANSMISSION_INFO( ... + p_combined_float.TRANSMISSION_INFO.MOMSN == current_momsn ... + , :); + + index = 1; + if size(current_xmit_info, 1) > 1 + warning([mfilename ': More than one SBD Message with MOMSN ' current_momsn ' has been found!']); + %[~, index] = min(current_xmit_info.CEPRAD); + end + + xmit_info = [xmit_info; current_xmit_info(index, :)]; + end + + if (isempty(xmit_info)) + iridium_positions = []; + iridium_best = []; + return + end + + % create return structure for all positions + iridium_positions = [ ... + xmit_info.IRIDIUM_LAT, ... + xmit_info.IRIDIUM_LON, ... + xmit_info.CEPRAD, ... + xmit_info.TIMEOFSESSION ... + ]; + + % get best position & time + iridium_best = iridium_positions(iridium_positions(:, 3) == min(iridium_positions(:, 3)), :); + iridium_best = iridium_best(end, :); + % get closest time to the surface time + iridium_best(4:9) = iridium_positions(1, 4:9); + + + end + +%% IRIDIUM_DATA_FORMAT + + function [ awi_struct ] = iridium_data_format() + + awi_struct = { ... + 'mean_lat'; 'mean_lon'; 'mean_cep'; ... + 'yyyy'; 'mm'; 'dd'; ... + 'hh'; 'mm'; 'ss' + }'; + + end + +%% SORT IRIDIUM MESSAGES + + function [ profile_numbers_sorted, indices ] = sort_iridium_messages(p_profile_fieldnames) + profile_numbers_unsorted = []; + tmp_valid_profile_count = 0; + % collect all profile numbers + for o_fields = 1:length(p_profile_fieldnames) + current_profile = ... + p_combined_float.DECODED_SBD.(p_profile_fieldnames{o_fields}); + if ~strcmp(p_combined_float.DECODED_SBD.(p_profile_fieldnames{o_fields}).DATATYPE, 'Profile Message') ... + || ~isfield(p_combined_float.DECODED_SBD.(p_profile_fieldnames{o_fields}), 'PROFILE_NUMBER') ... + || isnan(p_combined_float.DECODED_SBD.(p_profile_fieldnames{o_fields}).PROFILE_NUMBER) + continue; + end + tmp_valid_profile_count = tmp_valid_profile_count + 1; + profile_numbers_unsorted(tmp_valid_profile_count, :) = ... + [p_combined_float.DECODED_SBD.(p_profile_fieldnames{o_fields}).PROFILE_NUMBER, ... + o_fields]; + end + % sort them + if ~isempty(profile_numbers_unsorted) + [profile_numbers_sorted, indices] = sortrows(profile_numbers_unsorted, 1); + indices = profile_numbers_sorted(:, 2); + else + indices = false; + profile_numbers_sorted = false; + end + end + + function [best_ctd_indices] = filter_multiple_profiles(sorted_indices) + % initialize return variable + best_ctd_indices = []; + best_indices_count = 0; + % get profile number of the first element + if isfield(p_combined_float.DECODED_SBD.(profile_fieldnames{sorted_indices(1)}), 'DATA_RECORDS2') ... + && p_combined_float.DECODED_SBD.(profile_fieldnames{sorted_indices(1)}).DATA_RECORDS2(4) > 0 + % if it is < 0, there is no possibility to compare CTD data + best_indices_count = best_indices_count + 1; + last_profile_number = p_combined_float.DECODED_SBD.(profile_fieldnames{sorted_indices(1)}).PROFILE_NUMBER; + best_ctd_indices(best_indices_count) = sorted_indices(1); + end + + for o_index = 2:length(sorted_indices) + if ~isfield(p_combined_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}), 'DATA_RECORDS2') ... + || p_combined_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}).DATA_RECORDS2(4) == 0 + continue + end + % get current profile number + current_profile_number = p_combined_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}).PROFILE_NUMBER; + if best_indices_count > 0 && current_profile_number == last_profile_number + % check which ctd data is better + current_ctd_length = p_combined_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}).DATA_RECORDS2(4); + last_ctd_length = p_combined_float.DECODED_SBD.(profile_fieldnames{best_ctd_indices(best_indices_count)}).DATA_RECORDS2(4); + if current_ctd_length > last_ctd_length + best_ctd_indices(best_indices_count) = sorted_indices(o_index); + elseif current_ctd_length == last_ctd_length + % take the one without or less NaN's + tmp_current = [ ... + p_combined_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}).DS4_TIME; ... + p_combined_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}).DS4_FORMAT; ... + p_combined_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}).DS4_PRES; ... + p_combined_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}).DS4_TEMP; ... + p_combined_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}).DS4_SAL ... + ]; + tmp_last = [ ... + p_combined_float.DECODED_SBD.(profile_fieldnames{best_ctd_indices(best_indices_count)}).DS4_TIME; ... + p_combined_float.DECODED_SBD.(profile_fieldnames{best_ctd_indices(best_indices_count)}).DS4_FORMAT; ... + p_combined_float.DECODED_SBD.(profile_fieldnames{best_ctd_indices(best_indices_count)}).DS4_PRES; ... + p_combined_float.DECODED_SBD.(profile_fieldnames{best_ctd_indices(best_indices_count)}).DS4_TEMP; ... + p_combined_float.DECODED_SBD.(profile_fieldnames{best_ctd_indices(best_indices_count)}).DS4_SAL ... + ]; + sum_current = sum(sum(isnan(tmp_current))); + sum_last = sum(sum(isnan(tmp_last))); + if sum_last > sum_current + best_ctd_indices(best_indices_count) = sorted_indices(o_index); + end + clear tmp_current tmp_last sum_current sum_last + + end + else + best_indices_count = best_indices_count + 1; + best_ctd_indices(best_indices_count) = sorted_indices(o_index); + end + + last_profile_number = current_profile_number; + end + end + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+convert/float.m b/lib/vendor/nemo2profilelib/+nemo/+convert/float.m new file mode 100644 index 0000000..b1a2515 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+convert/float.m @@ -0,0 +1,969 @@ +function [ awi_float ] = float( p_optimare_float ) +%FLOAT Converts the given float in optimare structure to the structre of AWI. +% This is only a temporary function and will be deprecated as soon as the +% decoding process has been rewritten. During the decoding, optimare created a +% variable structre that differs from the one AWI is using. This function +% transfers all variables into the AWI format. In addition to that, it also +% checks for incomplete profiles and always saves the profile with the biggest +% size of CTD data. +% +% Parameters: +% p_optimare_float: Structure that was created during the decoding process. +% +% Returns: +% awi_float: Structure with the structure that is being used by AWI. False +% if there is no data, no profiles or if something went wrong +% during conversion. + +%% Initialize return variables +awi_float = {}; + +%% Convert information to AWI structure + +if ~isfield(p_optimare_float, 'DECODED_SBD') + % no data available + awi_float = false; + return +end +% get fieldnames of every decoded profile +profile_fieldnames = fieldnames(p_optimare_float.DECODED_SBD); + +% sort iridium messages by profile number +[profile_numbers_sorted, sorted_indices] = sort_iridium_messages(profile_fieldnames); + +if ~sorted_indices + % something went wrong, for example only incomplete iridium messages available + awi_float = false; + return +end + +% get profiles with most CTD data if multiple ones have been transmitted +best_ctd_indices = filter_multiple_profiles(sorted_indices); + +% count valid profiles +valid_profile_count = length(best_ctd_indices); + + +%% Get format data that is always the same +deployment_info_struct = deployment_info(); +rafos_values_format_struct = rafos_values_format(); +profile_header_struct = profile_header(); +quality_control_header_struct = qualtiy_control_header(); +profile_data_header_struct = profile_data_header(); +surface_gps_data_format_struct = surface_gps_data_format(); +iridium_positions_format_struct = iridium_positions_format(); +iridium_data_format_struct = iridium_data_format(); + +% create variable, counting available valid profiles +valid_profiles = 0; + +for o_profile = 1:valid_profile_count + % get index of current profile, may not be same as o_profile because optimare + % structure is not sorted by profile numbers + current_index = best_ctd_indices(o_profile); + + if ~strcmp(p_optimare_float.DECODED_SBD.(profile_fieldnames{current_index}).DATATYPE, ... + 'Profile Message') + % everything that is not a profile message is being ignored + continue + else + % increase number of valid profiles + valid_profiles = valid_profiles + 1; + end + try % converting the input structure + + %% FLOAT_IDENTIFICATION + awi_float{1, valid_profiles}.FLOAT_IDENTIFICATION = ... + float_identification(profile_fieldnames{current_index}); + %% OVERALL_MISSION_INFORMATION + awi_float{1, valid_profiles}.OVERALL_MISSION_INFORMATION = ... + overall_mission_information(profile_fieldnames{current_index}); + %% DEPLOYMENT_INFO + awi_float{1, valid_profiles}.DEPLOYMENT_INFO = ... + deployment_info_struct; + %% PROFILE_TECHNICAL_DATA + awi_float{1, valid_profiles}.PROFILE_TECHNICAL_DATA = ... + profile_technical_data(profile_fieldnames{current_index}); + %% BOTTOM_VALUES_DURING_DRIFT + awi_float{1, valid_profiles}.BOTTOM_VALUES_DURING_DRIFT = ... + bottom_values_during_drift(profile_fieldnames{current_index}); + %% RAFOS_VALUES_FORMAT + awi_float{1, valid_profiles}.RAFOS_VALUES_FORMAT = ... + rafos_values_format_struct; + %% RAFOS_VALUES + awi_float{1, valid_profiles}.RAFOS_VALUES = ... + rafos_values(profile_fieldnames{current_index}); + %% PROFILE_HEADER + awi_float{1, valid_profiles}.PROFILE_HEADER = ... + profile_header_struct; + %% QUALITY_CONTROL_HEADER + awi_float{1, valid_profiles}.QUALITY_CONTROL_HEADER = ... + quality_control_header_struct; + %% PROFILE_DATA_HEADER + awi_float{1, valid_profiles}.PROFILE_DATA_HEADER = ... + profile_data_header_struct; + %% PROFILE_DATA + awi_float{1, valid_profiles}.PROFILE_DATA = ... + profile_data(profile_fieldnames{current_index}); + + % if there is no profile data, clean up and go to next profile + if isempty(awi_float{1, valid_profiles}.PROFILE_DATA) + % skip profiles without data + awi_float{1, valid_profiles} = []; + valid_profiles = valid_profiles - 1; + continue + end + + %% SURFACE_GPS_DATA_FORMAT + awi_float{1, valid_profiles}.SURFACE_GPS_DATA_FORMAT = ... + surface_gps_data_format_struct; + %% SURFACE_GPS_DATA + awi_float{1, valid_profiles}.SURFACE_GPS_DATA = ... + surface_gps_data(profile_fieldnames{current_index}); + %% IRIDIUM_POSITIONS_FORMAT + awi_float{1, valid_profiles}.IRIDIUM_POSITIONS_FORMAT = ... + iridium_positions_format_struct; + %% IRIDIUM_POSITIONS & IRIDIUM_DATA + [ iridium_positions, iridium_best ] = ... + iridium_positions_and_data(profile_fieldnames{current_index}); + awi_float{1, valid_profiles}.IRIDIUM_POSITIONS = iridium_positions; + clear iridium_positions + %% IRIDIUM_DATA_FORMAT + awi_float{1, valid_profiles}.IRIDIUM_DATA_FORMAT = ... + iridium_data_format_struct; + awi_float{1, valid_profiles}.IRIDIUM_DATA = iridium_best; + clear iridium_best + + + catch exception + % something went wrong, so complain by returning false + awi_float = false; + return; + end + + +end + +% if there is no profile data at all, return false +if valid_profiles == 0 + awi_float = false; + return; +end + + function [ value ] = store_if_available(p_search, p_needle) + % if the given needle is a member of search, return the containing value. + if isfield(p_search, p_needle) + value = p_search.(p_needle); + switch class(value) + case 'char' + value = strtrim(value); + end + return + else + value = NaN; + return + end + end + + +%% FLOAT_IDENTIFICATION + + function [ awi_struct ] = float_identification(p_profile_fieldname) + + % abbreveations usually not allowed, this time used for better overview + awi_struct = {}; + + awi_struct.internal_ID_number = store_if_available( ... + p_optimare_float.DECODED_SBD, 'internal_ID_number'); + + if isfield( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname),'DEVICE') ... + && strcmp(strtrim( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname).DEVICE),'NEMO Standard' ... + ) + awi_struct.transmission_type = 'IRIDIUM SBD'; + else + awi_struct.transmission_type = NaN; + end + + if isfield(p_optimare_float.GENERAL_INFO, 'IMEI') + awi_struct.transmission_id_number_dec = ... + p_optimare_float.GENERAL_INFO.IMEI; + awi_struct.transmission_id_number_hex = NaN; + else + awi_struct.transmission_id_number_dec = NaN; + awi_struct.transmission_id_number_hex = NaN; + end + + awi_struct.wmo_id_number = store_if_available( ... + p_optimare_float.GENERAL_INFO, 'WMO_ID'); + + awi_struct.wmo_instrument_type = store_if_available( ... + p_optimare_float.GENERAL_INFO, 'WMO_INSTRUMENT_TYPE'); + + + + awi_struct.wmo_recorder_type = store_if_available( ... + p_optimare_float.GENERAL_INFO, 'WMO_RECORD_TYPE'); + + awi_struct.instrument_type = store_if_available( ... + p_optimare_float.GENERAL_INFO, 'INSTRUMENT_TYPE'); + + awi_struct.float_manufacturer = store_if_available( ... + p_optimare_float.GENERAL_INFO, 'FLOAT_MANUFACTURER'); + + awi_struct.float_serial_number = store_if_available( ... + p_optimare_float.GENERAL_INFO, 'SERIAL'); + + awi_struct.ice_detection_software = store_if_available( ... + p_optimare_float.SENSOR_INFO, 'ICE_DETECTION'); + + + awi_struct.float_provider = store_if_available( ... + p_optimare_float.PROVIDER_DEPLOYER_INFO, 'PROVIDER_NAME'); + + + awi_struct.float_provider_institution = store_if_available( ... + p_optimare_float.PROVIDER_DEPLOYER_INFO, 'PROVIDER_INSTITUTE'); + + awi_struct.originating_country = store_if_available( ... + p_optimare_float.PROVIDER_DEPLOYER_INFO, 'PROVIDER_COUNTRY'); + + + end + +%% OVERALL_MISSION_INFORMATION + + function [ awi_struct ] = overall_mission_information(p_profile_fieldname) + + awi_struct = {}; + + if isfield(p_optimare_float.DECODED_SBD.(p_profile_fieldname), 'TIME_CYCLE') + awi_struct.total_cycle = [ ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname).TIME_CYCLE/(60*24) ... + 0 0]; + else + awi_struct.total_cycle = NaN; + end + + awi_struct.down_time = NaN; + awi_struct.up_time = NaN; + awi_struct.transmission_repetition_rate = NaN; + awi_struct.clock_drift = NaN; + awi_struct.nominal_drift_depth = NaN; + awi_struct.max_departure_from_drift_depth = NaN; + + awi_struct.nominal_profile_depth = store_if_available( ... + p_optimare_float.MISSION_PARAMETERS, 'PROFILE_PRESSURE'); + + if isfield(p_optimare_float.DEPLOYMENT_INFO, 'START_DATE') + tmp_mat = strsplit(p_optimare_float.DEPLOYMENT_INFO.START_DATE, '.'); + awi_struct.start_date = [ ... + str2double(tmp_mat{1}) ... + str2double(tmp_mat{2}) ... + str2double(tmp_mat{3})]; + clear tmp_mat + else + awi_struct.start_date = NaN; + end + + if isfield(p_optimare_float.DEPLOYMENT_INFO, 'START_TIME') + tmp_mat = strsplit(p_optimare_float.DEPLOYMENT_INFO.START_TIME, ':'); + awi_struct.start_time = [ ... + str2double(tmp_mat{1}) ... + str2double(tmp_mat{2})]; + clear tmp_mat + else + awi_struct.start_time = NaN; + end + + awi_struct.RAFOS_clock_offset_at_start = NaN; + awi_struct.start_surface_pressure = NaN; + + awi_struct.ice_detection_temperature_threshold = store_if_available( ... + p_optimare_float.MISSION_PARAMETERS, 'ICE_TEMPERATURE'); + + + end + +%% DEPLOYMENT_INFO + function [ awi_struct ] = deployment_info() + + awi_struct.float_deployer = store_if_available( ... + p_optimare_float.PROVIDER_DEPLOYER_INFO, 'DEPLOYER_NAME'); + + awi_struct.float_deployer_institution = store_if_available( ... + p_optimare_float.PROVIDER_DEPLOYER_INFO, 'DEPLOYER_INSTITUTE'); + + awi_struct.deployment_platform_type = store_if_available( ... + p_optimare_float.DEPLOYMENT_INFO, 'DEPLOYMENT_PLATFORM'); + + awi_struct.deployment_platform_code = store_if_available( ... + p_optimare_float.DEPLOYMENT_INFO, 'DEPLOYMENT_PLATFORM_SIGN'); + + if isfield(p_optimare_float.DEPLOYMENT_INFO, 'DEPLOYMENT_PLATFORM_EXPEDITION') + tmp_string = strsplit( ... + p_optimare_float.DEPLOYMENT_INFO.DEPLOYMENT_PLATFORM_EXPEDITION, ... + '/'); + if length(tmp_string) == 2 + awi_struct.deployment_platform_cruise_name = tmp_string{1}; + awi_struct.deployment_platform_leg = tmp_string{2}; + else + awi_struct.deployment_platform_cruise_name = ... + p_optimare_float.DEPLOYMENT_INFO.DEPLOYMENT_PLATFORM_EXPEDITION; + awi_struct.deployment_platform_leg = 'none'; + end + clear tmp_string + else + awi_struct.deployment_platform_cruise_name = NaN; + awi_struct.deployment_platform_leg = 'none'; + end + + awi_struct.deployment_ctd_station = NaN; + + if isfield(p_optimare_float.DEPLOYMENT_INFO, 'DEPLOYMENT_DATE') + tmp_string = strsplit( ... + p_optimare_float.DEPLOYMENT_INFO.DEPLOYMENT_DATE, ... + '.'); + awi_struct.deployment_date = [ str2double(tmp_string{1}) ... + str2double(tmp_string{2}) ... + str2double(tmp_string{3}) ]; + clear tmp_string + else + awi_struct.deployment_date = NaN; + end + + if isfield(p_optimare_float.DEPLOYMENT_INFO, 'DEPLOYMENT_TIME') + tmp_string = strsplit( ... + p_optimare_float.DEPLOYMENT_INFO.DEPLOYMENT_TIME, ... + ':'); + awi_struct.deployment_time = [ str2double(tmp_string{1}) ... + str2double(tmp_string{2}) ]; + clear tmp_string + else + awi_struct.deployment_time = NaN; + end + + if isfield(p_optimare_float.DEPLOYMENT_INFO, 'DEPLOYMENT_LAT') ... + && isfield(p_optimare_float.DEPLOYMENT_INFO, 'DEPLOYMENT_LON') + awi_struct.deployment_position = [ ... + p_optimare_float.DEPLOYMENT_INFO.DEPLOYMENT_LAT ... + p_optimare_float.DEPLOYMENT_INFO.DEPLOYMENT_LON]; + else + awi_struct.deployment_position = NaN; + end + + awi_struct.deployment_speed = NaN; + + awi_struct.deployment_sea_state = store_if_available( ... + p_optimare_float.DEPLOYMENT_INFO, 'DEPLOYMENT_SEA_STATE'); + + awi_struct.deployment_wave_height = NaN; + + awi_struct.deployment_ice_coverage = store_if_available( ... + p_optimare_float.DEPLOYMENT_INFO, 'DEPLOYMENT_SEA_ICE'); + + awi_struct.delay_of_first_down_time = NaN; + + end + +%% PROFILE_TECHNICAL_DATA + + function [ awi_struct ] = profile_technical_data(p_profile_fieldname) + + awi_struct = {}; + + awi_struct.xmit_profile_number = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'PROFILE_NUMBER'); + + awi_struct.xmit_serial_number = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'SERIAL'); + + awi_struct.xmit_upcast_status = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'STATUS_UPCAST'); + + awi_struct.xmit_older_profiles_not_send = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'PROFILE_OLDER'); + + awi_struct.xmit_motor_errors_count = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'ERROR_MOTOR'); + + awi_struct.xmit_sbe42_ctd_errors = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'ERROR_CTD'); + + awi_struct.xmit_o2_optode_errors = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'ERROR_OPTODE'); + + awi_struct.xmit_rafos_errors = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'ERROR_RAFOS'); + + awi_struct.xmit_cpu_battery_voltage = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'BATT_CPU'); + + awi_struct.xmit_pump_battery_voltage = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'BATT_PMP'); + + awi_struct.xmit_motor_current = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'CURR_MOTOR'); + + awi_struct.xmit_motor_current_mean = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'CURR_MOTOR_MEAN'); + + awi_struct.xmit_internal_pressure_surface = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'PRES_TUBE_SURFACE'); + + awi_struct.xmit_internal_pressure_depth = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'PRES_TUBE_DEPTH'); + + awi_struct.xmit_pressure_offset = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'PRES_OFFSET'); + + awi_struct.xmit_surface_pressure = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'PRES_SURFACE'); + + awi_struct.xmit_parking_pressure_median = median(store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'DS2_PRES')); + + awi_struct.xmit_depth_pressure = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'PRES_DEPTH'); + + awi_struct.xmit_depth_pressure_max = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'PRES_MAX'); + + awi_struct.xmit_profile_recovery = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'RECOVERY_START'); + + awi_struct.xmit_piston_counts_surface = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'PIST_SURFACE'); + + awi_struct.xmit_piston_counts_parking = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'PIST_PARKING'); + + awi_struct.xmit_piston_counts_calc = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'PIST_CALC'); + + awi_struct.xmit_piston_counts_eop = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'PIST_EOP'); + + awi_struct.xmit_descent_start_time = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'MCNT_DESCENT'); + + awi_struct.xmit_parking_start_time = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'MCNT_PARKING'); + + awi_struct.xmit_upcast_start_time = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'MCNT_SAG'); + + awi_struct.xmit_ascent_start_time = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'MCNT_ASCENT'); + + awi_struct.xmit_ascent_end_time = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'MCNT_ASCENT_END'); + + awi_struct.xmit_surface_start_time = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'MCNT_SURFACE'); + + if isfield(p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'DATA_RECORDS2') ... + && length(p_optimare_float.DECODED_SBD.(p_profile_fieldname).DATA_RECORDS2) == 5 + awi_struct.xmit_profile_length_1 = ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname).DATA_RECORDS2(1); + awi_struct.xmit_profile_length_2 = ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname).DATA_RECORDS2(2); + awi_struct.xmit_profile_length_3 = ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname).DATA_RECORDS2(3); + awi_struct.xmit_profile_length_4 = ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname).DATA_RECORDS2(4); + awi_struct.xmit_profile_length_5 = ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname).DATA_RECORDS2(5); + else + awi_struct.xmit_profile_length_1 = NaN; + awi_struct.xmit_profile_length_2 = NaN; + awi_struct.xmit_profile_length_3 = NaN; + awi_struct.xmit_profile_length_4 = NaN; + awi_struct.xmit_profile_length_5 = NaN; + end + + awi_struct.xmit_surface_detection_GPS = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'ICEGPS_TIME'); + + awi_struct.xmit_surface_detection_Iridium = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'ICEIRDM_TIME'); + + awi_struct.xmit_airpump_runtime = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'AIRPUMP_RUNTIME'); + + awi_struct.xmit_ice_detection_temp_median = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), ... + 'ICE_TEMPMEDIAN'); + + end + +%% BOTTOM_VALUES_DURING_DRIFT + function [ awi_struct ] = bottom_values_during_drift(p_profile_fieldname) + + awi_struct = {}; + + awi_struct.xmit_bottom_pressure = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), 'DS2_PRES'); + + awi_struct.xmit_bottom_salt = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), 'DS2_SAL'); + + awi_struct.xmit_bottom_temperature = store_if_available( ... + p_optimare_float.DECODED_SBD.(p_profile_fieldname), 'DS2_TEMP'); + + end + +%% RAFOS_VALUES_FORMAT + + function [ awi_struct ] = rafos_values_format() + + awi_struct = { ... + 'status'; 'rtc_year '; 'rtc_month'; 'rtc_day'; 'rtc_hour'; ... + 'rtc_minute'; 'rtc_second'; '1STCORHEIGHT'; '1STTRAVELTIME'; ... + '2NDCORHEIGHT'; '2NDTRAVELTIME'; '3RDCORHEIGHT'; '3RDTRAVELTIME'; ... + '4THCORHEIGHT'; '4THTRAVELTIME'; '5THCORHEIGHT'; '5THTRAVELTIME'; ... + '6THCORHEIGHT'; '6THTRAVELTIME'; 'pressure'; 'salinity'; 'temp' ... + }'; + + end + +%% RAFOS_VALUES + + function [ awi_struct ] = rafos_values(p_profile_fieldname) + + p = p_optimare_float.DECODED_SBD.(p_profile_fieldname); + if isfield(p, 'DS3_DATE_year') && isfield(p, 'DS3_DATE_month') ... + && isfield(p, 'DS3_DATE_day') && isfield(p, 'DS3_TIME') ... + && isfield(p, 'DS3_STATUS') && isfield(p, 'DS3_RAFOSAMP') ... + && isfield(p, 'DS3_RAFOSRANK') && isfield(p, 'DS3_PRES') ... + && isfield(p, 'DS3_SAL') && isfield(p, 'DS3_TEMP') + + y = (1/(4836-4464) .* p.DS3_DATE_year) + 2000; + mon = p.DS3_DATE_month; + d = p.DS3_DATE_day; + + h = floor(p.DS3_TIME./3600); + m = floor((p.DS3_TIME - (h*3600))/60); + s = uint8(mod(p.DS3_TIME,60)); + + prefix = [p.DS3_STATUS', y', mon', d', h', m', double(s')]; + + awi_struct = [ ... + prefix, ... + p.DS3_RAFOSAMP(:, 1), p.DS3_RAFOSRANK(:, 1), ... + p.DS3_RAFOSAMP(:, 2), p.DS3_RAFOSRANK(:, 2), ... + p.DS3_RAFOSAMP(:, 3), p.DS3_RAFOSRANK(:, 3), ... + p.DS3_RAFOSAMP(:, 4), p.DS3_RAFOSRANK(:, 4), ... + p.DS3_RAFOSAMP(:, 5), p.DS3_RAFOSRANK(:, 5), ... + p.DS3_RAFOSAMP(:, 6), p.DS3_RAFOSRANK(:, 6), ... + p.DS3_PRES', p.DS3_SAL', p.DS3_TEMP' ... + ]; + else + awi_struct = []; + end + + + end + +%% PROFILE_HEADER + + function [ awi_struct ] = profile_header() + + awi_struct = {}; + + awi_struct.number_of_columns = 13; + awi_struct.column_1 = 'line_number'; + awi_struct.column_2 = 'hour'; + awi_struct.column_3 = 'minute'; + awi_struct.column_4 = 'second'; + awi_struct.column_5 = 'temperature [deg C]'; + awi_struct.column_6 = 'temperature flag'; + awi_struct.column_7 = 'pressure [dbar]'; + awi_struct.column_8 = 'pressure flag'; + awi_struct.column_9 = 'salinity [psu]'; + awi_struct.column_10 = 'salinity flag'; + awi_struct.column_11 = 'temperature raw'; + awi_struct.column_12 = 'pressure raw'; + awi_struct.column_13 = 'salinity raw'; + awi_struct.temperature_conversion_equation = 'T=cnts/1000, if(cnts>62535),cnts=cnts-65536'; + awi_struct.pressure_conversion_equation = 'P=cnts/10'; + awi_struct.salinity_conversion_equation = 'S=cnts/1000'; + + end + +%% QUALITY_CONTROL_HEADER + + function [ awi_struct ] = qualtiy_control_header() + + awi_struct = {}; + + awi_struct.overall_flag_for_T = 0; + awi_struct.overall_flag_for_P = 0; + awi_struct.overall_flag_for_S = 0; + awi_struct.maximum_speed_check_flag = 0; + awi_struct.maximum_analysis_check_T_flag = 0; + awi_struct.maximum_climatology_T_flag = 0; + awi_struct.maximum_gross_check_T_flag = 0; + awi_struct.maximum_vertical_gradient_T_flag = 0; + awi_struct.maximum_spike_check_T_flag = 0; + awi_struct.maximum_analysis_check_S_flag = 0; + awi_struct.maximum_climatology_S_flag = 0; + awi_struct.maximum_gross_check_S_flag = 0; + awi_struct.maximum_vertical_gradient_S_flag = 0; + awi_struct.maximum_spike_check_S_flag = 0; + awi_struct.sigma_used_for_T_climatology = 10; + awi_struct.sigma_used_for_T_analysis = 10; + awi_struct.sigma_used_for_S_climatology = 10; + awi_struct.sigma_used_for_S_analysis = 10; + + end + +%% PROFILE_DATA_HEADER + + function [ awi_struct ] = profile_data_header() + + awi_struct = { ... + '#'; 'hour'; 'minute'; 'second'; 'temp'; 'temp_flag'; 'pressure'; ... + 'pres_flag'; 'salinity'; 'sal_flag'; 'temp_raw'; 'pres_raw'; ... + 'sal_raw'; 'light_442_nm'; 'light_550_nm'; 'light_676_nm' ... + }'; + + end + +%% PROFILE_DATA + + function [ awi_struct ] = profile_data(p_profile_fieldname) + + p = p_optimare_float.DECODED_SBD.(p_profile_fieldname); + if isfield(p, 'DS4_TIME') && isfield(p, 'DS4_TEMP') ... + && isfield(p, 'DS4_PRES') && isfield(p, 'DS4_SAL') + + h = floor(p.DS4_TIME./3600); + m = floor((p.DS4_TIME - (h*3600))/60); + s = uint8(mod(p.DS4_TIME, 60)); + profile_numbers = 1:length(h); + flags = zeros(1, length(h)); + + prefix = [profile_numbers', h', m', double(s')]; + + % somtimes LICH1-3 has different length than TEMP, PRES & SAL + if ~isfield(p, 'DS4_LICH1') || ~isfield(p, 'DS4_LICH2') ... + || ~isfield(p, 'DS4_LICH3') || (length(p.DS4_LICH1) ~= length(p.DS4_TEMP)) + LICH1 = flags; + LICH2 = flags; + LICH3 = flags; + else + LICH1 = p.DS4_LICH1; + LICH2 = p.DS4_LICH2; + LICH3 = p.DS4_LICH3; + end + + awi_struct = [ ... + prefix, ... + p.DS4_TEMP', flags', ... + p.DS4_PRES', flags', ... + p.DS4_SAL', flags', ... + p.DS4_TEMP', p.DS4_PRES', p.DS4_SAL', ... + LICH1', LICH2', LICH3' ... + ]; + else + awi_struct = []; + end + + + end + +%% SURFACE_GPS_DATA_FORMAT + + function [ awi_struct ] = surface_gps_data_format() + + awi_struct = { ... + '#'; 'rtc_year'; 'rtc_month'; 'rtc_day'; 'rtc_hour'; 'rtc_minute'; ... + 'rtc_second'; 'GPS_year'; 'GPS_month'; 'GPS_day'; 'GPS_hour'; ... + 'GPS_minute'; 'GPS_second'; 'lat'; 'lon' ... + }'; + + end + +%% SURFACE_GPS_DATA + + function [ awi_struct ] = surface_gps_data(p_profile_fieldname) + + p = p_optimare_float.DECODED_SBD.(p_profile_fieldname); + if isfield(p, 'DS5_GPS') && isfield(p, 'DS5_RTC') ... + && isfield(p, 'DS5_LAT') && isfield(p, 'DS5_LON') + + gps_datevec = datevec(p.DS5_GPS); + % check if gps date is correct + if gps_datevec(1) == 1999 && gps_datevec(2) == 12 ... + && gps_datevec(3) == 31 + gps_datevec = [NaN, NaN, NaN, NaN, NaN, NaN]; + end + + awi_struct = [ ... + 1, ... + datevec(p.DS5_RTC), ... + gps_datevec, ... + p.DS5_LAT, ... + p.DS5_LON ... + ]; + else + awi_struct = []; + end + + + end + +%% IRIDIUM_POSITIONS_FORMAT + + function [ awi_struct ] = iridium_positions_format() + + awi_struct = { ... + 'sbd_lat'; 'sbd_lon'; 'sbd_cep'; ... + 'yyyy'; 'mm'; 'dd'; ... + 'hh'; 'mm'; 'ss' + }'; + + end + +%% IRIDIUM_POSITIONS + + function [ iridium_positions, iridium_best ] = ... + iridium_positions_and_data(p_profile_fieldname) + + p = p_optimare_float.DECODED_SBD.(p_profile_fieldname); + sbd_info = p_optimare_float.SBD_MESSAGES_INFO; + + if ~isfield(p_optimare_float.TRANSMISSION_INFO, 'MOMSN') ... + || ~isfield(p_optimare_float.TRANSMISSION_INFO, 'TIMEOFSESSION') ... + || ~isfield(p_optimare_float.TRANSMISSION_INFO, 'IRIDIUM_LAT') ... + || ~isfield(p_optimare_float.TRANSMISSION_INFO, 'IRIDIUM_LON') ... + || ~isfield(p_optimare_float.TRANSMISSION_INFO, 'CEPRAD') + return; % one of the fields is not available + end + + % get transmission info + momsn = p_optimare_float.TRANSMISSION_INFO.MOMSN; + time_of_session = p_optimare_float.TRANSMISSION_INFO.TIMEOFSESSION; + lat = p_optimare_float.TRANSMISSION_INFO.IRIDIUM_LAT; + lon = p_optimare_float.TRANSMISSION_INFO.IRIDIUM_LON; + cep = p_optimare_float.TRANSMISSION_INFO.CEPRAD; + + sbd_info_count = length(sbd_info); + + ir_lat = []; + ir_lon = []; + ir_cep = []; + ir_time = []; + ir_time_datevec = []; + + % get all profile numbers of the transmitted iridium messsages + table_sbd_info = struct2table(sbd_info); + profile_number_lsb = table_sbd_info(:, {'profilenumber_lsb'}); + % transform them to be able to use it as comparison + profile_number_lsb = table2array(profile_number_lsb); + % compare them to the current one + tmp_logical_array = (profile_number_lsb(:) == p.PROFILE_NUMBER); + % get all corresponding messages + iridium_messages = table_sbd_info(tmp_logical_array, :); + iridium_messages_count = size(iridium_messages, 1); + + for o_iridium = 1:iridium_messages_count + tmp_logical_array = (momsn == iridium_messages(o_iridium, :).momsn); + % get current lat + current_lat = lat(tmp_logical_array); + current_lon = lon(tmp_logical_array); + current_cep = cep(tmp_logical_array); + current_time = time_of_session(tmp_logical_array); + current_time_datevec = datevec(current_time); + if length(current_lat) > 1 + % get line with best cep + [~, index] = min(current_cep); + current_lat = nanmean(current_lat(index)); + current_lon = nanmean(current_lon(index)); + current_cep = nanmean(current_cep(index)); + current_time = nanmean(current_time(index)); + current_time_datevec = datevec(current_time); + end + ir_lat = [ir_lat; current_lat]; + ir_lon = [ir_lon; current_lon]; + ir_cep = [ir_cep; current_cep]; + ir_time = [ir_time; current_time]; + ir_time_datevec = [ir_time_datevec; current_time_datevec]; + end + + + if isempty(ir_lat) || isempty(ir_lon) || isempty(ir_cep) ... + || isempty(ir_time) + iridium_positions = []; + iridium_best = []; + return +% else +% % delete all cep > 10, if there is nothing left store all to prevent +% % NaN's +% tmp_logical_array = (ir_cep < 10); +% if sum(tmp_logical_array) > 0 +% ir_lat = ir_lat(tmp_logical_array); +% ir_lon = ir_lon(tmp_logical_array); +% ir_cep = ir_cep(tmp_logical_array); +% ir_time = ir_time(tmp_logical_array); +% ir_time_datevec = datevec(ir_time); +% end + end + + % create return structure for all positions + iridium_positions = [ ... + ir_lat, ir_lon, ir_cep, ir_time_datevec ... + ]; + + % get best position + best_ceps = unique(ir_cep); + ix_best = (ir_cep == min(best_ceps)); + ir_lat_mean = nanmean(ir_lat(ix_best)); + ir_lon_mean = nanmean(ir_lon(ix_best)); + ir_cep_mean = nanmean(ir_cep(ix_best)); + %ir_time_mean = nanmean(ir_time(ix_best)); + + iridium_best = [ ... + ir_lat_mean, ir_lon_mean, ir_cep_mean, ir_time_datevec(end, :) + ]; + + end + +%% IRIDIUM_DATA_FORMAT + + function [ awi_struct ] = iridium_data_format() + + awi_struct = { ... + 'mean_lat'; 'mean_lon'; 'mean_cep'; ... + 'yyyy'; 'mm'; 'dd'; ... + 'hh'; 'mm'; 'ss' + }'; + + end + +%% SORT IRIDIUM MESSAGES + + function [ profile_numbers_sorted, indices ] = sort_iridium_messages(p_profile_fieldnames) + profile_numbers_unsorted = []; + tmp_valid_profile_count = 0; + % collect all profile numbers + for o_fields = 1:length(p_profile_fieldnames) + current_profile = ... + p_optimare_float.DECODED_SBD.(p_profile_fieldnames{o_fields}); + if ~strcmp(p_optimare_float.DECODED_SBD.(p_profile_fieldnames{o_fields}).DATATYPE, 'Profile Message') ... + || ~isfield(p_optimare_float.DECODED_SBD.(p_profile_fieldnames{o_fields}), 'PROFILE_NUMBER') ... + || isnan(p_optimare_float.DECODED_SBD.(p_profile_fieldnames{o_fields}).PROFILE_NUMBER) + continue; + end + tmp_valid_profile_count = tmp_valid_profile_count + 1; + profile_numbers_unsorted(tmp_valid_profile_count, :) = ... + [p_optimare_float.DECODED_SBD.(p_profile_fieldnames{o_fields}).PROFILE_NUMBER, ... + o_fields]; + end + % sort them + if ~isempty(profile_numbers_unsorted) + [profile_numbers_sorted, indices] = sortrows(profile_numbers_unsorted, 1); + indices = profile_numbers_sorted(:, 2); + else + indices = false; + profile_numbers_sorted = false; + end + end + + function [best_ctd_indices] = filter_multiple_profiles(sorted_indices) + % initialize return variable + best_ctd_indices = []; + best_indices_count = 0; + % get profile number of the first element + if isfield(p_optimare_float.DECODED_SBD.(profile_fieldnames{sorted_indices(1)}), 'DATA_RECORDS2') ... + && p_optimare_float.DECODED_SBD.(profile_fieldnames{sorted_indices(1)}).DATA_RECORDS2(4) > 0 + % if it is < 0, there is no possibility to compare CTD data + best_indices_count = best_indices_count + 1; + last_profile_number = p_optimare_float.DECODED_SBD.(profile_fieldnames{sorted_indices(1)}).PROFILE_NUMBER; + best_ctd_indices(best_indices_count) = sorted_indices(1); + end + + for o_index = 2:length(sorted_indices) + if ~isfield(p_optimare_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}), 'DATA_RECORDS2') ... + || p_optimare_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}).DATA_RECORDS2(4) == 0 + continue + end + % get current profile number + current_profile_number = p_optimare_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}).PROFILE_NUMBER; + if best_indices_count > 0 && current_profile_number == last_profile_number + % check which ctd data is better + current_ctd_length = p_optimare_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}).DATA_RECORDS2(4); + last_ctd_length = p_optimare_float.DECODED_SBD.(profile_fieldnames{best_ctd_indices(best_indices_count)}).DATA_RECORDS2(4); + if current_ctd_length > last_ctd_length + best_ctd_indices(best_indices_count) = sorted_indices(o_index); + elseif current_ctd_length == last_ctd_length + % take the one without or less NaN's + tmp_current = [ ... + p_optimare_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}).DS4_TIME; ... + p_optimare_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}).DS4_FORMAT; ... + p_optimare_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}).DS4_PRES; ... + p_optimare_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}).DS4_TEMP; ... + p_optimare_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}).DS4_SAL ... + ]; + tmp_last = [ ... + p_optimare_float.DECODED_SBD.(profile_fieldnames{best_ctd_indices(best_indices_count)}).DS4_TIME; ... + p_optimare_float.DECODED_SBD.(profile_fieldnames{best_ctd_indices(best_indices_count)}).DS4_FORMAT; ... + p_optimare_float.DECODED_SBD.(profile_fieldnames{best_ctd_indices(best_indices_count)}).DS4_PRES; ... + p_optimare_float.DECODED_SBD.(profile_fieldnames{best_ctd_indices(best_indices_count)}).DS4_TEMP; ... + p_optimare_float.DECODED_SBD.(profile_fieldnames{best_ctd_indices(best_indices_count)}).DS4_SAL ... + ]; + sum_current = sum(sum(isnan(tmp_current))); + sum_last = sum(sum(isnan(tmp_last))); + if sum_last > sum_current + best_ctd_indices(best_indices_count) = sorted_indices(o_index); + end + clear tmp_current tmp_last sum_current sum_last + + end + else + best_indices_count = best_indices_count + 1; + best_ctd_indices(best_indices_count) = sorted_indices(o_index); + end + + last_profile_number = current_profile_number; + end + end + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+convert/optimare2nemostruct.m b/lib/vendor/nemo2profilelib/+nemo/+convert/optimare2nemostruct.m new file mode 100644 index 0000000..ff7c052 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+convert/optimare2nemostruct.m @@ -0,0 +1,48 @@ +function [ boolean, nemo_data ] = optimare2nemostruct( p_optimare_data ) +%OPTIMARE2NEMOSTRUCT Converts the optimare data structure to nemo format. +% Converts the given optimare data structure to the nemo data format that +% is used by this library. +% +% Parameters: +% p_optimare_float: Data structure created by the decoding process +% written by optimare. In case of the source +% nemo2profile, this should be +% handles.all_floats.(float_name) +% +% Returns: +% boolean: False if the conversion failed. +% nemo_data: The converted float data. +% + +%% Initialize return variables +boolean = false; +nemo_data = {}; + +%% Parameter check (not implemented yet) + +floatnames = fieldnames(p_optimare_data); +converted_floatcount = 0; + +for o_float = 1:length(floatnames) + + % get current float data + current_float = p_optimare_data.(floatnames{o_float}); + + converted_float = nemo.convert.float(current_float); + + if ~iscell(converted_float) + warning(['Error converting ' floatnames{o_float} '!']); + else + % store it to nemo data + converted_floatcount = converted_floatcount + 1; + nemo_data{converted_floatcount} = converted_float; + clear converted_float + end + +end + +boolean = true; + + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+float/collect.m b/lib/vendor/nemo2profilelib/+nemo/+float/collect.m new file mode 100644 index 0000000..9e8bd64 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+float/collect.m @@ -0,0 +1,192 @@ +function [ data ] = collect( p_nemo_float, p_section, p_member, p_options ) +%COLLECT Collects given section and/or member of float for every profile. +% Iterates through every profile in the given nemo float and stores the +% selected data either into a cell or a matrix depending on the class +% that is being collected. This function is helpful if you require one +% parameter of the float of all profiles in one variable. +% +% Parameters: +% p_nemo_float: Float from which the data is being collected. +% p_section: String, Section identifier of the data that should +% be stored. +% p_member: String, member of the section that should be +% stored. If this equals an empty string, the +% complete section is being stored. +% p_options: Currently the following options can be passed to +% the function: +% +% 'col2mat, {COLUMN_INTEGER}' +% -> collects the given column and combines it to a +% matrix (used for collecting pressure data of all +% profiles for example) +% +% 'combine' +% -> collects the given section/member and combines +% it as a matrix +% +% Returns: +% data: Collected data for every profile of the given +% float. + +%% Initialize return variable +data = false; + +%% Parameter check +parameter_error = false; + +if (~iscell(p_nemo_float) || ~ischar(p_section)) + parameter_error = true; +end + +if (exist('p_options', 'var') && ~ischar(p_options)) + parameter_error = true; +elseif (~exist('p_options', 'var')) + p_options = ''; +end + +if (parameter_error) % display error message + disp([ mfilename ': Wrong parameter! Please check your input parameters carefully!']); + return; +else % do some cleanup + clear parameter_error; +end + +%% Collect data + +profile_count = length(p_nemo_float); + +if (profile_count == 0) + return; +end + +% Calculate internal clock if option is given +if (strcmp(p_section, 'xmitDatevec')) + data = collect_and_calculate_xmit_datevec(p_member); + return +end + +if ~exist('p_member', 'var') || strcmp(p_member, '') % the whole section is being collected + + switch class(p_nemo_float{1}.(p_section)) + + case 'double' % it may be possible to store it as matrix + % be sure that data is always one line + one_line_double = true; + column_count = 0; + for o_profiles = 1:profile_count + if ~isempty(p_nemo_float{o_profiles}.(p_section)) ... + && size(p_nemo_float{o_profiles}.(p_section), 1) ~= 1 + one_line_double = false; + break; + end + if column_count == 0 && ~isempty(p_nemo_float{o_profiles}.(p_section)) + column_count = size(p_nemo_float{o_profiles}.(p_section), 2); + end + end + + if one_line_double + % only one line per profile, so create a matrix + data = []; + for o_profiles = 1:profile_count + if ~isempty(p_nemo_float{o_profiles}.(p_section)) + data(o_profiles, :) = p_nemo_float{o_profiles}.(p_section); + else + data(o_profiles, 1:column_count) = NaN; + end + end + elseif (is_option_set('col2mat')) + % the given column will be collected from all profiles and + % combined into a column-wise matrix + all_options = get_options(p_options); + [column_number, status] = str2int(all_options{2}); + if (~status) + disp([mfilename ': Cannot convert option number 2 to integer!']); + return + end + data = []; + for o_profiles = 1:profile_count + if isfield(p_nemo_float{o_profiles}, p_section) + data = [data, p_nemo_float{o_profiles}.(p_section)(:, column_number)]; + end + end + elseif (is_option_set('combine')) + % user selected to get a matrix containing all values + % instead of seperated cells + data = []; + for o_profiles = 1:profile_count + if isfield(p_nemo_float{o_profiles}, p_section) + data = [data; p_nemo_float{o_profiles}.(p_section)]; + end + end + else + % create a struct because there are more lines in every profile + data = {}; + for o_profiles = 1:profile_count + if ~isfield(p_nemo_float{o_profiles}, p_section) + data{o_profiles} = NaN; + else + data{o_profiles} = p_nemo_float{o_profiles}.(p_section); + end + end + end + + otherwise + data = {}; + for o_profiles = 1:profile_count + data{o_profiles} = p_nemo_float{o_profiles}.(p_section); + end + + end +else + % exact the same passage as above, but in this one there is a member given + switch class(p_nemo_float{1}.(p_section).(p_member)) + + case 'double' + if size(p_nemo_float{1}.(p_section).(p_member), 1) == 1 + % only one line, so create a matrix + data = []; + for o_profiles = 1:profile_count + data(o_profiles, :) = p_nemo_float{o_profiles}.(p_section).(p_member); + end + else + data = {}; + for o_profiles = 1:profile_count + data{o_profiles} = p_nemo_float{o_profiles}.(p_section).(p_member); + end + end + + otherwise + data = {}; + for o_profiles = 1:profile_count + data{o_profiles} = p_nemo_float{o_profiles}.(p_section).(p_member); + end + end + +end + + function boolean = is_option_set(option_string) + options = strsplit(p_options, ','); + boolean = any(cellfun(@(x) strcmp(x, option_string), options)); + end + + function options = get_options(option_string) + options = strtrim(strsplit(p_options, ',')); + end + + function data = collect_and_calculate_xmit_datevec(p_xmit_parameter_name) + data = NaN(profile_count, 6); + for i = 1:profile_count + data(i, :) = ... + datevec( ... + datetime([ ... + fliplr(p_nemo_float{i}.OVERALL_MISSION_INFORMATION.start_date), ... + p_nemo_float{i}.OVERALL_MISSION_INFORMATION.start_time 0 ... + ]) ... + + seconds(p_nemo_float{i}.PROFILE_TECHNICAL_DATA.(p_xmit_parameter_name)) ... + ); + end + end + + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+float/extract_startup_message.m b/lib/vendor/nemo2profilelib/+nemo/+float/extract_startup_message.m new file mode 100644 index 0000000..adcef52 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+float/extract_startup_message.m @@ -0,0 +1,60 @@ +function [ startup_messages ] = extract_startup_message( p_combined_float ) +%DECODE_STARTUP_MESSAGE Extracts the startup messages of the given float +% Detailed explanation goes here + +%% Initialize return variable +startup_messages = false; + +%% Check if float is not empty +if ~isfield(p_combined_float, 'DECODED_SBD') + % no data available + return +end + +%% Search startup messages and get them into the awi structure + +startup_messages = {}; + +% get fieldnames of every profile +profile_fieldnames = fieldnames(p_combined_float.DECODED_SBD); + +for i = 1:length(profile_fieldnames) + + if ~strcmp( ... + p_combined_float.DECODED_SBD.(profile_fieldnames{i}).DATATYPE, ... + 'Startup Test Message' ... + ) + % everything that is not a profile or a test message is being ignored + continue + else + % extract the message + current_message = p_combined_float.DECODED_SBD.(profile_fieldnames{i}); + startup_messages{end + 1} = struct(); + startup_messages{end}.float_serial_number = current_message.SERIAL; + startup_messages{end}.software_version = current_message.SOFTWARE; + startup_messages{end}.real_time_clock = current_message.RTC; + startup_messages{end}.piston_max_count = current_message.PISTON_MAX; + startup_messages{end}.piston_min_count = current_message.PISTON_MIN; + startup_messages{end}.cpu_battery_voltage = current_message.BATT_CPU; + startup_messages{end}.pump_battery_voltage = current_message.BATT_PMP; + startup_messages{end}.ext_battery_voltage = current_message.BATT_EXT; + startup_messages{end}.pressure_tube_surface = current_message.PRES_TUBE_SURFACE; + startup_messages{end}.pressure_profile_depth = current_message.PRES_PROFILE; + startup_messages{end}.transmission_time = current_message.TIME_TX; + startup_messages{end}.parking_sample_time = current_message.TIME_PARKSAMPLE; + startup_messages{end}.cycle_time = current_message.TIME_CYCLE; + startup_messages{end}.descent_mode = current_message.DESCENT_MODE; + startup_messages{end}.start_recovery = current_message.RECOVERY_START; + startup_messages{end}.descent_speed = current_message.DESCENT_SPEED; + startup_messages{end}.ice_detection_temperature = current_message.TEMP_ICEDETECT; + startup_messages{end}.recovery_transmission = current_message.RECOVERY_TX; + startup_messages{end}.recovery_no_transmission = current_message.RECOVERY_NOTX; + startup_messages{end}.gps_datetime = current_message.GPS_DATETIME; + startup_messages{end}.gps_lat = current_message.GPS_LAT; + startup_messages{end}.gps_lon = current_message.GPS_LON; + end + +end + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+float/fetch_process_mails.m b/lib/vendor/nemo2profilelib/+nemo/+float/fetch_process_mails.m new file mode 100644 index 0000000..3c7d0f3 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+float/fetch_process_mails.m @@ -0,0 +1,202 @@ +function [ boolean, float_imeis ] = fetch_process_mails( p_config, p_metadata ) +%FETCH_MAILS Fetches the mails of the floats in p_metadata +% Fetches mails of the given floats in p_metadata. For this, javax.mail +% package is being used. +% +% Parameters: +% p_config: Configuration structure, must contain the +% following values: +% -username {string} +% -password {string} +% -port {integer} +% -ssl {0 or 1} +% -server {string} +% -imap_timeout {integer} +% -imaps_connection_timeout {integer} +% -storage_directory {string} +% -folder_to_fetch {string} +% -sort_by_imei {0 or 1} +% -separate_sbd_and_txt {0 or 1} +% -mark_read_after_fetch {0 or 1} +% -fetch_unread_only {0 or 1} +% -sender_address {string} +% -use_sender_address {0 or 1} +% +% Returns: +% boolean: True, if successful. +% float_imeis List with length of p_metadata. Contains either 0 +% or the IMEI number of the float if there are new +% mails that have been fetched. + +%% Initialize return variables +boolean = false; + +%% Parameter check +if (~isstruct(p_config) || (~isstruct(p_metadata) && ~iscell(p_metadata))) + warning([ mfilename ': Wrong parameter! Please check your input parameters carefully!']); +end + +%% Setup some alias +fetch_unread_only = p_config.MAIL.fetch_unread_only; +mark_read_after_fetch = p_config.MAIL.mark_read_after_fetch; +sort_by_imei = p_config.MAIL.sort_by_imei; +storage_directory = p_config.MAIL.storage_directory; +separate_sbd_and_txt = p_config.MAIL.separate_sbd_and_txt; + +%% Setup folder +if (~isdir(storage_directory)) + mkdir(storage_directory); +end + +%% Connect to mail server +imapstore = nemo.mail.connect(p_config); +if (~imapstore.isConnected()) % warning is already given in connect() + return; +end + +%% Create search and flag term +% there are difficulties accessing static class members of an inner java +% class. This solution has been found at: +% http://undocumentedmatlab.com/blog/accessing-internal-java-class-members +if (fetch_unread_only) + search_flag = javax.mail.Flags(); % create search flag + tmp = search_flag.getClass.getDeclaredClasses; + seen_flag = tmp(1).getField('SEEN').get(1); + clear tmp; + search_flag.add(seen_flag); + flag_term = javax.mail.search.FlagTerm(search_flag, false); +else + flag_term = false; +end + +if (~isfield(p_config.MAIL, 'sender_address') || ~isfield(p_config.MAIL, 'use_sender_address')) + warning([mfilename ': Config does not include a MAIL.sender_address or MAIL.use_sender_address field!']); + return; +elseif (p_config.MAIL.use_sender_address) + java_sender_address = java.lang.String(p_config.MAIL.sender_address); + sender_term = javax.mail.search.FromStringTerm(java_sender_address); +else + sender_term = false; +end + + + +%% Fetch all mails and save them to disk + +switch class(p_metadata) + case 'struct' + % only one float has to be processed + [boolean, float_imeis] = process_float(p_metadata); + case 'cell' + [booleans, float_imeis] = cellfun(@process_float, p_metadata); + if (~all(booleans)) + warning([mfilname ': Something went wrong during message download! Please contact developer or system administrator!']); + else + boolean = true; + end +end + +clear imapstore; + + + function [boolean, float_imei] = process_float(p_float_metadata) + imei_int = p_float_metadata.GENERAL_INFO.IMEI; + imei_str = int2str(imei_int); + float_imei = 0; % will be overridden if there are messages to process + %% Check for directories + if (sort_by_imei) + float_storage_directory = [storage_directory imei_str p_config.GENERAL.folder_separator]; + else + float_storage_directory = storage_directory; + end + % check if folders exists + if (~isdir(float_storage_directory)) + mkdir(float_storage_directory); + end + if (separate_sbd_and_txt) + float_txt_directory = [float_storage_directory 'txt' p_config.GENERAL.folder_separator]; + float_sbd_directory = [float_storage_directory 'sbd' p_config.GENERAL.folder_separator]; + float_incomplete_directory = [float_storage_directory 'incomplete' p_config.GENERAL.folder_separator]; + if (~isdir(float_sbd_directory)) + mkdir(float_sbd_directory); + end + if (~isdir(float_txt_directory)) + mkdir(float_txt_directory); + end + if (~isdir(float_incomplete_directory)) + mkdir(float_incomplete_directory); + end + else + float_sbd_directory = float_storage_directory; + float_txt_directory = float_sbd_directory; + end + + %% Create term for message filtering + subject_term = javax.mail.search.SubjectTerm(imei_str); + if isa(flag_term, 'javax.mail.search.FlagTerm') ... + && isa(sender_term, 'javax.mail.search.FromStringTerm') + tmp_comb = javax.mail.search.AndTerm(subject_term, flag_term); + combined_terms = javax.mail.search.AndTerm(tmp_comb, sender_term); + elseif isa(flag_term, 'javax.mail.search.FlagTerm') + combined_terms = javax.mail.search.AndTerm(flag_term, subject_term); + elseif isa(sender_term, 'javax.mail.search.FromStringTerm') + combined_terms = javax.mail.search.AndTerm(sender_term, subject_term); + else + combined_terms = subject_term; + end + + %% Get mail that fit the terms + [messages, folder] = nemo.mail.fetch_messages(imapstore, ... + p_config.MAIL.folder_to_fetch, ... + combined_terms, ... + ~mark_read_after_fetch ... + ); + + %% Process all messages + for i = 1:messages.length + [from, ~, inline, attachments] = nemo.mail.extract(messages(i)); + if (p_config.MAIL.use_sender_address && ~strcmp(from, p_config.MAIL.sender_address)) + disp([mfilename ': Filter fetched the wrong address: ' from{1} '! Skipping message...']); + continue; + end + + if (~isempty(attachments)) + % store the float serial + float_imei = imei_int; + filename = char(attachments{1}); + [~, file, ~] = fileparts(filename); + % export inline as well as attachment + nemo.mail.export_attachment(attachments{2}, [float_sbd_directory filename]); + % save inline + fid = fopen([float_txt_directory file '.txt'], 'wt'); + fprintf(fid, inline); + fclose(fid); + else % mail has been received but no attachment + % export only inline to incomplete folder + processed_inline = nemo.mail.process_inline(inline); + fid = fopen([float_incomplete_directory int2str(processed_inline.MOMSN) '.txt'], 'wt'); + fprintf(fid, inline); + fclose(fid); + end + clear from subject inline attachments filename; + end + + if (mark_read_after_fetch && messages.length > 0) + % mark processed as read + flag = javax.mail.Flags(); % create search flag + tmp_class = flag.getClass.getDeclaredClasses; + flag_seen = tmp_class(1).getField('SEEN').get(1); + clear tmp_class; + flag.add(flag_seen); + folder.setFlags(messages, flag, true); + clear flag + end + clear messages combined_terms subject_term messages folder; + + boolean = true; + + end + + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+float/find.m b/lib/vendor/nemo2profilelib/+nemo/+float/find.m new file mode 100644 index 0000000..3725dfa --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+float/find.m @@ -0,0 +1,49 @@ +function [ floats, indices ] = find( p_nemo_data, p_float_number ) +%FIND Returns the float with the given p_float_number. +% Searches for the given float in p_nemo_struct. +% +% Parameters: +% p_nemo_data: The nemo data to be searched. +% p_float_number: The float number that is being searched for. +% It can also be a 1D vector of float numbers. +% +% Returns: +% float: Copy from p_nemo_data of the float with the given +% float number. +% index: The index of the float in p_nemo_data. + +%index = cellfun(@(x) x{1}.FLOAT_IDENTIFICATION.float_serial_number == p_float_number, p_nemo_data); + +indices = []; +floats = {}; + +float_number_length = length(p_float_number); +for current_float_number = 1:float_number_length + index = cellfun(@(x) find_float(x), p_nemo_data); + if (~any(index)) + continue; + elseif (sum(index) > 1) + disp(['Multiple floats found: ' index]); + return + end + + indices = [indices find(index)]; + floats{end + 1} = p_nemo_data{index}; + + +end + +if (length(p_float_number) == 1) + floats = floats{1}; +end + + function [ is_float ] = find_float(p_float) + if isfield(p_float, 'GENERAL_INFO') + is_float = p_float.GENERAL_INFO.SERIAL == p_float_number(current_float_number); + else + is_float = p_float{1}.FLOAT_IDENTIFICATION.float_serial_number == p_float_number(current_float_number); + end + end + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+float/get_changed_profiles.m b/lib/vendor/nemo2profilelib/+nemo/+float/get_changed_profiles.m new file mode 100644 index 0000000..8e506d8 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+float/get_changed_profiles.m @@ -0,0 +1,57 @@ +function [ changed_profiles ] = get_changed_profiles( p_config, p_float ) +%GET_CHANGED_PROFILES Loads the old profiles from the harddisk and compares them to the structure given in p_float. +% Compares the old profiles that are saved on the harddisk with the given +% float in p_float. Compares the profiles by using the +% nemo.profile.compare function. +% +% Parameters: +% p_config: The nemo configuration file. +% p_float: The nemo data where the old profiles will be +% compared with. +% +% Returns: +% changed_profiles: Cell array containing the profiles that have +% changed. + +%% Initialize return variables +changed_profiles = false; + +%% Parameter check +if (~isstruct(p_config) || ~iscell(p_float)) + disp([ mfilename ': Please check your parameter!']); + return; +end + +%% Save the float to a temporary folder and load it afterwards +% this is because the comparison does not work with a float loaded from HDD +% and directly converted from SBD messages in RAM + + +%% Load old profiles + +old_profiles = nemo.load.data( ... + [ ... + p_config.OUTPUT_FILES.output_path ... + sprintf('%04d', p_float{1}.FLOAT_IDENTIFICATION.float_serial_number) ... + ] ... +); + +%% Compare them to the current ones + +old_profiles = nemo.float.sort_by_profile_number(old_profiles); +p_float = nemo.float.sort_by_profile_number(p_float); +changed_profiles = {}; + +for i = 1:length(p_float) + current_profile = p_float{i}; + old_profile = nemo.profile.find(old_profiles, current_profile.PROFILE_TECHNICAL_DATA.xmit_profile_number); + if (isa(old_profile, 'logical') && ~old_profile) ... + || ~nemo.profile.compare(old_profile, current_profile) + changed_profiles{end + 1} = current_profile; + continue; + end +end + + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+float/process_mission_parameter.m b/lib/vendor/nemo2profilelib/+nemo/+float/process_mission_parameter.m new file mode 100644 index 0000000..b9f6909 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+float/process_mission_parameter.m @@ -0,0 +1,127 @@ +function [ mission_parameters ] = process_mission_parameter( p_mission_parameter_string ) +%PROCESS_MISSION_PARAMETER Summary of this function goes here +% Detailed explanation goes here + + + try + ix = regexp(p_mission_parameter_string,'Serial number ','end'); + mission_parameters.SERIAL=str2num(p_mission_parameter_string(ix:ix+3)); + catch + mission_parameters.SERIAL=NaN; + end + clear ix + + try + ix = regexp(p_mission_parameter_string,'Firmware version ','end'); + mission_parameters.FIRMWARE_VERSION=str2num(p_mission_parameter_string(ix:ix+6)); + catch + mission_parameters.FIRMWARE_VERSION=NaN; + end + clear ix + + try + ix = regexp(p_mission_parameter_string,'Startup time: ','end'); + mission_parameters.STARTUP_TIME=str2num(p_mission_parameter_string(ix:ix+4)); + catch + mission_parameters.STARTUP_TIME=NaN; + end + clear ix + + try + ix = regexp(p_mission_parameter_string,'Cycle time: ','end'); + mission_parameters.START_CYCLETIME=str2num(p_mission_parameter_string(ix:ix+5)); + catch + mission_parameters.START_CYCLETIME=NaN; + end + clear ix + + try + ix = regexp(p_mission_parameter_string,'Change cycletime to ','end'); + mission_parameters.CYCLETIME=str2num(p_mission_parameter_string(ix:ix+6)); + catch + mission_parameters.CYCLETIME=NaN; + end + clear ix + + try + ix = regexp(p_mission_parameter_string,'minutes in profile ','end'); + mission_parameters.CYCLE_CHANGE=str2num(p_mission_parameter_string(ix:ix+3)); + catch + mission_parameters.CYCLE_CHANGE=NaN; + end + clear ix + + try + ix = regexp(p_mission_parameter_string,'Startup delay: ','end'); + mission_parameters.STARTUP_DELAY=(p_mission_parameter_string(ix:ix+3)); + catch + mission_parameters.STARTUP_DELAY='NaN'; + end + clear ix + + try + ix = regexp(p_mission_parameter_string,'Startprofiles: ','end'); + mission_parameters.STARTPROFILES=str2num(p_mission_parameter_string(ix:ix+3)); + catch + mission_parameters.STARTPROFILES=NaN; + end + clear ix + + try + ix = regexp(p_mission_parameter_string,'Profile pressure: ','end'); + mission_parameters.PROFILE_PRESSURE=str2num(p_mission_parameter_string(ix:ix+5)); + catch + mission_parameters.PROFILE_PRESSURE=NaN; + end + clear ix + + try + ix = regexp(p_mission_parameter_string,'Maximum pressure: ','end'); + mission_parameters.MAX_PRESSURE=str2num(p_mission_parameter_string(ix:ix+5)); + catch + mission_parameters.MAX_PRESSURE=NaN; + end + clear ix + + try + ix = regexp(p_mission_parameter_string,'PistonOut @ alert = ','end'); + mission_parameters.PISTON_OUT_ALERT=str2num(p_mission_parameter_string(ix:ix+4)); + catch + mission_parameters.PISTON_OUT_ALERT=NaN; + end + clear ix + + try + ix = regexp(p_mission_parameter_string,'Last min/max piston counts: ','end'); + mission_parameters.LAST_PISTON_MIN_MAX=(p_mission_parameter_string(ix:ix+9)); + catch + mission_parameters.LAST_PISTON_MIN_MAX='NaN'; + end + clear ix + + try + ix = regexp(p_mission_parameter_string,'RAFOS Start time ','end'); + mission_parameters.RAFOS_STARTTIME=(p_mission_parameter_string(ix:ix+6)); + catch + mission_parameters.RAFOS_STARTTIME='NaN'; + end + clear ix + + try + ix = regexp(p_mission_parameter_string,'Duration ','end'); + mission_parameters.RAFOS_DURATION=str2num(p_mission_parameter_string(ix:ix+4)); + catch + mission_parameters.RAFOS_DURATION=NaN; + end + clear ix + + try + ix = regexp(p_mission_parameter_string,'temperature = ','end'); + mission_parameters.ICE_TEMPERATURE=str2num(p_mission_parameter_string(ix:ix+5)); + catch + mission_parameters.ICE_TEMPERATURE=NaN; + end + clear ix + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+float/save_startup_message.m b/lib/vendor/nemo2profilelib/+nemo/+float/save_startup_message.m new file mode 100644 index 0000000..e6cdac4 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+float/save_startup_message.m @@ -0,0 +1,107 @@ +function [ boolean ] = save_startup_message( p_startup_messages, p_filepath, p_overwrite ) +%SAVE_STARTUP_MESSAGES Summary of this function goes here +% Detailed explanation goes here + +%% Initialize variables +boolean = false; + +%% Parameter check +parameter_error = false; + +if (~iscell(p_startup_messages) || ~ischar(p_filepath)) + parameter_error = true; +end + +if (~exist('p_overwrite', 'var') || isempty(p_overwrite)) + p_overwrite = 1; % default setting is to overwrite existing profile files +end + +if (parameter_error) % display error message + disp([ mfilename ': Wrong parameter! Please check your input parameters carefully!']); + return; +else % do some cleanup + clear parameter_error; +end + +%% File existence checks + +if ~exist(p_filepath, 'dir') + mkdir(p_filepath); +end + +%% Configuration parameters +double_format = '%.15g'; +help_tabs = ['\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t']; +desired_tab_count = 12; + + +%% Write message to file + +%for outer = 1:length(p_startup_messages) % required if there are multiple messages +current_message = p_startup_messages{end}; +filename = [ ... + p_filepath ... + sprintf('%04d', current_message.float_serial_number) ... + '.startup_message' ... + ]; + +if exist(filename, 'file') == 2 && ~p_overwrite + % file will not be overwritten + boolean = true; + return +end + +of_id = fopen(filename, 'w'); % open file for writing +fprintf(of_id, '[STARTUP_MESSAGE]\n'); +write_struct(current_message); +fclose(of_id); + +boolean = true; +%end + + function [ tabs ] = get_tabs_to_insert(p_member_name) + % this function computes the required tabs to insert, making the + % textfile easier to read. + tablength = 4; + tab_count = desired_tab_count - floor((length(p_member_name)+1)/tablength); + tabs = help_tabs(1:(tab_count*2)); + end + + function write_struct(p_input_struct) + % get all fieldnames and write them to the file + member_names = fieldnames(p_input_struct); + member_count = length(member_names); + for o_member = 1:member_count % run through all members + % calculate how many tabs have to be inserted to have a + % good table layout + tabstring = get_tabs_to_insert(member_names{o_member}); + fprintf(of_id, ['-' member_names{o_member} tabstring]); + current_member = p_input_struct.(member_names{o_member}); + current_member_class = class(current_member); + switch current_member_class + case {'double', 'logical'} + write_double(current_member); + case 'char' + fprintf(of_id, [current_member '\n']); + otherwise + disp(['Unknown class in file: ' class(current_member)]); + end + + end + clear o_member + end + + function write_double(p_input_double) + + for o_double = 1:size(p_input_double, 1) + if length(p_input_double(o_double, :)) > 1 + fprintf(of_id, sprintf([double_format '\t'], p_input_double(o_double, 1:end-1))); + end + fprintf(of_id, [double_format '\n'], p_input_double(o_double, end)); + end + clear o_double + + end + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+float/sort_by_profile_number.m b/lib/vendor/nemo2profilelib/+nemo/+float/sort_by_profile_number.m new file mode 100644 index 0000000..d9ece54 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+float/sort_by_profile_number.m @@ -0,0 +1,34 @@ +function [ sorted_float ] = sort_by_profile_number( p_float ) +%SORT_BY_PROFILE_NUMBER Gets the input float and sorts its profile by their numbers +% +% Parameters: +% p_float: Float that will be sorted. +% +% Returns: +% sorted_float: Sorted float by profile number. + +% get all profile numbers +profile_numbers = nemo.float.collect(p_float, 'PROFILE_TECHNICAL_DATA', 'xmit_profile_number'); + +% sort them and get the indices +[~, indices] = sort(profile_numbers); + +% create helping variable to check if the float is already sorted +sorted = [1:length(profile_numbers)]'; +if indices == sorted + % indeed it is, so save work and just return the input one :) + sorted_float = p_float; + return +end + +% sorting required +sorted_float = {}; + +% indices have already been taken so just run through them +for i = 1:length(p_float) + sorted_float{i} = p_float{indices(i)}; +end + + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+float/sort_profile_data.m b/lib/vendor/nemo2profilelib/+nemo/+float/sort_profile_data.m new file mode 100644 index 0000000..4747285 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+float/sort_profile_data.m @@ -0,0 +1,26 @@ +function [ sorted_float ] = sort_profile_data( p_float ) +%SORT_PROFILE_DATA Sorts the profile data of the given float ascending by pressure. +% Sorts every CTD profile ascending by pressure of the given float. +% +% Parameters: +% p_float: The float containing the CTD data to be sorted. +% +% Returns: +% sorted_float: Copy of the float with sorted CTD data. + + +%% Initialize return variable +sorted_float = false; + +%% Parameter check +if (~iscell(p_float)) + disp([mfilename ': Unknown input type! Please check your input parameter!']); + return +end + +%% Sort profile data by time +sorted_float = cellfun(@nemo.profile.sort_profile_data, p_float, 'UniformOutput', false); + + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+ftp/upload.m b/lib/vendor/nemo2profilelib/+nemo/+ftp/upload.m new file mode 100644 index 0000000..0dea90a --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+ftp/upload.m @@ -0,0 +1,137 @@ +function [ is_successful ] = upload( p_config, p_nemo_data ) +%ftp_server_upload Uploads profiles to the ftp.awi.de server as user argo. +% If p_nemo_data is given, only the given profiles will be uploaded. +% Otherwise all profiles on the harddisk will be uploaded to the server +% as defined in p_config. +% Some default names are being ignored. +% Existing files on the server will be overwritten. Files +% existing ONLY on the server will not be touched. +% +% Parameters: +% p_config: The nemo configuration. +% p_nemo_data: Optional, the nemo data that will be uploaded to +% the server. + + +%% Initialize return variables +is_successful = false; + +%% Check input parameter +if (~isstruct(p_config) || ~isfield(p_config, 'FTP')) % display a warning + disp([ mfilename ': Please provide valid nemo configuration!']); + return; +end + +if (exist('p_files_to_upload', 'var') && ~iscell(p_nemo_data)) + disp([ mfilename ': Please provide a cell of strings in p_files_to_upload!']); + return; +end + +%% Set up server connection + +try % connecting to ftp server + ftp_server = ftp( ... + p_config.FTP.server, ... + p_config.FTP.username, ... + p_config.FTP.password ... + ); +catch error + disp([mfilename sprintf(': Failed to connect to server!\n') error]); + return; +end + +% change to correct folder if necessary +if (~strcmp(cd(ftp_server), p_config.FTP.remote_folder)) + cd(ftp_server, p_config.FTP.remote_folder); +end + +%% Start upload process + +if exist('p_nemo_data', 'var') % upload files that have been given + for i = 1:length(p_nemo_data) + for k = 1:length(p_nemo_data{i}) + profile_filename = [ ... + sprintf('%04d', p_nemo_data{i}{k}.FLOAT_IDENTIFICATION.float_serial_number) ... + '_' ... + sprintf('%04d', p_nemo_data{i}{k}.PROFILE_TECHNICAL_DATA.xmit_profile_number) ... + '.profile' + ]; + mkdir( ... + ftp_server, ... + [ ... + p_config.FTP.remote_folder ... + p_config.FTP.folder_separator ... + sprintf('%04d', p_nemo_data{i}{k}.FLOAT_IDENTIFICATION.float_serial_number) ... + ] ... + ); + cd( ... + ftp_server, ... + [ ... + p_config.FTP.remote_folder ... + p_config.FTP.folder_separator ... + sprintf('%04d', p_nemo_data{i}{k}.FLOAT_IDENTIFICATION.float_serial_number) ... + ] ... + ); + mput(ftp_server, ... + [ ... + p_config.OUTPUT_FILES.output_path ... + sprintf('%04d', p_nemo_data{i}{k}.FLOAT_IDENTIFICATION.float_serial_number) ... + p_config.GENERAL.folder_separator ... + profile_filename ... + ] ... + ); + end + end + +else + + directory_content = dir(p_config.OUTPUT_FILES.output_path); % should contain all float directories + + % run through every content of the folder + for current = 1:size(directory_content, 1) + if (directory_content(current).isdir) + % create directory + mkdir(ftp_server, ... + [ ... + p_config.FTP.remote_folder ... + p_config.FTP.folder_separator ... + directory_content(current).name ... + ] ... + ); + % switch to it on the server side + cd(ftp_server, [p_config.FTP.remote_folder p_config.FTP.folder_separator directory_content(current).name]); + % upload all .profile files + current_profiles = dir([p_config.OUTPUT_FILES.output_path directory_content(current).name p_config.GENERAL.folder_separator '*.profile']); + for i = 1:size(current_profiles, 1) + if (strcmp(current_profiles(i).name, 'Startup_Test_Message.profile') ... + || strcmp(current_profiles(i).name, 'MTM_Report_Message.profile') ... + || strcmp(current_profiles(i).name, 'nemo_0NaN_NaN.profile')) + continue % skip some default names + end + % upload the file + mput(ftp_server, [p_config.OUTPUT_FILES.output_path ... + directory_content(current).name p_config.GENERAL.folder_separator current_profiles(i).name]); + end + end + end + +end + + + + +%% Close server connection + +try % closing the connection + close(ftp_server); +catch error + disp([mfilename sprintf(': Closing connection to server failed:\n') getReport(error)]); +end + +is_successful = true; + +return + + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+load/additional_content.m b/lib/vendor/nemo2profilelib/+nemo/+load/additional_content.m new file mode 100644 index 0000000..e6e1cd8 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+load/additional_content.m @@ -0,0 +1,29 @@ +function [ additional_content ] = additional_content( p_config ) +%LOAD_ADDITIONAL_CONTENT Loads content that is defined in [ADDITIONAL_CONTENT] section of the global configuration file. + + +%% Initialize return variable +additional_content = {}; + +%% Load all additional content + +ice_cover_location = p_config.ADDITIONAL_CONTENT.antarctic_sea_ice_coverage_file; +grid_location = p_config.ADDITIONAL_CONTENT.antarctic_grid_file; + +additional_content.antarctic_latitudes = ... + hdfread(grid_location, '/Latitudes', 'Index', {[1 1],[1 1],[1328 1264]}); +additional_content.antarctic_longitudes = ... + hdfread(grid_location, '/Longitudes', 'Index', {[1 1],[1 1],[1328 1264]}); +additional_content.antarctic_ice_concentration = ... + hdfread(ice_cover_location, '/ASI Ice Concentration', 'Index', {[1 1],[1 1],[1328 1264]}); +% get etopo +[additional_content.etopo_z, additional_content.etopo_refvec] = ... + etopo(p_config.ADDITIONAL_CONTENT.etopo_file, 1, ... + p_config.PLOT_OVERVIEW_SOUTH.latitude_limit, ... + p_config.PLOT_OVERVIEW_SOUTH.longitude_limit); +% load moorings +additional_content.moorings = ... + nemo.load.moorings(p_config.ADDITIONAL_CONTENT.moorings_file); + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+load/config.m b/lib/vendor/nemo2profilelib/+nemo/+load/config.m new file mode 100644 index 0000000..2a27547 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+load/config.m @@ -0,0 +1,110 @@ +function [ config ] = config( p_filename, p_force_load ) +%LOAD_CONFIG Loads the given config file. +% +% Parameters: +% p_filename: String, absolute or relative filepath to the target file. +% It is recommended to give the absolute filepath. +% +% Returns: Struct, containing the sections and member of the +% configuration file. +% + +%% Initialize input parameter +config = false; + +if ~exist('p_force_load', 'var') + p_force_load = false; +end + +%% Parameter check +parameter_error = false; + +if (~ischar(p_filename)) + warning([mfilename ': Please check input parameter. p_filename is not a string!']); + parameter_error = true; +elseif ~p_force_load + % get filename details + [pathstr, name, ext] = fileparts(p_filename); + if (~strcmp(ext, '.conf')) + warning([mfilename ': Please check input parameter. Wrong file extension! Should be .conf!']); + parameter_error = true; + end +end + +if parameter_error + return; % return if parameter check has not been passed successfully +else + clear parameter_error; +end + +%% Check if file exists +if exist(p_filename, 'file') ~= 2 + return; +end + +%% Load configuration into variable + +% initialize all required variables +config = {}; +current_section = ''; +comment_char = '%'; % character to indicate comments +section_char = '['; + +% open file +file_id = fopen(p_filename); +fseek(file_id, 0, 'bof'); + + +% read file line by line +current_line = fgetl(file_id); +while (ischar(current_line)) + if length(current_line) == 0 || strcmp(current_line(1), comment_char) + current_line = fgetl(file_id); + continue + end + + switch current_line(1) + case section_char % this indicates a new section + current_line = strtrim(current_line); + current_section = current_line(2:end-1); + read_section + end + %current_line = fgetl(file_id); +end + +fclose(file_id); + + function read_section + % read complete section + current_line = fgetl(file_id); + while ischar(current_line) && ~isempty(current_line) ... + && ~strcmp(current_line(1), section_char) + if strcmp(current_line(1), comment_char) % it is a comment line + current_line = fgetl(file_id); + continue + end + + % if it contains a comment char anywhere, just take the string + % before it + found_comment = strfind(current_line, comment_char); + if ~isempty(found_comment) + current_line = current_line(1:found_comment(1)-1); + end + + % get variable name and value + [token, remain] = strtok(current_line); + % try to convert remain to a number + remain_as_num = str2num(strtrim(remain)); + if (~isempty(remain_as_num)) + config.(current_section).(token(2:end)) = remain_as_num; + else + config.(current_section).(token(2:end)) = strtrim(remain); + end + + current_line = fgetl(file_id); + end + end + + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+load/data.m b/lib/vendor/nemo2profilelib/+nemo/+load/data.m new file mode 100644 index 0000000..033be95 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+load/data.m @@ -0,0 +1,87 @@ +function [ nemo_data ] = data(p_path) +%LOAD_DATA Loads nemo data from the file structure into memory. +% +% Parameters: +% p_path: Absolute or relative filepath to the file structure +% containing the nemo data. +% +% Returns: Struct, containing loaded nemo data from the text files. + +%% Initialize input parameter +nemo_data = false; + +%% Parameter check +parameter_error = false; + +if (~ischar(p_path)) + warning([mfilename ': Please check input parameter. p_path is not a string!']); + parameter_error = true; +end + +if parameter_error + return; % return if parameter check has not been passed successfully +else + clear parameter_error; +end + +if (~exist('p_folder_separator', 'var')) + p_folder_separator = '\'; +end + +% check if last character is a \ +if (~strcmp(p_path(end), p_folder_separator)) + p_path = [p_path p_folder_separator]; +end + +%% Read profiles + +nemo_folder_names = dir(p_path); % get all elements in given directory +folders_to_exclude = {'.', '..'}; % these folders will be excluded from the list +nemo_folder_names = exclude_files(nemo_folder_names); +nemo_folder_names = exclude_elements(nemo_folder_names, folders_to_exclude, 'dir'); + +%% Loop through every float + +files_to_exclude = { ... + 'Startup_Test_Message.profile', ... + 'MTM_Report_Message.profile', ... + 'nemo_0NaN_NaN.profile' ... + }; + +if (isempty(nemo_folder_names)) + nemo_data = nemo.load.single_float(p_path, p_folder_separator, files_to_exclude); + return; +end + +nemo_data = {}; % initialize return variable as struct +nemo_data_count = 0; + +for o_float_index = 1:length(nemo_folder_names) % every float directory + % getting the absolute path to the current directory + current_dir = [p_path nemo_folder_names(o_float_index).name]; + % get all files in it + current_files = dir(fullfile([current_dir p_folder_separator], '*.profile')); + current_files = exclude_elements(current_files, files_to_exclude, 'dir'); + + profile_count = length(current_files); + + if (profile_count > 0) + current_profiles = {}; + + for o_profile_index = 1:profile_count + % read current profile + profile = nemo.profile.load(fullfile([current_dir p_folder_separator], current_files(o_profile_index).name)); + current_profiles{o_profile_index} = profile; + clear profile; + end + + nemo_data_count = nemo_data_count + 1; + nemo_data{nemo_data_count} = current_profiles; + clear current_profiles; + end + +end + +return + +end diff --git a/lib/vendor/nemo2profilelib/+nemo/+load/float_metadata.m b/lib/vendor/nemo2profilelib/+nemo/+load/float_metadata.m new file mode 100644 index 0000000..2e6f6bc --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+load/float_metadata.m @@ -0,0 +1,34 @@ +function [ float_metadata ] = float_metadata( p_filename ) +%FLOAT_METADATA Loads the given metadata file. +% If p_filename is a directory, all files with the .xml extension are +% being selected and loaded. +% +% Parameters: +% p_filename: The file or folder to be loaded. +% +% Returns: +% float_metadata: The loaded metadata files in a cell. + +%% Initialize return value +float_metadata = false; + +%% Parameter is already being checked in nemo.load.xml_file +float_metadata = nemo.load.xml_file(p_filename); + +switch (class(float_metadata)) + case 'struct' + float_metadata.MISSION_PARAMETERS = ... + nemo.float.process_mission_parameter(float_metadata.MISSION_PARAMETER_STRING); + case 'cell' + for i = 1:length(float_metadata) + float_metadata{i}.MISSION_PARAMETERS = ... + nemo.float.process_mission_parameter(float_metadata{i}.MISSION_PARAMETER_STRING); + end + otherwise + warning([mfilename ': Something went wrong while loading the xml file(s)!']); + return; +end + + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+load/float_transmission_info.m b/lib/vendor/nemo2profilelib/+nemo/+load/float_transmission_info.m new file mode 100644 index 0000000..e03e46b --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+load/float_transmission_info.m @@ -0,0 +1,48 @@ +function [ processed_inline ] = float_transmission_info( p_config, p_imei ) +%FLOAT_TRANSMITTION_INFO Reads the transmittion information stored in the mail body. +% Loads all mail bodies of the given IMEI and stores the processed text +% in a cell. +% +% Parameters: +% p_config: The nemo configuration. +% p_imei: The IMEI to load the information. +% +% Returns: +% processed_inline: A cell containing the processed information. + +%% Initialize return variables +processed_inline = false; + +%% TODO: Parameter check + + +%% Load inline file(s) +txt = ['txt' p_config.GENERAL.folder_separator]; +if (~ischar(p_imei)) + imei = int2str(p_imei); +else + imei = p_imei; +end + +% create full filepath +filepath = p_config.MAIL.storage_directory; + +if (p_config.MAIL.sort_by_imei) + filepath = [filepath imei p_config.GENERAL.folder_separator]; +end +if (p_config.MAIL.separate_sbd_and_txt) + filepath = [filepath txt]; +end +filenames = struct2table(dir([filepath '*.txt'])); +if (isempty(filenames.name)) + return; +end +absolute_filenames = cellfun(@(name) [filepath p_config.GENERAL.folder_separator name], filenames.name, 'UniformOutput', false); +processed_inline = cellfun(@read_file, absolute_filenames); + + function inline = read_file(filename) + inline = nemo.mail.process_inline(fileread(filename)); + end + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+load/floats2nemostruct.m b/lib/vendor/nemo2profilelib/+nemo/+load/floats2nemostruct.m new file mode 100644 index 0000000..35142a5 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+load/floats2nemostruct.m @@ -0,0 +1,56 @@ +function [ nemo_struct, startup_messages ] = floats2nemostruct( p_config, p_metadata ) +%SBD2OPTIMARE Converts and combines the float .sbd data into the nemo structure. +% Uses nemo.sbd.decode_float function to decode the profiles and +% nemo.convert.combined_float2nemostruct to convert the combined profiles +% to the nemo structure. +% +% Parameters: +% p_config: The nemo configuration. +% p_metadata: The metadata information about the floats to process. +% +% Returns: +% nemo_struct: The resulting nemo struct. + +%% Run through metadata and process every float +nemo_struct = cell(length(p_metadata), 1); +startup_messages = cell(length(p_metadata), 1); + +if (isa(p_metadata, 'struct')) + tmp = p_metadata; + p_metadata = {}; + p_metadata{1} = tmp; + clear tmp; +end + +for i = 1:length(p_metadata) + % copy all metadata to new struct + current_float = p_metadata{i}; + imei_int = p_metadata{i}.GENERAL_INFO.IMEI; + %serial_str = sprintf('%04d', p_metadata{i}.GENERAL_INFO.SERIAL); + + % collect all information from harddisk + [decoded_sbd, sbd_messages_info] = nemo.sbd.decode_float(p_config, imei_int); + current_float.DECODED_SBD = decoded_sbd; + current_float.SBD_MESSAGES_INFO = sbd_messages_info; + transmission_info = nemo.load.float_transmission_info(p_config, imei_int); + current_float.TRANSMISSION_INFO = transmission_info; + + % convert startup messages + startup_messages{i} = nemo.float.extract_startup_message(current_float); + % convert this information to the nemo structure + nemo_struct{i} = nemo.convert.combined_float2nemostruct(current_float); + % add startup message + if ~isempty(startup_messages{i}) ... + && ~isempty(startup_messages{i}{end}) ... + && ~isa(nemo_struct{i}, 'logical') + nemo_struct{i} = cellfun(@add_startup_message, nemo_struct{i}, 'UniformOutput', false); + end + +end + + function [ p_float ] = add_startup_message(p_float) + p_float.STARTUP_MESSAGE = startup_messages{i}{end}; + end + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+load/latest_profile.m b/lib/vendor/nemo2profilelib/+nemo/+load/latest_profile.m new file mode 100644 index 0000000..1aaba6c --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+load/latest_profile.m @@ -0,0 +1,46 @@ +function [ nemo_profile, profile_count ] = latest_profile( p_config, p_float_number ) +%LATEST_PROFILE Loads the latest profile of the given float. +% + +%% Initialize return variable +nemo_profile = {}; + +%% Parameter check +if ~isstruct(p_config) || ~isa(p_float_number, 'double') + disp([mfilename ': Wrong parameters, please check your input!']); + return; +end + +%% Determine the latest profile file +profile_files = dir( ... + [ ... + p_config.OUTPUT_FILES.output_path ... + sprintf('%04d', p_float_number) ... + p_config.GENERAL.folder_separator ... + '*.profile' ... + ] ... +); + +profile_files = {profile_files.name}; +profile_files = sort(profile_files); + +if isempty(profile_files) + profile_count = 0; + return +end +filename = profile_files{end}; +profile_count = length(profile_files); + +%% Now load the profile if available +nemo_profile = nemo.profile.load( ... + [ ... + p_config.OUTPUT_FILES.output_path ... + sprintf('%04d', p_float_number) ... + p_config.GENERAL.folder_separator ... + filename ... + ] ... +); + + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+load/moorings.m b/lib/vendor/nemo2profilelib/+nemo/+load/moorings.m new file mode 100644 index 0000000..537232c --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+load/moorings.m @@ -0,0 +1,103 @@ +function [ moorings ] = moorings( p_filename ) +%LOAD_MOORINGS Loads all moorings from a file into memory +% Almost the same procedure as nemo.load.config(). It has some changes +% to make it useful for loading moorings configuration file. + +%% Initialize input parameter +moorings = false; + +%% Parameter check +parameter_error = false; + +if (~ischar(p_filename)) + warning([mfilename ': Please check input parameter. p_filename is not a string!']); + parameter_error = true; +else + % get filename details + if ~exist(p_filename, 'file') + warning([mfilename ': Please check input parameter, given file does not exist!']); + parameter_error = true; + end +end + +if parameter_error + return; % return if parameter check has not been passed successfully +else + clear parameter_error; +end + +%% Load profile into variable + +% initialize all required variables +moorings = {}; +current_section = ''; +comment_char = '%'; % character to indicate comments +section_char = '['; % character indicating new section +mooring_count = 1; + +% open file +file_id = fopen(p_filename); +fseek(file_id, 0, 'bof'); + + +% read file line by line +current_line = fgetl(file_id); +while (ischar(current_line)) + if length(current_line) == 0 || strcmp(current_line(1), comment_char) + current_line = fgetl(file_id); + continue + end + + switch current_line(1) + case section_char % this indicates a new section + current_line = strtrim(current_line); + current_section = current_line(2:end-1); + moorings{mooring_count}.name = current_section; + read_section + mooring_count = mooring_count + 1; + end + %current_line = fgetl(file_id); +end + +fclose(file_id); + + function read_section + % read complete section + current_line = fgetl(file_id); + while ischar(current_line) && ~isempty(current_line) ... + && ~strcmp(current_line(1), section_char) + + % ignore comment char + if strcmp(current_line(1), comment_char) + current_line = fgetl(file_id); + continue + end + + % if it contains a comment char anywhere, just take the string + % before it + found_comment = strfind(current_line, comment_char); + if ~isempty(found_comment) + current_line = current_line(1:found_comment(1)-1); + end + + % get variable name and value + [token, remain] = strtok(current_line); + if (strcmp(token(2:end), 'pong_time')) + moorings{mooring_count}.(token(2:end)) = strtrim(remain); + else + % try to convert remain to a number + remain_as_num = str2num(strtrim(remain)); + if (~isempty(remain_as_num)) + moorings{mooring_count}.(token(2:end)) = remain_as_num; + else + moorings{mooring_count}.(token(2:end)) = strtrim(remain); + end + end + + current_line = fgetl(file_id); + end + end + + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+load/rfb_config.m b/lib/vendor/nemo2profilelib/+nemo/+load/rfb_config.m new file mode 100644 index 0000000..2baafb2 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+load/rfb_config.m @@ -0,0 +1,101 @@ +function [ config ] = rfb_config( p_filename ) +%RFB_CONFIG Loads the given config file. +% +% Parameters: +% p_filename: String, absolute or relative filepath to the target file. +% It is recommended to give the absolute filepath. +% +% Returns: Struct, containing the sections and member of the +% configuration file. +% + +%% Initialize input parameter +config = false; + +%% Parameter check +parameter_error = false; + +if (~ischar(p_filename)) + warning([mfilename ': Please check input parameter. p_filename is not a string!']); + parameter_error = true; +else + % get filename details + [pathstr, name, ext] = fileparts(p_filename); + if (~strcmp(ext, '.conf')) + warning([mfilename ': Please check input parameter. Wrong file extension! Should be .conf!']); + parameter_error = true; + end +end + +if parameter_error + return; % return if parameter check has not been passed successfully +else + clear parameter_error; +end + +%% Load profile into variable + +% initialize all required variables +config = {}; +current_section = ''; +comment_char = '%'; % character to indicate comments +section_char = '['; + +% open file +file_id = fopen(p_filename); +fseek(file_id, 0, 'bof'); + + +% read file line by line +current_line = fgetl(file_id); +while (ischar(current_line)) + if length(current_line) == 0 || strcmp(current_line(1), comment_char) + current_line = fgetl(file_id); + continue + end + + switch current_line(1) + case section_char % this indicates a new section + current_line = strtrim(current_line); + current_section = current_line(2:end-1); + read_section + end + %current_line = fgetl(file_id); +end + +fclose(file_id); + + function read_section + % read complete section + current_line = fgetl(file_id); + while ischar(current_line) && ~isempty(current_line) ... + && ~strcmp(current_line(1), section_char) + if strcmp(current_line(1), comment_char) % it is a comment line + current_line = fgetl(file_id); + continue + end + + % if it contains a comment char anywhere, just take the string + % before it + found_comment = strfind(current_line, comment_char); + if ~isempty(found_comment) + current_line = current_line(1:found_comment(1)-1); + end + + % get variable name and value + [token, remain] = strtok(current_line); + % try to convert remain to a number + remain_as_num = str2num(strtrim(remain)); + if (~isempty(remain_as_num)) + config.(current_section).(token(2:end)) = remain_as_num; + else + config.(current_section).(token(2:end)) = strtrim(remain); + end + + current_line = fgetl(file_id); + end + end + + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+load/single_float.m b/lib/vendor/nemo2profilelib/+nemo/+load/single_float.m new file mode 100644 index 0000000..319ed14 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+load/single_float.m @@ -0,0 +1,40 @@ +function [ float_data ] = single_float( p_path, p_folder_separator, p_files_to_exclude ) +%SINGLE_FLOAT Loads a float from the harddisk. +% Loads all profiles of a float in the given path. +% +% Parameters: +% p_path: The filepath where the .profile files are stored. +% p_folder_separator: The folder separator character. +% p_files_to_exclude: List of files that will be ignored. +% +% Returns: +% float_data: The loaded profiles. + +%% Initialize return variable +float_data = {}; + +%% Get all files in the folder and load profiles +if (~exist('p_folder_separator', 'var')) + p_folder_separator = '\'; +end +current_files = dir(fullfile([p_path p_folder_separator], '*.profile')); +if (exist('p_files_to_exclude', 'var')) + current_files = exclude_elements(current_files, p_files_to_exclude, 'dir'); +end + +profile_count = length(current_files); + +if (profile_count > 0) + float_data = {}; + + for o_profile_index = 1:profile_count + % read current profile + profile = nemo.profile.load(fullfile([p_path p_folder_separator], current_files(o_profile_index).name)); + float_data{o_profile_index} = profile; + clear profile; + end + +end + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+load/xml_file.m b/lib/vendor/nemo2profilelib/+nemo/+load/xml_file.m new file mode 100644 index 0000000..06e4062 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+load/xml_file.m @@ -0,0 +1,61 @@ +function [ xml_file ] = xml_file( p_filename, p_filemask ) +%FLOAT_METADATA Loads xml files into a matlab cell. +% Loads either one xml file or all xml files from the given directory. +% +% Parameter: +% p_filename: string, if empty the complete directory is being loaded. +% p_filemask: string, the filemask of the files to be selected, for +% example: *float_* +% +% Returns: +% xml_file: cell, containing all files that have been read. + +%% Initialize return variables +xml_file = false; + +%% Parameter check +parameter_error = false; +file_only = false; + +if (~ischar(p_filename) || (exist('p_filemask', 'var') && ~ischar(p_filemask))) + parameter_error = true; +end + +if (~isdir(p_filename)) + file_only = true; +end + +if (parameter_error) % display error message + disp([ mfilename ': Wrong parameter! Please check your input parameters carefully!']); + return; +else % do some cleanup + clear parameter_error; +end + +%% Check operating system +if (ismac || isunix) + folder_separator = '/'; +elseif (ispc) + folder_separator = '\'; +end + +%% Load metadata files +if (file_only) + if ~exist(p_filename, 'file') % warn user + warning([mfilename ': Float metadata file ' p_filename ' does not exist!']); + return + end + xml_file = utils.xml_read(p_filename); +else + if (exist('p_filemask', 'var')) + filenames = struct2table(dir([p_filename folder_separator p_filemask '.xml'])); + else + filenames = struct2table(dir([p_filename folder_separator '*.xml'])); + end + + absolute_filenames = cellfun(@(name) [p_filename folder_separator name], filenames.name, 'UniformOutput', false); + xml_file = cellfun(@(name) utils.xml_read(name), absolute_filenames, 'UniformOutput', false); +end + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+mail/connect.m b/lib/vendor/nemo2profilelib/+nemo/+mail/connect.m new file mode 100644 index 0000000..eeb352e --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+mail/connect.m @@ -0,0 +1,62 @@ +function [ imapstore ] = connect( p_config ) +%CONNECT Connects with the specified server details where nemo emails are stored. +% This functions connects to the mail server given in the config +% parameter. Therefore the java library javax.mail.jar (javaMail) and +% activation.jar (JavaBeans Activation Framework) is required. There is no +% disconnect function, use 'clear imapstore' instead. +% javaMail Documentation: https://javamail.java.net/nonav/docs/api/ +% +% Parameter: +% p_config: struct, containing the content of the nemo config file. +% +% Returns: +% imapstore: IMAPSSLSTORE/IMAPSTORE java object, false on fail. + +%% Initialize return variable +imapstore = false; + +%% Parameter check +parameter_error = false; + +if (~isstruct(p_config)) + parameter_error = true; +end + +if (parameter_error) % display error message + disp([ mfilename ': Wrong parameter! Please check your input parameters carefully!']); + return; +else % do some cleanup + clear parameter_error; +end + +if (~isfield(p_config, 'MAIL')) + disp('No MAIL section found in p_config.'); + return; +end + +%% Prepare connection +try + props = java.util.Properties; % default imap properties + props.put('mail.imap.port', p_config.MAIL.port); + props.put('mail.imap.timeout', p_config.MAIL.imap_timeout); + props.put('mail.imaps.connectiontimeout', p_config.MAIL.imaps_connection_timeout); + session = javax.mail.Session.getInstance(props); % create a session + if (p_config.MAIL.ssl) % get the ssl store object + imapstore = session.getStore('imaps'); + else % get the store object + imapstore = session.getStore('imap'); + end + imapstore.connect( ... + p_config.MAIL.server, ... + p_config.MAIL.port, ... + p_config.MAIL.username, ... + p_config.MAIL.password ... + ); +catch + warning('Something failed during the establishment of the imap connection!'); + imapstore = false; + return; +end + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+mail/export_attachment.m b/lib/vendor/nemo2profilelib/+nemo/+mail/export_attachment.m new file mode 100644 index 0000000..41f96bf --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+mail/export_attachment.m @@ -0,0 +1,50 @@ +function [ Boolean ] = export_attachment( Attachment, AbsoluteFilename ) +%EXPORT_ATTACHMENT Exports the given attachment to the given filename. +% Exports the attachment (type of com.sun.mail.util.BASE64DecoderStream) +% to the given filename. +% +% Parameters: +% Attachment: The attachment to be saved. +% AbsoluteFilename: The filename where the attachment is being +% exported to. +% +% Returns: +% Boolean: True if successful. + +%% Initialize return variable +Boolean = false; + +%% Check parameter + +if (~isa(Attachment, 'com.sun.mail.util.BASE64DecoderStream') ... + || ~ischar(AbsoluteFilename)) + warning([mfilename ': Input parameter check failed!']); + return +end + +%% Create file and export attachment to it + +[Path, ~, ~] = fileparts(AbsoluteFilename); + +if (~isdir(Path)) % create it + mkdir(Path); +end + +% save every byte in attachment to file +File = java.io.File(AbsoluteFilename); +OutputStream = java.io.FileOutputStream(File); + +Byte = Attachment.read(); +while (Byte ~= -1) + OutputStream.write(Byte); + Byte = Attachment.read(); +end + +OutputStream.flush(); % prevent loosing bytes in write process +OutputStream.close(); +Attachment.close(); + +Boolean = true; + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+mail/extract.m b/lib/vendor/nemo2profilelib/+nemo/+mail/extract.m new file mode 100644 index 0000000..0262492 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+mail/extract.m @@ -0,0 +1,63 @@ +function [ FromArray, Subject, Inline, AttachmentArray ] = extract( Message ) +%EXTRACT Extracts basic information from the given message. +% Extracts the message created by the javax.mail library. +% +% Parameters: +% Message: The message to be extracted from. +% +% Returns: +% FromArray: All senders addresses (usually just one) +% Subject: Subject of the message. +% Inline: The message body. +% AttachmentArray: All attachments of the message. + +%% Initialize return variables +FromArray = false; +Subject = false; +Inline = ''; +AttachmentArray = false; + +%% Get from address +FromArray = Message.getFrom(); +nFrom = FromArray.length; +% convert to matlab char +tmp = {}; +for i = 1:nFrom + tmp{i} = char(FromArray(i)); +end +FromArray = tmp; +clear tmp; + +%% Get subject +Subject = char(Message.getSubject()); + +%% Get inline text and attachments +MessageContent = Message.getContent(); +if (~isempty(strfind(Message.getContentType, 'text/plain'))) + Inline = MessageContent; + AttachmentArray = {}; + return +end +nBodies = MessageContent.getCount(); +if (nBodies > 0) + AttachmentArray = {}; +end + +for iBody = 0:nBodies - 1 + BodyPart = MessageContent.getBodyPart(iBody); + if (BodyPart.isMimeType('text/plain') ... + || BodyPart.isMimeType('text/html')) + Inline = char(BodyPart.getContent()); + elseif (BodyPart.isMimeType('attachment') ... + || BodyPart.isMimeType('application/x-zip-compressed')) + AttachmentArray{iBody, 1} = BodyPart.getDataHandler.getName(); + AttachmentArray{iBody, 2} = BodyPart.getDataHandler.getInputStream(); + else + disp([mfilename ': MimeType ' char(BodyPart.getContentType()) ' not supported!']); + end + +end + + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+mail/fetch_messages.m b/lib/vendor/nemo2profilelib/+nemo/+mail/fetch_messages.m new file mode 100644 index 0000000..f39ef0b --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+mail/fetch_messages.m @@ -0,0 +1,69 @@ +function [ messages, folder ] = fetch_messages( p_imapstore, p_foldername, p_searchterm, p_read_only ) +%FETCH_UNREAD Fetches all messages in the given folder. +% Fetches all messages that match the given searchterm from the given +% folder. +% +% Parameters: +% p_imapstore: The imapstore object of javax.mail library. +% p_foldername: The foldername where the messages will be +% fetched from. +% p_searchterm: The searchterm created with javax.mail library. +% p_read_only: If true, the folder will be opened as read +% only. +% +% Returns: +% messages: The messages that match the searchterm. +% folder: The folder object. + +%% Initialize return variables +messages = false; + +if (~exist('p_read_only', 'var')) + p_read_only = true; +end + +%% Parameter check +parameter_error = false; + +if (~ischar(p_foldername)) + parameter_error = true; +end + +if (parameter_error) % display error message + disp([ mfilename ': Wrong parameter! Please check your input parameters carefully!']); + return; +else % do some cleanup + clear parameter_error; +end + +%% Check connection + +if (~p_imapstore.isConnected()) + disp('IMAPStore not connected, trying to reconnect...'); + p_imapstore.connect(); + if (~p_imapstore.isConnected()) + warning('IMAPStore reconnect failed. Please check your configuration!'); + return; + else + disp('... success!'); + end +end + +%% Get and open folder +folder = p_imapstore.getFolder(p_foldername); +if (~p_read_only) + folder.open(folder.READ_WRITE); +else + folder.open(folder.READ_ONLY); +end + +%% Fetch messages +if (isa(p_searchterm, 'logical') || strcmp(p_searchterm, '')) + messages = folder.getMessages(); +else + messages = folder.search(p_searchterm); +end + + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+mail/fetch_unread.m b/lib/vendor/nemo2profilelib/+nemo/+mail/fetch_unread.m new file mode 100644 index 0000000..0e02e12 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+mail/fetch_unread.m @@ -0,0 +1,60 @@ +function [ unread_messages, folder ] = fetch_unread( p_imapstore, p_foldername ) +%FETCH_UNREAD Fetches all messages that are flagged as unread on the server. +% Fetches all messages that are flagged as unread in the given folder. +% +% Parameters: +% p_imapstore: The imapstore object. +% p_foldername: The folder name where the messages are stored. +% +% Returns: +% unread_messages: The messages that have been fetched. +% folder: The folder object. + +%% Initialize return variables +unread_messages = false; + +%% Parameter check +parameter_error = false; + +if (~ischar(p_foldername)) + parameter_error = true; +end + +if (parameter_error) % display error message + disp([ mfilename ': Wrong parameter! Please check your input parameters carefully!']); + return; +else % do some cleanup + clear parameter_error; +end + +%% Check connection + +if (~p_imapstore.isConnected()) + disp('IMAPStore not connected, trying to reconnect...'); + p_imapstore.connect(); + if (~p_imapstore.isConnected()) + warning('IMAPStore reconnect failed. Please check your configuration!'); + return; + end +end + +%% Get and open folder +folder = p_imapstore.getFolder(p_foldername); +folder.open(folder.READ_ONLY); + +%% Fetch all unread messages + +% there are difficulties accessing static class members of an inner java +% class. This solution has been found at: +% http://undocumentedmatlab.com/blog/accessing-internal-java-class-members +search_flag = javax.mail.Flags(); % create search flag +tmp = search_flag.getClass.getDeclaredClasses; +seen_flag = tmp(1).getField('SEEN').get(1); +clear tmp; +search_flag.add(seen_flag); +flagterm = javax.mail.search.FlagTerm(search_flag, false); +unread_messages = folder.search(flagterm); + + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+mail/process_inline.m b/lib/vendor/nemo2profilelib/+nemo/+mail/process_inline.m new file mode 100644 index 0000000..9c46a96 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+mail/process_inline.m @@ -0,0 +1,84 @@ +function [ processed_inline ] = process_inline( p_inline ) +%PROCESS_INLINE Searches the given inline text by the Iridium information and extracts them. +% Processes the given inline text and extracts the iridium information. +% +% Parameters: +% p_inline: The inline text to be processed. +% +% Returns: +% processed_inline: Struct, containing the found information. Contains +% false on fail. + +%% Initialize return variable +processed_inline = false; + +%% Parameter check +if (~ischar(p_inline)) + warning([mfilename ': Wrong parameter given. Please check them carefully!']); + return; +end + +%% Initialize values to extract +utc = 'Time of Session (UTC): '; +momsn = 'MOMSN: '; +mtmsn = 'MTMSN: '; +session_status = 'Session Status: '; +message_size_in_bytes = 'Message Size (bytes): '; +unit_location = 'Unit Location: '; +cep_radius = 'CEPradius = '; + +%% Split message by lines +lines = strsplit(p_inline, '\n'); + +% find values and store them + +location = process_if_available(unit_location); +if (~isa(location, 'logical')) + location = strsplit(strtrim(location)); + iridium_lat = str2double(location{3}); + iridium_lon = str2double(location{6}); +else + iridium_lat = false; + iridium_lon = false; +end + +processed_inline = struct( ... + 'TIMEOFSESSION', process_if_available(utc), ... + 'MOMSN', process_if_available(momsn), ... + 'MTMSN', process_if_available(mtmsn), ... + 'SESSIONSTATUS', process_if_available(session_status), ... + 'MESSAGESIZE', process_if_available(message_size_in_bytes), ... + 'IRIDIUM_LAT', iridium_lat, ... + 'IRIDIUM_LON', iridium_lon, ... + 'CEPRAD', process_if_available(cep_radius) ... +); + + +%% Convert to usable matlab values +if (~isa(processed_inline.TIMEOFSESSION, 'logical')) + processed_inline.TIMEOFSESSION = datevec(processed_inline.TIMEOFSESSION, 'ddd mmm dd HH:MM:SS yyyy'); +end +if (~isa(processed_inline.MOMSN, 'logical')) + processed_inline.MOMSN = str2double(processed_inline.MOMSN); +end +if (~isa(processed_inline.MTMSN, 'logical')) + processed_inline.MTMSN = str2double(processed_inline.MTMSN); +end +if (~isa(processed_inline.MESSAGESIZE, 'logical')) + processed_inline.MESSAGESIZE = str2double(processed_inline.MESSAGESIZE); +end +if (~isa(processed_inline.CEPRAD, 'logical')) + processed_inline.CEPRAD = str2double(processed_inline.CEPRAD); +end + + function value = process_if_available(p_variable) + value = false; + try + value = lines{find(~cellfun(@isempty, strfind(lines, p_variable)))}(length(p_variable):end); + catch ex + return + end + end + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/devicedata.m b/lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/devicedata.m new file mode 100644 index 0000000..ca0be17 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/devicedata.m @@ -0,0 +1,65 @@ +function [device,data,status]=devicedata(devicedata) + + % Decode types + dd = dec2bin(devicedata,8); + data = bin2dec(dd(5:8)); + device = bin2dec(dd(1:4)); + status = 1; + + % Set device and data type + switch (device) + case 1 + % NEMO float + device = 'NEMO Standard '; + switch (data) + case 0 + % Test/Startup data + data = 'Startup Test Message'; + case 1 + % Profile data + data = 'Profile Message'; + case 2 + % Report data + data = 'MTM Report Message'; + case 3 + % Recovery data + data = 'Recovery Message'; + case 4 + % File data + data = 'File transmission Message'; + case 5 + % Reserved profile data + data = 'Reserved profile Message'; + case 6 + % Filtered continous profile data + data = 'Filtered CTD data Message'; + + otherwise + % Unknown data + data = 'Unknown Message'; + %disp(['Unknown data type ('+num2str(dd)+')']); + status = 0; + end + + case 2 + % CTD Profiler + device = 'CTD Profiler/Logger'; + switch (data) + case 1 + % Profile data + data = 'Profile Message'; + + otherwise + % Unknown data + data = 'Unknown Message'; + %disp(['Unknown data type ('+num2str(dd)+')']); + status = 0; + end + + otherwise + % Unknown device type + device = 'Unknown'; + status = 0; + end + +end \ No newline at end of file diff --git a/lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/phext_t2.m b/lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/phext_t2.m new file mode 100644 index 0000000..91f67c3 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/phext_t2.m @@ -0,0 +1,285 @@ +function [S,offset] = phext_t2(S,offset,k) + +% Decode different NEMO profile header extensions type 2 (mission +% configuration) +% +% + +% Optimare Sensorsysteme, 24.05.2012 M.Busack + +switch (S.DS1_FORMAT(k)) + + case 1 + % Format 1 vom 8-Mar-2012 + S.SERIAL = S.uD(offset+1)*256 + S.uD(offset); + S.AIRPUMPTIME = S.uD(offset+3)*256 + S.uD(offset+2); + S.STARTUPTIME = (S.uD(offset+5)*256 + S.uD(offset+4))/60; + S.TIME_CYCLE = S.uD(offset+7)*256 + S.uD(offset+6); + S.PRES_PROFILE = NaN; %orginal double + S.PISTON_MIN = S.uD(offset+13)*256 + S.uD(offset+12); + S.PISTON_MAX = S.uD(offset+15)*256 + S.uD(offset+14); + S.STARTUPDELAY = S.uD(offset+17)*256 + S.uD(offset+16); + S.DESCENT_MODE = S.uD(offset+18); + % Byte 19 not used + S.SPEED_DESCENT = NaN; %orginal double + S.DESCENT_TIMEOUTFACTOR = NaN; %orginal double + S.DESCENT_PISTONINTERVAL_MIN = S.uD(offset+28); + S.DESCENT_DEPTH_CONTROL = S.uD(offset+29); + S.DESCENT_PISTONINTERVAL = S.uD(offset+31)*256 + S.uD(offset+30); + S.DESCENT_PISTON_ONTIME = S.uD(offset+32); + % Byte 33 not used + S.PRES_DESCENT_MARGIN = NaN; %orginal double + S.PRES_PARKING = NaN; %orginal double + S.PRES_PARKING_HYSTERESE = S.uD(offset+42); + % Byte 43 not used + S.ATTENUATION_FACTOR = NaN; %orginal double + S.TIME_PARKSAMPLE = (S.uD(offset+51)*16777216 + S.uD(offset+50)*65536 + S.uD(offset+49)*256 + S.uD(offset+48))/60; + S.PARKING_SAMPLES = S.uD(offset+52); + S.PARKING_DELAY = S.uD(offset+53); + S.PRES_PARKINGHIGH = S.uD(offset+55)*256 + S.uD(offset+54); + S.SAFETYZONE_MARGIN = S.uD(offset+57)*256 + S.uD(offset+56); + S.SAFETYZONE_COUNTCHANGE = S.uD(offset+59)*256 + S.uD(offset+58); + S.SAFETYZONE_INCREMENT = S.uD(offset+61)*256 + S.uD(offset+60); + S.INITIAL_PARKCOUNT = S.uD(offset+63)*256 + S.uD(offset+62); + S.SPEED_SAG = NaN; %orginal double + S.SAG_TIMEOUTFACTOR = NaN; %orginal double + S.ASCENT_MODE = S.uD(offset+72); + % Byte 73 not used + S.SPEED_ASCENT = NaN; %orginal double + S.ASCENT_TIMEOUTFACTOR = NaN; %orginal double + S.ASCENT_PISTONINTERVAL_MIN = S.uD(offset+82); + S.ASCENT_DEPTH_CONTROL = S.uD(offset+83); + S.ASCENT_PISTONINTERVAL = S.uD(offset+85)*256 + S.uD(offset+84); + S.ASCENT_PISTON_ONTIME = S.uD(offset+86); + % Byte 87 not used + S.TEMP_ICEDETECT = NaN; %orginal double + S.PRES_ICEUP = S.uD(offset+93)*256 + S.uD(offset+92); %until here verified with sbd 1 + S.PRES_ICEDOWN = S.uD(offset+95)*256 + S.uD(offset+94); + S.PRES_ICEINTERVAL = S.uD(offset+96); + S.SUCCESSFUL_ASCENTS = S.uD(offset+97); + S.POST_UPCAST_SAMPLES = S.uD(offset+98); + S.POST_UPCAST_DELAY = S.uD(offset+99); + S.TIME_SURFACE_ATTEMP = S.uD(offset+100); + % Byte 101 not used + S.TIME_TX = (S.uD(offset+105)*16777216 + S.uD(offset+104)*65536 + S.uD(offset+103)*256 + S.uD(offset+102))/60; + S.MULTIMSG = [S.uD(offset+106) S.uD(offset+107) S.uD(offset+108) S.uD(offset+109)]; + S.ARGOSID = S.uD(offset+110); + S.RAFOS_MODULEADR = S.uD(offset+111); + S.RECOVERY_START = S.uD(offset+113)*256 + S.uD(offset+112); + S.RECOVERY_TX = S.uD(offset+114); + % Byte 115 not used + S.RECOVERY_NOTX = S.uD(offset+117)*256 + S.uD(offset+116); + S.LOGMODE = S.uD(offset+118); + S.TESTMODE = S.uD(offset+119); + S.TIME_CO2WARMUP = (S.uD(offset+121)*256 + S.uD(offset+120))/60; + S.TIME_GPSFIX = (S.uD(offset+123)*256 + S.uD(offset+122))/60; + S.PRES_MAXALARM = S.uD(offset+125)*256 + S.uD(offset+124); + S.COUNT_MAXALARM = S.uD(offset+127)*256 + S.uD(offset+126); + S.CTDCP_FILTER = S.uD(offset+129)*256 + S.uD(offset+128); + S.PRES_CTDOFF_MAX = (S.sD(offset+131)*256 + S.uD(offset+130))/10; + S.PRES_CTDOFF_MIN = (S.sD(offset+133)*256 + S.uD(offset+132))/10; + S.PRES_CTDOFF_CHN = (S.uD(offset+135)*256 + S.uD(offset+134))/10; + S.PRES_PCUTOFF = (S.uD(offset+137)*256 + S.uD(offset+136))/10; + S.PRES_SAFETY = NaN; %orginal double + S.STARTPROFILE = S.uD(offset+143)*256 + S.uD(offset+142); + S.ICEGPS_TIMEOUT = S.uD(offset+144); + S.ICEIRDM_TIMEOUT = S.uD(offset+145); + S.CONFIGTX = S.uD(offset+146); + S.RAFOS_ACTIVE = [S.uD(offset+147) S.uD(offset+148) S.uD(offset+149) S.uD(offset+150) S.uD(offset+151)]; + S.RAFOS_STARTHH = [S.uD(offset+152) S.uD(offset+153) S.uD(offset+154) S.uD(offset+155) S.uD(offset+156)]; + S.RAFOS_STARTMM = [S.uD(offset+157) S.uD(offset+158) S.uD(offset+159) S.uD(offset+160) S.uD(offset+161)]; + S.RAFOS_ONMM = [S.uD(offset+163)*256+S.uD(offset+162) S.uD(offset+165)*256+S.uD(offset+164) ... + S.uD(offset+167)*256+S.uD(offset+166) S.uD(offset+169)*256+S.uD(offset+168) ... + S.uD(offset+171)*256+S.uD(offset+170)]; + offset = offset + 172; + + case 2 + % Format 2 vom 23-May-2012 + S.SERIAL = S.uD(offset+1)*256 + S.uD(offset); + S.AIRPUMPTIME = S.uD(offset+3)*256 + S.uD(offset+2); + S.STARTUPTIME = (S.uD(offset+5)*256 + S.uD(offset+4))/60; + S.TIME_CYCLE = S.uD(offset+7)*256 + S.uD(offset+6); + S.PRES_PROFILE = NaN; %orginal double + S.PISTON_MIN = S.uD(offset+13)*256 + S.uD(offset+12); + S.PISTON_MAX = S.uD(offset+15)*256 + S.uD(offset+14); + S.STARTUPDELAY = S.uD(offset+17)*256 + S.uD(offset+16); + S.DESCENT_MODE = S.uD(offset+18); + % Byte 19 not used + S.SPEED_DESCENT = NaN; %orginal double + S.DESCENT_TIMEOUTFACTOR = NaN; %orginal double + S.DESCENT_PISTONINTERVAL_MIN = S.uD(offset+28); + S.DESCENT_DEPTH_CONTROL = S.uD(offset+29); + S.DESCENT_PISTONINTERVAL = S.uD(offset+31)*256 + S.uD(offset+30); + S.DESCENT_PISTON_ONTIME = S.uD(offset+32); + % Byte 33 not used + S.PRES_DESCENT_MARGIN = NaN; %orginal double + S.PRES_PARKING = NaN; %orginal double + S.PRES_PARKING_HYSTERESE = S.uD(offset+42); + % Byte 43 not used + S.ATTENUATION_FACTOR = NaN; %orginal double + S.TIME_PARKSAMPLE = (S.uD(offset+51)*16777216 + S.uD(offset+50)*65536 + S.uD(offset+49)*256 + S.uD(offset+48))/60; + S.PARKING_SAMPLES = S.uD(offset+52); + S.PARKING_DELAY = S.uD(offset+53); + S.PRES_PARKINGHIGH = S.uD(offset+55)*256 + S.uD(offset+54); + S.SAFETYZONE_MARGIN = S.uD(offset+57)*256 + S.uD(offset+56); + S.SAFETYZONE_COUNTCHANGE = S.uD(offset+59)*256 + S.uD(offset+58); + S.SAFETYZONE_INCREMENT = S.uD(offset+61)*256 + S.uD(offset+60); + S.INITIAL_PARKCOUNT = S.uD(offset+63)*256 + S.uD(offset+62); + S.SPEED_SAG = NaN; %orginal double + S.SAG_TIMEOUTFACTOR = NaN; %orginal double + S.ASCENT_MODE = S.uD(offset+72); + % Byte 73 not used + S.SPEED_ASCENT = NaN; %orginal double + S.ASCENT_TIMEOUTFACTOR = NaN; %orginal double + S.ASCENT_PISTONINTERVAL_MIN = S.uD(offset+82); + S.ASCENT_DEPTH_CONTROL = S.uD(offset+83); + S.ASCENT_PISTONINTERVAL = S.uD(offset+85)*256 + S.uD(offset+84); + S.ASCENT_PISTON_ONTIME = S.uD(offset+86); + % Byte 87 not used + S.TEMP_ICEDETECT = NaN; %orginal double + S.PRES_ICEUP = S.uD(offset+93)*256 + S.uD(offset+92); %until here verified with sbd 1 + S.PRES_ICEDOWN = S.uD(offset+95)*256 + S.uD(offset+94); + S.PRES_ICEINTERVAL = S.uD(offset+96); + S.SUCCESSFUL_ASCENTS = S.uD(offset+97); + S.POST_UPCAST_SAMPLES = S.uD(offset+98); + S.POST_UPCAST_DELAY = S.uD(offset+99); + S.TIME_SURFACE_ATTEMP = S.uD(offset+100); + % Byte 101 not used + S.TIME_TX = (S.uD(offset+105)*16777216 + S.uD(offset+104)*65536 + S.uD(offset+103)*256 + S.uD(offset+102))/60; + S.MULTIMSG = [S.uD(offset+106) S.uD(offset+107) S.uD(offset+108) S.uD(offset+109)]; + S.ARGOSID = S.uD(offset+110); + S.RAFOS_MODULEADR = S.uD(offset+111); + S.RECOVERY_START = S.uD(offset+113)*256 + S.uD(offset+112); + S.RECOVERY_TX = S.uD(offset+114); + % Byte 115 not used + S.RECOVERY_NOTX = S.uD(offset+117)*256 + S.uD(offset+116); + S.LOGMODE = S.uD(offset+118); + S.TESTMODE = S.uD(offset+119); + S.TIME_CO2WARMUP = (S.uD(offset+121)*256 + S.uD(offset+120))/60; + S.TIME_GPSFIX = (S.uD(offset+123)*256 + S.uD(offset+122))/60; + S.PRES_MAXALARM = S.uD(offset+125)*256 + S.uD(offset+124); + S.COUNT_MAXALARM = S.uD(offset+127)*256 + S.uD(offset+126); + S.CTDCP_FILTER = S.uD(offset+129)*256 + S.uD(offset+128); + S.PRES_CTDOFF_MAX = (S.sD(offset+131)*256 + S.uD(offset+130))/10; + S.PRES_CTDOFF_MIN = (S.sD(offset+133)*256 + S.uD(offset+132))/10; + S.PRES_CTDOFF_CHN = (S.uD(offset+135)*256 + S.uD(offset+134))/10; + S.PRES_PCUTOFF = (S.uD(offset+137)*256 + S.uD(offset+136))/10; + S.PRES_SAFETY = NaN; %orginal double + S.STARTPROFILE = S.uD(offset+143)*256 + S.uD(offset+142); + S.ICEGPS_TIMEOUT = S.uD(offset+144); + S.ICEIRDM_TIMEOUT = S.uD(offset+145); + S.CONFIGTX = S.uD(offset+146); + % New + S.AIRPUMP_MODE = S.uD(offset+147); + S.AIRPUMP_PRESSDIFF = S.uD(offset+149)*256 + S.uD(offset+148); + S.AIRPUMP_TIMEOUT = S.uD(offset+151)*256 + S.uD(offset+150); + S.MISSION_MOD = S.uD(offset+153)*256 + S.uD(offset+152); + S.TIME_CYCLE2 = S.uD(offset+155)*256 + S.uD(offset+154); + % Changed + S.RAFOS_ACTIVE = [S.uD(offset+156) S.uD(offset+157) S.uD(offset+158) S.uD(offset+159) S.uD(offset+160)]; + S.RAFOS_STARTHH = [S.uD(offset+161) S.uD(offset+162) S.uD(offset+163) S.uD(offset+164) S.uD(offset+165)]; + S.RAFOS_STARTMM = [S.uD(offset+166) S.uD(offset+167) S.uD(offset+168) S.uD(offset+169) S.uD(offset+170)]; + % Byte 171 not used + S.RAFOS_ONMM = [S.uD(offset+173)*256+S.uD(offset+172) S.uD(offset+175)*256+S.uD(offset+174) ... + S.uD(offset+177)*256+S.uD(offset+176) S.uD(offset+179)*256+S.uD(offset+178) ... + S.uD(offset+181)*256+S.uD(offset+180)]; + offset = offset + 182; + + case 3 + % Format 3 vom 12-June-2012 (Ice detection temperature A01 change) + S.SERIAL = S.uD(offset+1)*256 + S.uD(offset); + S.AIRPUMPTIME = S.uD(offset+3)*256 + S.uD(offset+2); + S.STARTUPTIME = (S.uD(offset+5)*256 + S.uD(offset+4))/60; + S.TIME_CYCLE = S.uD(offset+7)*256 + S.uD(offset+6); + S.PRES_PROFILE = NaN; %orginal double + S.PISTON_MIN = S.uD(offset+13)*256 + S.uD(offset+12); + S.PISTON_MAX = S.uD(offset+15)*256 + S.uD(offset+14); + S.STARTUPDELAY = S.uD(offset+17)*256 + S.uD(offset+16); + S.DESCENT_MODE = S.uD(offset+18); + % Byte 19 not used + S.SPEED_DESCENT = NaN; %orginal double + S.DESCENT_TIMEOUTFACTOR = NaN; %orginal double + S.DESCENT_PISTONINTERVAL_MIN = S.uD(offset+28); + S.DESCENT_DEPTH_CONTROL = S.uD(offset+29); + S.DESCENT_PISTONINTERVAL = S.uD(offset+31)*256 + S.uD(offset+30); + S.DESCENT_PISTON_ONTIME = S.uD(offset+32); + % Byte 33 not used + S.PRES_DESCENT_MARGIN = NaN; %orginal double + S.PRES_PARKING = NaN; %orginal double + S.PRES_PARKING_HYSTERESE = S.uD(offset+42); + % Byte 43 not used + S.ATTENUATION_FACTOR = NaN; %orginal double + S.TIME_PARKSAMPLE = (S.uD(offset+51)*16777216 + S.uD(offset+50)*65536 + S.uD(offset+49)*256 + S.uD(offset+48))/60; + S.PARKING_SAMPLES = S.uD(offset+52); + S.PARKING_DELAY = S.uD(offset+53); + S.PRES_PARKINGHIGH = S.uD(offset+55)*256 + S.uD(offset+54); + S.SAFETYZONE_MARGIN = S.uD(offset+57)*256 + S.uD(offset+56); + S.SAFETYZONE_COUNTCHANGE = S.uD(offset+59)*256 + S.uD(offset+58); + S.SAFETYZONE_INCREMENT = S.uD(offset+61)*256 + S.uD(offset+60); + S.INITIAL_PARKCOUNT = S.uD(offset+63)*256 + S.uD(offset+62); + S.SPEED_SAG = NaN; %orginal double + S.SAG_TIMEOUTFACTOR = NaN; %orginal double + S.ASCENT_MODE = S.uD(offset+72); + % Byte 73 not used + S.SPEED_ASCENT = NaN; %orginal double + S.ASCENT_TIMEOUTFACTOR = NaN; %orginal double + S.ASCENT_PISTONINTERVAL_MIN = S.uD(offset+82); + S.ASCENT_DEPTH_CONTROL = S.uD(offset+83); + S.ASCENT_PISTONINTERVAL = S.uD(offset+85)*256 + S.uD(offset+84); + S.ASCENT_PISTON_ONTIME = S.uD(offset+86); + % Byte 87 not used + S.TEMP_ICEDETECT = NaN; %orginal double + S.ICE_DTMEDIAN = S.uD(offset+92); + S.ICE_DTENTRY = S.uD(offset+93); + S.SUCCESSFUL_ASCENTS = S.uD(offset+94); + S.POST_UPCAST_SAMPLES = S.uD(offset+95); + S.POST_UPCAST_DELAY = S.uD(offset+96); + S.TIME_SURFACE_ATTEMP = S.uD(offset+97); + S.TIME_TX = (S.uD(offset+101)*16777216 + S.uD(offset+100)*65536 + S.uD(offset+99)*256 + S.uD(offset+98))/60; + S.MULTIMSG = [S.uD(offset+102) S.uD(offset+103) S.uD(offset+104) S.uD(offset+105)]; + S.ARGOSID = S.uD(offset+106); + S.RAFOS_MODULEADR = S.uD(offset+107); + S.RECOVERY_START = S.uD(offset+109)*256 + S.uD(offset+108); + S.RECOVERY_TX = S.uD(offset+110); + % Byte 111 not used + S.RECOVERY_NOTX = S.uD(offset+113)*256 + S.uD(offset+112); + S.LOGMODE = S.uD(offset+114); + S.TESTMODE = S.uD(offset+115); + S.TIME_CO2WARMUP = (S.uD(offset+117)*256 + S.uD(offset+116))/60; + S.TIME_GPSFIX = (S.uD(offset+119)*256 + S.uD(offset+118))/60; + S.PRES_MAXALARM = S.uD(offset+121)*256 + S.uD(offset+120); + S.COUNT_MAXALARM = S.uD(offset+123)*256 + S.uD(offset+122); + S.CTDCP_FILTER = S.uD(offset+125)*256 + S.uD(offset+124); + S.PRES_CTDOFF_MAX = (S.sD(offset+127)*256 + S.uD(offset+126))/10; + S.PRES_CTDOFF_MIN = (S.sD(offset+129)*256 + S.uD(offset+128))/10; + S.PRES_CTDOFF_CHN = (S.uD(offset+131)*256 + S.uD(offset+130))/10; + S.PRES_PCUTOFF = (S.uD(offset+133)*256 + S.uD(offset+132))/10; + S.PRES_SAFETY = NaN; %orginal double + S.STARTPROFILE = S.uD(offset+139)*256 + S.uD(offset+138); + S.ICEGPS_TIMEOUT = S.uD(offset+140); + S.ICEIRDM_TIMEOUT = S.uD(offset+141); + S.CONFIGTX = S.uD(offset+142); + S.AIRPUMP_MODE = S.uD(offset+143); + S.AIRPUMP_PRESSDIFF = S.uD(offset+145)*256 + S.uD(offset+144); + S.AIRPUMP_TIMEOUT = S.uD(offset+147)*256 + S.uD(offset+146); + S.MISSION_MOD = S.uD(offset+149)*256 + S.uD(offset+148); + S.TIME_CYCLE2 = S.uD(offset+151)*256 + S.uD(offset+150); + % Changed + S.RAFOS_ACTIVE = [S.uD(offset+152) S.uD(offset+153) S.uD(offset+154) S.uD(offset+155) S.uD(offset+156)]; + S.RAFOS_STARTHH = [S.uD(offset+157) S.uD(offset+158) S.uD(offset+159) S.uD(offset+160) S.uD(offset+161)]; + S.RAFOS_STARTMM = [S.uD(offset+162) S.uD(offset+163) S.uD(offset+164) S.uD(offset+165) S.uD(offset+166)]; + % Byte 167 not used + S.RAFOS_ONMM = [S.uD(offset+169)*256+S.uD(offset+168) S.uD(offset+171)*256+S.uD(offset+170) ... + S.uD(offset+173)*256+S.uD(offset+172) S.uD(offset+175)*256+S.uD(offset+174) ... + S.uD(offset+177)*256+S.uD(offset+176)]; + offset = offset + 178; + otherwise + % Do nothing + +end + +% function end +end + + + \ No newline at end of file diff --git a/lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/press.m b/lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/press.m new file mode 100644 index 0000000..85885b5 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/press.m @@ -0,0 +1,30 @@ +function p=press(DATA, off, cnt) + + if cnt == 2 + % Low resulution mode + p = DATA(off+1)*256 + DATA(off); + if p <= 32767 + p = p/10; + else + p= (65535-p+1)/10*-1; + end + end + + if cnt == 4 + % High resulution mode + p = DATA(off+3)*16777216 + DATA(off+2)*65536 + DATA(off+1)*256 + DATA(off); + if p <= 2147483647 + p = p / 100; + else + p = (4294967295-p+1)/100*-1; + end + end + + +% function p=cnv_press(cnt) +% +% if cnt <= 32767 +% p=cnt/10; +% else +% p=(65535-cnt+1)/10*-1; +% end diff --git a/lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/salt.m b/lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/salt.m new file mode 100644 index 0000000..e382cfc --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/salt.m @@ -0,0 +1,19 @@ +function S=salt(DATA, off, cnt) + + if cnt == 2 + % Low resulution mode + S = DATA(off+1)*256 + DATA(off); + S = S / 1000; + end + + if cnt == 4 + % High resulution mode + S = DATA(off+3)*16777216 + DATA(off+2)*65536 + DATA(off+1)*256 + DATA(off); + S = S / 10000; + end + + +% function S=cnv_salt(cnt) +% +% S=cnt/1000; +% diff --git a/lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/temp.m b/lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/temp.m new file mode 100644 index 0000000..74fb89e --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/temp.m @@ -0,0 +1,31 @@ +function T=temp(DATA, off, cnt) + + if cnt == 2 + % Low resulution mode + T = DATA(off+1)*256 + DATA(off); + if T <= 62535 + T = T/1000; + else + T= (65535-T+1)/1000*-1; + end + end + + if cnt == 4 + % High resulution mode + T = DATA(off+3)*16777216 + DATA(off+2)*65536 + DATA(off+1)*256 + DATA(off); + if T <= 2147483647 + T = T / 10000; + else + T = (4294967295-T+1)/10000*-1; + end + end + + + +% function T=cnv_temp(cnt) +% +% if cnt <= 62535 +% T=cnt/1000; +% else +% T=(65535-cnt+1)/1000*-1; +% end diff --git a/lib/vendor/nemo2profilelib/+nemo/+optimare/decode_nemo.m b/lib/vendor/nemo2profilelib/+nemo/+optimare/decode_nemo.m new file mode 100644 index 0000000..f0aee1e --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+optimare/decode_nemo.m @@ -0,0 +1,316 @@ +function S = decode_nemo(S) + +% Decode different NEMO message types +% +% + +% Optimare Sensorsysteme, 22.10.2010 M.Busack + + +% Enable(1)/Disable(0) 3830 Optode sensor +funco2 = 1; + +switch S.DEVICE + + case 'CTD Profiler/Logger' + % Device type = CTD-Profiler/Logger + switch S.DATATYPE + + case 'Profile Message' + % Read profile header + S.SERIAL = S.uD(4)*256 + S.uD(3); + S.PROFILE_NUMBER = S.uD(2)*256 + S.uD(1); + S.STATUS_UPCAST = nemo.optimare.stat2str(S.uD(5),'C'); + % Startdate bytes 6,7,8 not used now + S.DS4_START = S.uD(9)*3600 + S.uD(10)*60 + S.uD(11); + S.BATT_CPU = S.uD(12) / 10; + S.BATT_PMP = S.uD(13) / 10; + S.PRES_TUBE_DEPTH = ( S.uD(14) / 10 ) * 68.9475; + % Enddate bytes 15,16,17 not used now + S.DS4_STOP = S.uD(18)*3600 + S.uD(19)*60 + S.uD(20); + S.BATT_CPU2 = S.uD(21) / 10; + S.BATT_PMP2 = S.uD(22) / 10; + S.PRES_TUBE_SURFACE = ( S.uD(23) / 10 ) * 69.9475; + %S.DATA_RECORDS_COMPLETE = S.uD(25)*256 + S.uD(24); + S.DATA_RECORDS1 = [0, 0, 0, S.uD(25)*256+S.uD(24), 0]; + S.DATA_RECORDS2 = S.DATA_RECORDS1; + offset = 26; + % Read sample data + if S.DATA_RECORDS1(4) > 0 + for k=1:S.DATA_RECORDS1(4) + S.DS4_PRES(k) = nemo.optimare.cnv.press(S.uD, offset, 2); + S.DS4_TEMP(k) = nemo.optimare.cnv.temp(S.uD, offset+2, 2); + S.DS4_SAL(k) = nemo.optimare.cnv.salt(S.uD, offset+4, 2); + offset = offset + 6; + end + end + + end + + + case 'NEMO Standard ' + % Device type = NEMO float + + switch S.DATATYPE + + case 'Startup Test Message' + S.SERIAL = S.uD(2)*256 + S.uD(1); + dt = [S.uD(3)+2000,S.uD(4),S.uD(5),0,0,0]; + S.SOFTWARE = datestr(dt); + dt = [S.uD(6)+2000,S.uD(7),S.uD(8),S.uD(9),S.uD(10),0]; + S.RTC = datestr(dt); + S.PISTON_MAX = S.uD(12)*256 + S.uD(11); + S.PISTON_MIN = S.uD(14)*256 + S.uD(13); + S.BATT_CPU = S.uD(15) / 10; + S.BATT_PMP = S.uD(16) / 10; + S.BATT_EXT = S.uD(17) / 10; + S.PRES_TUBE_SURFACE = (S.uD(18) / 10) * 68.94757; + S.PRES_PROFILE = nemo.optimare.cnv.press(S.uD, 19, 2); + S.PRES_PARKING = nemo.optimare.cnv.press(S.uD, 21, 2); + S.TIME_TX = S.uD(24)*256 + S.uD(23); + S.TIME_PARKSAMPLE = S.uD(26)*256 + S.uD(25); + S.TIME_CYCLE = S.uD(28)*256 + S.uD(27); + S.DESCENT_MODE = S.uD(29); + S.RECOVERY_START = S.uD(32)*256 + S.uD(31); + S.DESCENT_SPEED = (S.uD(34)*256 + S.uD(33))/1000; + S.TEMP_ICEDETECT = (S.sD(36)*256 + S.uD(35))/1000; + S.RECOVERY_TX = S.uD(37); + S.RECOVERY_NOTX = S.uD(40)*256 + S.uD(39); + dt = [S.uD(41)+2000,S.uD(42),S.uD(43),S.uD(44),S.uD(45),0]; + S.GPS_DATETIME = datestr(dt); + S.GPS_LAT = (S.sD(48)*256 + S.uD(47)) / 100; + S.GPS_LON = (S.sD(50)*256 + S.uD(49)) / 100; + + case 'Profile Message' + % Read profile header (ab rev.224) + S.SERIAL = S.uD(4)*256 + S.uD(3); + S.PROFILE_NUMBER = S.uD(2)*256 + S.uD(1); + S.STATUS_UPCAST = nemo.optimare.stat2str(S.uD(5),'P'); + % Byte 6 nicht benutzt + S.PROFILE_OLDER = S.uD(8)*256 + S.uD(7); + S.ERROR_MOTOR = S.uD(9); + S.ERROR_CTD = S.uD(10); + S.ERROR_OPTODE = S.uD(11); + S.ERROR_RAFOS = S.uD(12); + S.BATT_CPU = S.uD(13) / 10; + S.BATT_PMP = S.uD(14) / 10; + S.BATT_EXT = S.uD(15) / 10; + S.CURR_MOTOR = S.uD(16) / 100; + S.CURR_MOTOR_MEAN = S.uD(17) / 100; + if isnan(S.uD(18)) + S.CTD_MODE = NaN; + S.CTD_RES = NaN; + else + S.CTD_MODE = bitget(S.uD(18),1); + S.CTD_RES = bitget(S.uD(18),2); + end + S.PRES_TUBE_SURFACE = S.uD(20)*256 + S.uD(19); + S.TEMP_TUBE_SURFACE = (S.sD(22)*256 + S.uD(21))/10; + S.PRES_TUBE_SURFACE2 = S.uD(24)*256 + S.uD(23); + S.TEMP_TUBE_SURFACE2 = (S.sD(26)*256 + S.uD(25))/10; + S.PRES_TUBE_DEPTH = S.uD(28)*256 + S.uD(27); + S.TEMP_TUBE_DEPTH = (S.sD(30)*256 + S.uD(29))/10; + S.TEMP_SHT = (S.sD(32)*256 + S.uD(31)) / 10; + S.HUM_SHT = (S.uD(34)*256 + S.uD(33)) / 10; + S.PRES_OFFSET = nemo.optimare.cnv.press(S.uD, 35, 2); + S.PRES_SURFACE = nemo.optimare.cnv.press(S.uD, 37, 2); + S.PRES_PARKING = nemo.optimare.cnv.press(S.uD, 39, 2); + S.PRES_DEPTH = nemo.optimare.cnv.press(S.uD, 41, 2); + S.PRES_MAX = nemo.optimare.cnv.press(S.uD, 43, 2); + S.RECOVERY_START = S.uD(46)*256 + S.uD(45); + S.DATA_RECORDS1 = [S.uD(48)*256+S.uD(47), S.uD(50)*256+S.uD(49),... + S.uD(52)*256+S.uD(51), S.uD(54)*256+S.uD(53), S.uD(56)*256+S.uD(55)]; + S.PIST_SURFACE = S.uD(58)*256 + S.uD(57); + S.PIST_PARKING = S.uD(60)*256 + S.uD(59); + S.PIST_CALC = S.uD(62)*256 + S.uD(61); + S.PIST_DEPTH = S.uD(64)*256 + S.uD(63); + S.PIST_EOP = S.uD(66)*256 + S.uD(65); + S.MCNT_DESCENT = S.uD(70)*16777216 + S.uD(69)*65536 + S.uD(68)*256 + S.uD(67); + S.MCNT_PARKING = S.uD(74)*16777216 + S.uD(73)*65536 + S.uD(72)*256 + S.uD(71); + S.MCNT_SAG = S.uD(78)*16777216 + S.uD(77)*65536 + S.uD(76)*256 + S.uD(75); + S.MCNT_ASCENT = S.uD(82)*16777216 + S.uD(81)*65536 + S.uD(80)*256 + S.uD(79); + S.MCNT_ASCENT_END = S.uD(86)*16777216 + S.uD(85)*65536 + S.uD(84)*256 + S.uD(83); + S.MCNT_SURFACE = S.uD(90)*16777216 + S.uD(89)*65536 + S.uD(88)*256 + S.uD(87); + S.DATA_RECORDS2 = [S.uD(92)*256+S.uD(91), S.uD(94)*256+S.uD(93),... + S.uD(96)*256+S.uD(95), S.uD(98)*256+S.uD(97), S.uD(100)*256+S.uD(99)]; + % Verify data record quantity + for k = 1:5 + if ( (S.DATA_RECORDS1(k) == 0) && (S.DATA_RECORDS2(k) > 0) ) + q(k) = S.DATA_RECORDS2(k); + else + q(k) = S.DATA_RECORDS1(k); + end + end + % Set offset in array and resulution increment + offset = 101; + if S.CTD_RES == 0 + ctda = 2; + else + ctda = 4; + end + % Read profile data sets (ab Rev.224) + % Dataset 1 (profile header extensions) + if q(1) > 0 + for k=1:q(1) + % This record header is always there + S.DS1_TYPE(k) = S.uD(offset); + S.DS1_FORMAT(k) = S.uD(offset+1); + offset = offset + 2; + switch(S.DS1_TYPE(k)) + case 1 % Surface detect data only + if S.DS1_FORMAT(k) == 1 % Default format + S.ICEGPS_TIME = S.uD(offset); + S.ICEIRDM_TIME = S.uD(offset+1); + offset = offset + 2; + end + % Paste additional formats for type 1 here + + case 2 % Mission configuration data + [S,offset] = nemo.optimare.cnv.phext_t2(S,offset,k); + % Paste additional formats for type 2 here + + case 3 % Airpump control data + if S.DS1_FORMAT(k) == 1 % Default format + S.AIRPUMP_RUNTIME = S.uD(offset+1)*256 + S.uD(offset); + offset = offset + 2; + end + % Paste additional formats for type 3 here + + case 4 % Ice detection temperature data + if S.DS1_FORMAT(k) == 1 % Default format + S.ICE_TEMPMEDIAN = ( S.sD(offset+3)*16777216 + S.uD(offset+2)*65536 +... + S.uD(offset+1)*256 + S.uD(offset) ) / 10000; +% S.ICE_TEMPMEDIAN = ( S.uD(offset+3)*16777216 + S.uD(offset+2)*65536 +... +% S.uD(offset+1)*256 + S.uD(offset) ) / 10000; + offset = offset + 4; + end + % Paste additional formats for type 4 here + + % Paste additional types here + + otherwise + % Do nothing + + end + end + end + % Dataset 2 (Parking data) + if q(2) > 0 + for k=1:q(2) + S.DS2_PRES(k) = nemo.optimare.cnv.press(S.uD, offset, ctda); + S.DS2_TEMP(k) = nemo.optimare.cnv.temp(S.uD, offset+ctda, ctda); + S.DS2_SAL(k) = nemo.optimare.cnv.salt(S.uD, offset+2*ctda, ctda); + offset = offset + 3*ctda; + end + end + % Dataset 3 (Rafos data) + if q(3) > 0 + for k=1:q(3) + S.DS3_STATUS(k) = S.uD(offset); + S.DS3_DATE(k) = S.uD(offset+1)*372 + S.uD(offset+2)*31 + S.uD(offset+3); + S.DS3_DATE_year(k) = S.uD(offset+1)*372 ; + S.DS3_DATE_month(k) = S.uD(offset+2); + S.DS3_DATE_day(k) = S.uD(offset+3); + + S.DS3_TIME(k) = S.uD(offset+4)*3600 + S.uD(offset+5)*60 + S.uD(offset+6); + for i=1:6 + S.DS3_RAFOSAMP(k,i) = S.uD(offset+6+i); + S.DS3_RAFOSRANK(k,i) = (S.uD(offset+15+(i-1)*2)*256 + S.uD(offset+14+(i-1)*2))*0.3075; + end + % Evtl muss hier noch ein dummy byte eingefügt werden + % (ist im 160 0 00 00 502 schon drin) + % todo testen und in Firmware auch diesen datensatz + % evtl. mit high resulution ! + S.DS3_PRES(k) = nemo.optimare.cnv.press(S.uD, offset+26, 2); + S.DS3_TEMP(k) = nemo.optimare.cnv.temp(S.uD, offset+28, 2); + S.DS3_SAL(k) = nemo.optimare.cnv.salt(S.uD, offset+30, 2); + offset = offset + 32; + end + end + % Dataset 4 (Upcast profile data) + if q(4) > 0 + for k=1:q(4) + S.DS4_TIME(k) = S.uD(offset)*3600 + S.uD(offset+1)*60 + S.uD(offset+2); + S.DS4_FORMAT(k) = S.uD(offset+3); + S.DS4_PRES(k) = nemo.optimare.cnv.press(S.uD, offset+4, ctda); + offset = offset + 4 + ctda; + if S.CTD_MODE == 0 + % Discrete profile with full ctd data + S.DS4_TEMP(k) = nemo.optimare.cnv.temp(S.uD, offset, ctda); + S.DS4_SAL(k) = nemo.optimare.cnv.salt(S.uD, offset+ctda, ctda); + offset = offset + ctda*2; + else + % Cont. profile or NaN + S.DS4_TEMP(k) = NaN; + S.DS4_SAL(k) = NaN; + end + if ((funco2 > 0) || (S.DS4_FORMAT(k) > 0) ) + % Read additionaly sensor data + [S,offset] = nemo.optimare.read_sensors(S, k, offset); + end + end + end + % Dataset 5 (Extended surface data) + if q(5) > 0 + for k=1:q(5) + dt = [S.uD(offset)+2000,S.uD(offset+1),S.uD(offset+2),S.uD(offset+3),S.uD(offset+4),S.uD(offset+5)]; + %S.DS5_RTC(k) = datestr(dt); + S.DS5_RTC(k) = datenum(dt); + dt = [S.uD(offset+6)+2000,S.uD(offset+7),S.uD(offset+8),S.uD(offset+9),S.uD(offset+10),S.uD(offset+11)]; + %S.DS5_GPS(k) = datestr(dt); + S.DS5_GPS(k) = datenum(dt); + S.DS5_LAT(k) = (S.sD(offset+15)*16777216 + S.uD(offset+14)*65536 + S.uD(offset+13)*256 + S.uD(offset+12)) / 10000000; + S.DS5_LON(k) = (S.sD(offset+19)*16777216 + S.uD(offset+18)*65536 + S.uD(offset+17)*256 + S.uD(offset+16)) / 10000000; + offset = offset + 20; + end + end + + case 'MTM Report Message' + + + + case 'Recovery Message' + + + + case 'File transmission message' + + + case 'Filtered CTD data Message' + % Read continous profile data header (filtered) + S.SERIAL = S.uD(2)*256 + S.uD(1); + S.PROFILE_NUMBER = S.uD(4)*256 + S.uD(3); + S.DS4_START = S.uD(5)*3600 + S.uD(6)*60 + S.uD(7); + S.DS4_STOP = S.uD(8)*3600 + S.uD(9)*60 + S.uD(10); + S.DATA_RECORDS_COMPLETE = S.uD(12)*256 + S.uD(11); + S.DATA_RECORDS1 = [0, 0, 0, S.uD(14)*256+S.uD(13), 0]; + S.DATA_RECORDS2 = S.DATA_RECORDS1; + S.CTD_RES = bitget(S.uD(15),2); + offset = 16; + if S.CTD_RES == 0 + ctda = 2; + else + ctda = 4; + end + % Read sample data + if S.DATA_RECORDS1(4) > 0 + for k=1:S.DATA_RECORDS1(4) + S.DS4_PRES(k) = nemo.optimare.cnv.press(S.uD, offset, ctda); + S.DS4_TEMP(k) = nemo.optimare.cnv.temp(S.uD, offset+ctda, ctda); + S.DS4_SAL(k) = nemo.optimare.cnv.salt(S.uD, offset+2*ctda, ctda); + offset = offset + 3*ctda; + end + end + + end + + otherwise + % Unknown device type + +end + +% function end +end + + + \ No newline at end of file diff --git a/lib/vendor/nemo2profilelib/+nemo/+optimare/process_moms.m b/lib/vendor/nemo2profilelib/+nemo/+optimare/process_moms.m new file mode 100644 index 0000000..3f4688a --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+optimare/process_moms.m @@ -0,0 +1,340 @@ +function [S_all,S,SBD] = process_moms(filepath, imei) + +% Find and indentify mobile originated messages (mom) from Iridium SBD +% Transmitter. +% +% +% ATTENTION: Include path to repository /oze/functions ! + +% Optimare Sensorsysteme, 21.10.2010 M.Busack + +% compatibility fix, LP, 26.04.17: +S_all = struct(); + +% Get version from SVN +S.VERSION = '$Id: SBD2PROF.m 999 2012-06-18 10:00:00Z michael $'; +%S_all.empty=[]; +%clear all; + +% % Set filepath or get path via file dialog +% filepath='/home/michael/src/matlab/sbd2prof/sbd'; +% filepath=uigetdir; + +% Added support for searching specific imei +% Get all files from directory with .sbd extension +if (~exist('imei', 'var')) + D = dir(fullfile(filepath,'*.sbd')); +else + D = dir(fullfile(filepath, ['*' char(imei) '*.sbd'])); +end + +filelist=char(D.name); + +% Create figure +%figure; +%hold on; + +% bugfix: Output argument "SBD" (and maybe others) not assigned during +% call to "nemo.optimare.process_moms". +% LP, 27.04.17 +if (length(D) == 0) + SBD = []; +end + +% Read files +for i=1:length(D) + % Open file + infile=fullfile(filepath,filelist(i,:)); + % Read Message + SBD(i) = nemo.optimare.readSBDmsg(infile); +end + + +% Init processing variables +state = 'Readpart'; +findex = 0; % File index +grpflag = 0; % Message group flag, set to 1 if startmsg was found. Set to 0 at end of group. +msgcnt = 1; % Message counter +ofid = 0; % No output file +exit = 0; + +% Init overall energy variables +pnr = 0; +E.ENERGYCOMPLETE = 0; + +% Process messages +while exit == 0 + + %state + switch state + + case 'Readpart' + % Increment file counter index + findex = findex + 1; + if findex <= length(D) + % We have at least one file to read. + % Read SBD message + filename = fullfile(filepath,filelist(findex,:)); + %SBD(findex) = nemo.optimare.readSBDmsg(filename); + % Check if we have a valid message type + if SBD(findex).status == 1 + % Yes, this is a known device/data message + state = 'Getpart'; + else + % No, try again with next message + disp('Error: File has unknown device/data type'); + state = 'Readpart'; + end + else + % We reached the end of filelist. + % Before end, check if there is a group pending. + if grpflag == 1 + % A message group is pending, finish this first. + state = 'Endgroup'; + else + % Everything if finished, end function. + exit = 1; + end + end + + case 'Getpart' + % Decodable message was found + if SBD(findex).elementary_num == 1 + % This is the first message of a group + if grpflag == 0 + % Last group was finished successfully. + % => Everything is ok. + state = 'Startgroup'; + else + % We have an incomplete group pending. + % => Finish this group before starting the next one. + % Because findex in incremented after process, + % decrement here to prevent skipping of a file. + findex = findex - 1; + state = 'Endgroup'; + end + else + % This is not the first message of a group + if grpflag == 0 + % Group was not started before. + % => This can happen if the first message is missing + state = 'Startgroup'; + else + % Start of group was received before. + % => Everything is ok. + % Exception: Last message of group before is missing + % AND first message of next group is missing also ! + % TBD!! + state = 'Checkpart'; + end + end + + case 'Startgroup' + % Start of message group found. + % First, clear data from group before. + clear S; + grpflag = 1; + % Init device/data type + S.DEVICE = SBD(findex).device; + S.DATATYPE = SBD(findex).data; + % Set message counter + msgcnt = SBD(findex).elementary_num; + % Init buffer + l = SBD(findex).total_num * SBD(findex).ldata + 10; + l = l * 1.3; + l = ceil(l); % OB: added ceil, as ones requires interger parameter + S.uD=ones(1,l)*NaN; + S.sD=S.uD; + % Offset for uD/sD Array. All messages must have the same + % datasize here (expect the last one). + S.DATAPAYLOAD = SBD(findex).ldata; + % Create output file name with .out extension + filename = filelist(findex,:); + l = length(filename); + filename(l) = 't'; + filename(l-1) = 'u'; + filename(l-2) = 'o'; + % Open file for output + outfile = fullfile(filepath,filename); + %ofid = fopen(outfile,'w'); + % Next state + state = 'Checkpart'; + + case 'Checkpart' + % Check for message consitence + if SBD(findex).elementary_num == msgcnt + % Expected message number is ok + state = 'Writepart'; + else + % Message number read is not the expected one + % This can happen if one message/file is missing + %fprintf(ofid,'\r\nFilename: %s Device: %s Data type: %s Message: %3d of %3d, not found',... + % filelist(findex,:), SBD(findex).device, SBD(findex).data, msgcnt, SBD(findex).total_num); + % What to do ? + if msgcnt < SBD(findex).elementary_num + % Expected message number is smaller than received one. + % This can happen if a group file in the middle is missing. + % Try again expecting next message. + msgcnt = msgcnt + 1; + else + % Expected message number is larger than received one. + state = 'Endgroup'; + + % Ist das richtig ? : + % This can happen if this is the next group, already. + % Maybe the last message from the group before and the + % first message of the next group are missing. + end + end + + case 'Writepart' + % Write header information to file + %fprintf(ofid,'\r\nFilename: %s Device: %s Data type: %s Message: %3d of %3d, %3d bytes of data',... + % filelist(findex,:), SBD(findex).device, SBD(findex).data, SBD(findex).elementary_num,... + % SBD(findex).total_num, SBD(findex).ldata); + % Copy data to buffer + offset = (SBD(findex).elementary_num-1)*S.DATAPAYLOAD; + i = offset+1:offset+SBD(findex).ldata; + S.uD(i) = SBD(findex).udata; + S.sD(i) = SBD(findex).sdata; + % Check if message is last in set + if SBD(findex).elementary_num == SBD(findex).total_num + % Message is last one in set, end of group reached. + state = 'Endgroup'; + else + % Not the last message, read next one. + msgcnt = msgcnt + 1; + state = 'Readpart'; + end + + case 'Endgroup' + % Finish group + % Wir auch aufgerufen, wenn die leltzte msg fehlt ! + % Close file. + %fprintf(ofid,'\r\nNo more messages to read.'); + %fclose(ofid); + % Set flag to 'group closed successfully' + grpflag = 0; + state = 'Process'; + + case 'Process' + try + % Process the received group + % First, decode data + S = nemo.optimare.decode_nemo(S); + % Write data to output file + %write_NEMO(S,outfile); + catch ex + end + if findex > length(D) + exit = 1; + else + %state = 'Energycalculation'; % with energy calculation + state = 'Readpart'; % without any calculations + end + expr=['S_all.S_' num2str(findex) '=S;']; + eval(expr); + + case 'Energycalculation' + % Perform energy calculation from mission runtime and piston + % movements + if isfield(S,'DATA_RECORDS1'), + if S.DATA_RECORDS1(4) > 0 + %plot(S.DS4_PRES+findex); % Datset 4 pressure + %plot(S.PROFILE_NUMBER, S.DS4_LICH1+S.DS4_LICH2+S.DS4_LICH3, '.-b'); + %plot(S.PROFILE_NUMBER,S.DS4_LICH1,'.-r',... + % S.PROFILE_NUMBER,S.DS4_LICH2,'.-g',... + % S.PROFILE_NUMBER,S.DS4_LICH3,'.-b'); + + end + end + % Calculate overall energy (works only if all data from same float) + if isfield(S,'PROFILE_NUMBER'), + pnr = pnr + 1; + E.VALID(pnr) = 1; + E.PROFNR(pnr) = S.PROFILE_NUMBER; + + if ( (isfield(S,'PIST_SURFACE')) & (isfield(S,'PIST_PARKING')) ) + E.PISTON_DESCENT(pnr) = S.PIST_PARKING - S.PIST_SURFACE; + else + E.VALID(pnr) = 0; + end + if ( (isfield(S,'PIST_DEPTH')) & (isfield(S,'PIST_EOP')) & (E.VALID(pnr) == 1) ) + E.PISTON_ASCENT(pnr) = S.PIST_DEPTH - S.PIST_EOP; + else + E.VALID(pnr) = 0; + end + if ( (isfield(S,'CURR_MOTOR_MEAN')) & (E.VALID(pnr) == 1) ) + E.MOTORCURRENT(pnr) = S.CURR_MOTOR_MEAN; + else + E.VALID(pnr) = 0; + end + if E.VALID(pnr) == 1 + E.ENERGY(pnr) = (E.PISTON_DESCENT(pnr) + E.PISTON_ASCENT(pnr)) * E.MOTORCURRENT(pnr); + E.ENERGYCOMPLETE = E.ENERGYCOMPLETE + E.ENERGY(pnr); + end + end + % Start again + state = 'Readpart'; + + otherwise + % End loop + disp('State machine error'); + exit = 1; + end +end + +%hold off; + +%whos; + +% +% +% % This is the first message of a set. +% if ( (findex > 1) & (msgcnt < SBD(findex-1).total_num) ) +% % Only if this is NOT the first file from the list +% % and the expected elementray number (msgcnt) is +% % smaller than the total number of this set. +% % This can happen if the last set was not received +% % completly (one or more messages missing at the +% % end) +% % Todo: Search for any of the missing files. +% state = 'Checkpart'; +% +% % Note: msgcnt is equal to total number if last +% % set was completly received. So, we have a set with +% % at least the last file missing. +% % Use header informations from last message. +% %SBD(findex).device = SBD(findex-1).device; +% %SBD(findex).data = SBD(findex-1).data; +% %SBD(findex).total_num = SBD(findex-1).total_num; +% %msgcnt = msgcnt + 1; +% %state = 'Checkpart'; +% +% else +% % Start of message set (i.e. a profile) found +% if ofid > 0 +% % Close open output file +% % This can happen, if last file of message set is +% % missing +% fclose(ofid); +% end +% % Start of new message set +% state = 'Startpart'; +% end +% else +% % Message is not the first one +% if ofid == 0 +% % No output file was created before. +% % This can happen, if first message of set is missing +% state = 'Startpart'; +% msgcnt = SBD(findex).elementary_num; +% else +% % Output file present, regular writing of data +% state = 'Checkpart'; +% end +% end + + + +%end \ No newline at end of file diff --git a/lib/vendor/nemo2profilelib/+nemo/+optimare/readSBDmsg.m b/lib/vendor/nemo2profilelib/+nemo/+optimare/readSBDmsg.m new file mode 100644 index 0000000..f5d6bae --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+optimare/readSBDmsg.m @@ -0,0 +1,55 @@ +function sbdmsg = readSBDmsg(infi) + +% Read data from SBD file +% + +% Optimare Sensorsysteme, 21.10.2010 M.Busack + +% VERSION = '$Id: CTD2awi.m 701 2010-10-21 10:00:00Z michael $'; + + + +% Open file for reading +fid=fopen(infi,'r'); + +% Read first header byte : crc_status +sbdmsg.crc_status = fread(fid,1,'uchar'); + +% Read next : checksum +sbdmsg.checksum = fread(fid,1,'uint16'); + +% Read next : mtmsn +sbdmsg.mtmsn = fread(fid,1,'uchar'); + +% Read next : elementry message number +sbdmsg.elementary_num = fread(fid,1,'uchar'); + +% Read next : total message number +sbdmsg.total_num = fread(fid,1,'uchar'); + +% Read next : device/data type +device_data = fread(fid,1,'uchar'); +[sbdmsg.device,sbdmsg.data,sbdmsg.status] = nemo.optimare.cnv.devicedata(device_data); + +% Read next : profile number LSB +sbdmsg.profilenumber_lsb = fread(fid,1,'uchar'); + +% Read rest of file and store to 'udata' and 'sdata' array +offset = ftell(fid); +sbdmsg.udata = fread(fid,inf,'uchar'); +fseek(fid,offset,'bof'); % OB what is this good for? +sbdmsg.sdata = fread(fid,inf,'schar'); +sbdmsg.ldata = ftell(fid) - offset; + +sbdmsg.sbdfilename=infi; +[~, filename, ~] = fileparts(infi); +splitted_file = strsplit(filename, '_'); +sbdmsg.imei = str2num(splitted_file{1}); +sbdmsg.momsn = str2num(splitted_file{2}); + + +% Close file +fclose(fid); + + +end diff --git a/lib/vendor/nemo2profilelib/+nemo/+optimare/read_sensors.m b/lib/vendor/nemo2profilelib/+nemo/+optimare/read_sensors.m new file mode 100644 index 0000000..3e31b39 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+optimare/read_sensors.m @@ -0,0 +1,75 @@ +function [S,off] = read_sensors(S, i, off) + +% Read optinal data from additional sensors +% +% + +% Optimare Sensorsysteme, 26.10.2010 M.Busack + +o2type = 3830; +%o2type = 4330; + +% Format +%sen = uint8(S.uD(off)); +%off = off + 2; +sen = uint8(S.DS4_FORMAT(i)); + +% O2 Optode data +if bitget(sen,2) == 1 + if o2type == 4330 + % 4330 Optode + S.DS4_O2CONC(i) = (S.uD(off+3)*16777216 + S.uD(off+2)*65536 + S.uD(off+1)*256 + S.uD(off)) / 1000; + S.DS4_O2SATU(i) = (S.uD(off+7)*16777216 + S.uD(off+6)*65536 + S.uD(off+5)*256 + S.uD(off+4)) / 1000; + S.DS4_O2TEMP(i) = (S.uD(off+11)*16777216 + S.uD(off+10)*65536 + S.uD(off+9)*256 + S.uD(off+8)) / 1000; + S.DS4_O2CALP(i) = (S.uD(off+15)*16777216 + S.uD(off+14)*65536 + S.uD(off+13)*256 + S.uD(off+12)) / 1000; + S.DS4_O2TCP(i) = (S.uD(off+19)*16777216 + S.uD(off+18)*65536 + S.uD(off+17)*256 + S.uD(off+16)) / 1000; + off = off + 20; + %disp(['Read_sensors offset ',num2str(off)]); + end + if o2type == 3830 + % 3830 Optode + S.DS4_O2CONC(i) = ( (S.uD(off+1)*256 + S.uD(off)) / 100 ) - 10; + S.DS4_O2SATU(i) = ( (S.uD(off+3)*256 + S.uD(off+2)) / 100 ) - 10; + S.DS4_O2TEMP(i) = ( (S.uD(off+5)*256 + S.uD(off+4)) / 100 ) - 10; + off = off + 6; + if bitget(sen,1) == 1 + % Raw data format + S.DS4_O2DPHASE(i) = (S.uD(off+1)*256 + S.uD(off)) / 100; + S.DS4_O2BPHASE(i) = (S.uD(off+3)*256 + S.uD(off+2)) / 100; + S.DS4_O2RPHASE(i) = (S.uD(off+5)*256 + S.uD(off+4)) / 100; + S.DS4_O2BAMP(i) = (S.uD(off+9)*16777216 + S.uD(off+8)*65536 + S.uD(off+7)*256 + S.uD(off+6)) / 10; + S.DS4_O2BPOT(i) = (S.uD(off+11)*256 + S.uD(off+10)) / 100; + S.DS4_O2RAMP(i) = (S.uD(off+15)*16777216 + S.uD(off+14)*65536 + S.uD(off+13)*256 + S.uD(off+12)) / 10; + S.DS4_O2RAWTEMP(i) = (S.sD(off+19)*16777216 + S.uD(off+18)*65536 + S.uD(off+17)*256 + S.uD(off+16)) / 100; + off = off + 20; + end + end +end + + +% CO2 sensor data +if bitget(sen,3) == 1 + dt = [S.uD(off)+2000,S.uD(off+1),S.uD(off+2),S.uD(off+3),S.uD(off+4),S.uD(off+5)]; + %S.DS4_CO2DATETIME(i) = datestr(dt); + S.DS4_CO2DATETIME(i) = datenum(dt); + S.DS4_CO2PNDIR(i) = S.uD(off+9)*16777216 + S.uD(off+8)*65536 + S.uD(off+7)*256 + S.uD(off+6); + S.DS4_CO2PIN(i) = S.uD(off+13)*16777216 + S.uD(off+12)*65536 + S.uD(off+11)*256 + S.uD(off+10); + S.DS4_CO2SRAW(i) = S.uD(off+15)*256 + S.uD(off+14); + S.DS4_CO2SREF(i) = S.uD(off+17)*256 + S.uD(off+16); + S.DS4_CO2TSEN(i) = S.uD(off+19)*256 + S.uD(off+18); + S.DS4_CO2PCO2(i) = S.sD(off+23)*16777216 + S.uD(off+22)*65536 + S.uD(off+21)*256 + S.uD(off+20); + S.DS4_CO2TCTRL(i) = S.uD(off+25)*256 + S.uD(off+24); + S.DS4_CO2TGAS(i) = S.uD(off+27)*256 + S.uD(off+26); + S.DS4_CO2RHGAS(i) = S.uD(off+31)*16777216 + S.uD(off+30)*65536 + S.uD(off+29)*256 + S.uD(off+28); + off = off + 32; +end + + +% Lightsensor data +if bitget(sen,4) == 1 + S.DS4_LICH1(i) = S.uD(off+1)*256 + S.uD(off); + S.DS4_LICH2(i) = S.uD(off+3)*256 + S.uD(off+2); + S.DS4_LICH3(i) = S.uD(off+5)*256 + S.uD(off+4); + off = off + 6; +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+optimare/stat2str.m b/lib/vendor/nemo2profilelib/+nemo/+optimare/stat2str.m new file mode 100644 index 0000000..674c102 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+optimare/stat2str.m @@ -0,0 +1,70 @@ +function txt = stat2str(status, type) + +% Convert number of status to string +% +% + +% Optimare Sensorsysteme, 22.10.2010 M.Busack + +if type == 'R' + switch (status) + + case 0 + txt = sprintf('OFF '); + + case 1 + txt = sprintf('ON '); + + case 2 + txt = sprintf('DONE '); + + case 3 + txt = sprintf('OVERLAP ERROR'); + + case 4 + txt = sprintf('ENDON ERROR '); + + case 5 + txt = sprintf('COM ERROR '); + + otherwise + txt = sprintf('UNKNOWN '); + end + +end + +if type == 'P' + switch (status) + + case 0 + txt = sprintf('BUSY '); + + case 1 + txt = sprintf('DONE '); + + case 2 + txt = sprintf('TIMEOUT '); + + case 3 + txt = sprintf('ICE DETECTION'); + + otherwise + txt = sprintf('UNKNOWN '); + end + +end + +if type == 'C' + switch (status) + + case 0 + txt = sprintf('DONE '); + + case 1 + txt = sprintf('CTD FAILURE '); + + otherwise + txt = sprintf('UNKNOWN '); + end + +end diff --git a/lib/vendor/nemo2profilelib/+nemo/+plot/+templates/create_south.m b/lib/vendor/nemo2profilelib/+nemo/+plot/+templates/create_south.m new file mode 100644 index 0000000..30697ef --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+plot/+templates/create_south.m @@ -0,0 +1,157 @@ +function [ figure_id ] = create_south( p_config ) +%CREATE_SOUTH Creates a template plot that can be used for a variety of plots that require Weddell Sea as location. +% +% Returns: +% figure_id: Figure handle of the template. +% +% Parameters: +% p_config: Global configuration file, load it using nemo.load.config(). + + +%% Initialize return variable +figure_id = false; + +%% Create template + +% load additional files from disk +additional_content = nemo.load.additional_content(p_config); + +% store loaded data (should be changed on long term thought) +Latitudes = additional_content.antarctic_latitudes; +Longitudes = additional_content.antarctic_longitudes; +ASI_Ice_Concentration = additional_content.antarctic_ice_concentration; +Z = additional_content.etopo_z; +refvec = additional_content.etopo_refvec; +moorings = additional_content.moorings; + +clear additional_content + +% initialize figure +figure_tmp = figure('Visible', 'off'); +% prepare contour variables +[C1000,~] = contourm(Z,refvec,[-1000, -1000]); +[C2000,~] = contourm(Z,refvec,[-2000, -2000]); +[C3000,~] = contourm(Z,refvec,[-3000, -3000]); +%[C3500,~] = contourm(Z,refvec,[-3500 -3500]); +%[C4000,~] = contourm(Z,refvec,[-4000 -4000]); +close(figure_tmp); +clear figure_tmp Z refvec + +%figure_id = figure(); +figure_id = figure('Visible', 'Off'); + +%set(figure_id,'color',[1 1 1],'Position',get(0,'ScreenSize')); +%set(figure_id,'color',[1 1 1],'Position',[0, 0, 4000, 2250]); +worldmap(p_config.PLOT_OVERVIEW_SOUTH.latitude_limit, ... + p_config.PLOT_OVERVIEW_SOUTH.longitude_limit); +load coast +plotm(lat,long,'k'); + +geoshow(Latitudes, Longitudes, ASI_Ice_Concentration, 'DisplayType','surface'); +contourcmap('gray',10,'colorbar','off','location','horizontal', 'Visible', 'off'); +nValues = 128; %# The number of unique values in the colormap +%map = [linspace(0.5,1,nValues)' linspace(0.7,1,nValues)' ones(nValues,1)]; %'# 128-by-3 colormap +%colormap(map); +cmocean('ice', 'pivot', '-1'); + +ax_1000=plotm(C1000(2,:),C1000(1,:),'color',[0.2 0.6 .8]); +ax_2000=plotm(C2000(2,:),C2000(1,:),'color',[0 0 1]); +ax_3000= plotm(C3000(2,:),C3000(1,:),'color',[0.2 .4 0.8]); + +%ax_3500= plotm(C3500(2,:),C3500(1,:),'color',[0.2 .2 0.8]); +%ax_4000= plotm(C4000(2,:),C4000(1,:),'color',[0.2 .0 0.8]); + +%% make legend +%opengl hardware; % can only be used on pc with dedicated graphics + +ax_deploy=plot3m(0,0,-1,'marker',p_config.PLOT_STYLE_SOUTH.deploy_marker,'markersize',p_config.PLOT_STYLE_SOUTH.deploy_markersize,'color', p_config.PLOT_STYLE_SOUTH.deploy_color,'linestyle','none'); +ax_gps=plot3m(0,0,-1,'marker',p_config.PLOT_STYLE_SOUTH.gps_marker,'markersize',p_config.PLOT_STYLE_SOUTH.gps_markersize,'color',p_config.PLOT_STYLE_SOUTH.gps_color,'MarkerFaceColor', p_config.PLOT_STYLE_SOUTH.gps_color,'linestyle','none'); +ax_gps_interpolated=plot3m(0,0,-1,'marker',p_config.PLOT_STYLE_SOUTH.gps_marker,'markersize',p_config.PLOT_STYLE_SOUTH.gps_markersize,'color',p_config.PLOT_STYLE_SOUTH.gps_color_interpolated,'MarkerFaceColor', p_config.PLOT_STYLE_SOUTH.gps_color_interpolated,'linestyle','none'); +ax_last_transmittion=plot3m(0,0,-1,'marker',p_config.PLOT_STYLE_SOUTH.last_transmittion_marker,'markersize',p_config.PLOT_STYLE_SOUTH.last_transmittion_markersize,'color',p_config.PLOT_STYLE_SOUTH.last_transmittion_color,'MarkerFaceColor',p_config.PLOT_STYLE_SOUTH.last_transmittion_color,'linestyle','none'); + + +legend([ax_deploy,ax_gps,ax_gps_interpolated,ax_last_transmittion,ax_1000,ax_2000,ax_3000],'Deployment positions','Best guess positions', 'Best guess positions (interpolated)', 'Last known position','-1000m','-2000m','-3000m'); +%legend([ax_deploy,ax_gps,ax_gps_interpolated,ax_last_transmittion,ax_3000,ax_3500,ax_4000],'Deployment positions','Best guess positions', 'Best guess positions (interpolated)', 'Last known position','-3000m','-3500m','-4000m'); + +% cb=colorbar('southoutside'); +% ylabel(cb, 'Sea ice concentration in %'); + +%% create title +text_figure_title = title('', 'position', [4.367541205137968 -5154159.006427274 99.99999999999999]); +set(text_figure_title,'interpreter','none','fontsize',14); + +%% plot soso + +earthradius = 3.4401e+003; % nm + + +if p_config.PLOT_OVERVIEW_SOUTH.show_moorings == 1 + + if ~iscell(moorings) + warning([mfilename ': Error loading moorings, skipping...']); + else + for o_moorings = 1:length(moorings) + lat = moorings{o_moorings}.lat; + lon = moorings{o_moorings}.lon; + detection_radius = moorings{o_moorings}.detection_radius; + if (~moorings{o_moorings}.hide_mooring) + switch (p_config.PLOT_STYLE_SOUTH.moorings_display_format) + case 1 + plot3m(lat,lon,140,'.c','markersize',24); + [latc,longc,] = scircle1(lat, lon, detection_radius,[],earthradius); + height=ones(numel(latc),1)*200; + plot3m(latc,longc,height,'color','k'); + case 2 + plot3m(lat, lon, 140, 'pg','markersize',8,'MarkerFaceColor','k'); + case 3 + plotm(lat, lon,'.c','markersize',24); + [latc,longc] = scircle1(lat, lon, detection_radius,[],earthradius); + height=ones(numel(latc),1)*200; + plot3m(latc,longc,height,'color','k'); + case 4 + plot3m(lat, lon, 140,'dr','markersize',6,'LineWidth',3); + [latc,longc] = scircle1(lat, lon, detection_radius,[],earthradius); + height=ones(numel(latc),1)*200; + plot3m(latc,longc,height,'color','k'); + case 5 + plot3m(lat, lon,140,'.','markersize',30,'color',[.8 .8 .8]); + [latc,longc] = scircle1(lat, lon, detection_radius,[],earthradius); + height=ones(numel(latc),1)*200; + plot3m(latc,longc,height,'color',[.8 .8 .8]); + case 6 + plot3m(lat, lon,140,'ok','markersize',6,'linewidth',2.5); + [latc,longc] = scircle1(lat, lon, detection_radius,[],earthradius); + height=ones(numel(latc),1)*200; + plot3m(latc,longc,height,'color','k'); + case 7 + plot3m(lat, lon,140,'og','markersize',6,'linewidth',2.5); + [latc,longc] = scircle1(lat, lon, detection_radius,[],earthradius); + geoshow(latc,longc,'color','k'); + case 8 + plot3m(lat, lon,140,'k*','markersize',8); + case 9 + plot3m(lat, lon,140,'.','markersize',30,'color',[.8 .8 .8]); + [latc,longc] = scircle1(lat, lon, detection_radius,[],earthradius); + height=ones(numel(latc),1)*200; + plot3m(latc,longc,height,'color',[0 0 0]); + otherwise + plot3m(lat, lon,140,'.','markersize',30,'color',[.8 .8 .8]); + [latc,longc] = scircle1(lat, lon, detection_radius,[],earthradius); + height=ones(numel(latc),1)*200; + plot3m(latc,longc,height,'color',[0 0 0]); + end + + if moorings{o_moorings}.show_name + h2=textm(lat, lon,140, moorings{o_moorings}.name); + set(h2,'Color','k', 'FontSize',6,'FontWeight','bold','HorizontalAlignment','center'); + end + + end + end + clear lat lon detection_radius + end + +end + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+plot/arrival_of_profiles.m b/lib/vendor/nemo2profilelib/+nemo/+plot/arrival_of_profiles.m new file mode 100644 index 0000000..5091df5 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+plot/arrival_of_profiles.m @@ -0,0 +1,261 @@ +function [ boolean ] = arrival_of_profiles( p_nemo_data, p_config ) +%ARRIVAL_OF_PROFILES Plots details about iridium data. +% +% Returns: +% boolean: False if something went wrong during the plotting process. +% +% Parameters: +% p_nemo_data: Struct, containing all float data (load with nemo.load.data()). +% p_config: Global configuration file. Load using nemo.load.config(). + +%% Initialize return variable +boolean = false; + +%% Parameter check +parameter_error = false; + +if (~iscell(p_nemo_data) || ~isstruct(p_config)) + parameter_error = true; +end + +if (parameter_error) % display error message + disp([ mfilename ': Wrong parameter! Please check your input parameters carefully!']); + return; +else % do some cleanup + clear parameter_error; +end + +%% Setup directories +directory = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_ARRIVAL_OF_PROFILES.storage_directory_image); +directory_fig = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_ARRIVAL_OF_PROFILES.storage_directory_fig); +image_type = p_config.PLOT_ARRIVAL_OF_PROFILES.image_type; +if ~exist(directory, 'dir') + system(['mkdir ' directory]); +end +if ~exist(directory_fig, 'dir') + mkdir(directory_fig); +end + +%% Process every float + +float_count = length(p_nemo_data); + +for o_float = 1:float_count + + % collect required data + + % count of profiles that have not been send earlier + unsent_profiles_count = nemo.float.collect( ... + p_nemo_data{o_float}, ... + 'PROFILE_TECHNICAL_DATA', ... + 'xmit_older_profiles_not_send' ... + ); + + % average time when the profile has been transmitted + iridium_mean_times = nemo.float.collect( ... + p_nemo_data{o_float}, ... + 'IRIDIUM_DATA', ... + '' ... + ); + % extract only datevec from it + iridium_mean_times = datetime(iridium_mean_times(:, 4:9)); + + % data about every iridium message that has been transmitted for this profile + iridium_data = nemo.float.collect( ... + p_nemo_data{o_float}, ... + 'IRIDIUM_POSITIONS', ... + '' ... + ); + iridium_message_count = zeros(1, length(iridium_data)); + % time will be stored in seconds using etime + iridium_transmittion_time = zeros(1, length(iridium_data)); + for o_tmp = 1:length(iridium_data) + iridium_message_count(o_tmp) = size(iridium_data{o_tmp}, 1); + % get iridium transmittion time if available + if iridium_message_count(o_tmp) > 1 + tmp_min = iridium_data{o_tmp}(1, 4:9); + tmp_max = iridium_data{o_tmp}(end, 4:9); + iridium_transmittion_time(o_tmp) = etime(tmp_max, tmp_min); + else + iridium_transmittion_time(o_tmp) = NaN; + end + end + + clear o_tmp iridium_data + + % get numbers of the profiles that have been received + transmitted_profiles = nemo.float.collect(p_nemo_data{o_float}, ... + 'PROFILE_TECHNICAL_DATA', ... + 'xmit_profile_number' ... + ); + + % get gps data + gps_datetime = nemo.float.collect(p_nemo_data{o_float}, ... + 'SURFACE_GPS_DATA', ... + '' ... + ); + + best_guess_datevec = nemo.float.collect(p_nemo_data{o_float}, ... + 'SECOND_ORDER_INFORMATION', ... + 'best_guess_datevec' ... + ); + best_guess_datenum = datenum(best_guess_datevec); + + if isempty(gps_datetime) + disp(['No arrival of profiles generated for nemo ' ... + num2str(p_nemo_data{o_float}{1}.FLOAT_IDENTIFICATION.float_serial_number) ... + '. No GPS information available!']); + continue; + end + + + % get plot details + width = p_config.PLOT_ARRIVAL_OF_PROFILES.image_width; + height = p_config.PLOT_ARRIVAL_OF_PROFILES.image_height; + paperunit = p_config.PLOT_ARRIVAL_OF_PROFILES.image_paperunit; + + % plot collected data + + figure_id = figure('Visible', 'off'); % create figure + % initialize figure + set(figure_id,'InvertHardcopy','on'); + set(figure_id,'PaperUnits', paperunit); + papersize = get(figure_id, 'PaperSize'); + left = (papersize(1)- width)/2; + bottom = (papersize(2)- height)/2; + myfiguresize = [left, bottom, width, height]; + set(figure_id,'PaperPosition', myfiguresize); + + clear height width left bottom papersize myfiguresize + + %set(gcf, 'Position', get(0,'ScreenSize'), 'color', [1 1 1]); + sub = {}; + + sub{1} = subplot(4,1,1); + hold on + set(gca, 'YLimMode', 'manual', 'YLim', [1, length(transmitted_profiles)+1]); +% set(gca, 'XLimMode', 'manual', 'XLim', [min_datenum, max_datenum]); +% set_x_axis(); + + disp([' Processing arrival information of float number ',num2str(p_nemo_data{o_float}{1}.FLOAT_IDENTIFICATION.float_serial_number)]) % OB + + plot(best_guess_datenum, transmitted_profiles,'marker','.','color',[0.5 0.5 0.5],'linestyle','none','linewidth',1,'markersize',20); + + % OB START: for float 173 iridium_mean_times is of type datetime, which does not + % comply with plot command + % Old Code: + % plot(iridium_mean_times, transmitted_profiles,'marker','.','color',[0 0 0],'linestyle','none','linewidth',1,'markersize',20); + + % New Code + if isa(iridium_mean_times,'double') + plot(iridium_mean_times, transmitted_profiles,'marker','.','color',[0 0 0],'linestyle','none','linewidth',1,'markersize',20); + elseif isa(iridium_mean_times,'datetime') + plot(datenum(iridium_mean_times), transmitted_profiles,'marker','.','color',[0 0 0],'linestyle','none','linewidth',1,'markersize',20); + end + % OB END + + t = title([ 'Float ' ... + num2str(p_nemo_data{o_float}{1}.FLOAT_IDENTIFICATION.float_serial_number) ... + ' @ ' ... + datestr(datetime('now', 'Format', 'dd-MMM-yyyy HH:mm:SS')) ... + ]); + set(t, 'FontSize', 18); + clear t + ylabel('Profile number'); + grid on + box on + hold off + + % unsent profiles_count + sub{2} = subplot(4,1,2); + hold on +% set(gca, 'XLimMode', 'manual', 'XLim', [min_datenum, max_datenum]); +% set_x_axis(); + plot(best_guess_datenum, unsent_profiles_count, 'marker','s','color',[0 0 0],'markerfacecolor',[0 0 0],'linestyle','none','markersize',4); + + + ylabel('Unsent profile count'); + grid on + box on + hold off + + sub{3} = subplot(4,1,3); + hold on + plot(best_guess_datenum, iridium_transmittion_time./60, '.-','Markersize',15 ); + ylabel('Transmission time [min]'); + + grid on + box on + hold off + + sub{4} = subplot(4,1,4); + hold on + plot(best_guess_datenum, iridium_message_count, '.-', 'linestyle', 'none','Markersize',15 ); + + ylabel('Message count'); + grid on + box on + + hold off + + sub = cellfun(@set_x_axis, sub, 'UniformOutput', false); + + % store plot + + image_filename = [p_config.PLOT_ARRIVAL_OF_PROFILES.image_filename ... + num2str(p_nemo_data{o_float}{1}.FLOAT_IDENTIFICATION.float_serial_number)]; + figure_filename = [p_config.PLOT_ARRIVAL_OF_PROFILES.figure_filename ... + num2str(p_nemo_data{o_float}{1}.FLOAT_IDENTIFICATION.float_serial_number)]; + + print(figure_id, [directory image_filename], image_type); + + if (p_config.PLOT_ARRIVAL_OF_PROFILES.save_figure) + set(gcf,'CreateFcn','set(gcf,''Visible'',''on'')'); + saveas(figure_id, ... + [directory_fig figure_filename]); + end + + close(figure_id); + +end + +boolean = true; + + + function current_plot = set_x_axis(current_plot) + + % get min and max datevec + min_datenum = min(datenum(best_guess_datevec)); + max_datenum = max(datenum(best_guess_datevec)); + if (min_datenum == max_datenum) + max_datenum = max_datenum + 1; + end + diff_datenum = max_datenum - min_datenum; + if (diff_datenum > 365) + x_tick = [min_datenum:365:max_datenum]; + elseif (diff_datenum > 30) + x_tick = [min_datenum:30:max_datenum]; + elseif (diff_datenum > 5) + x_tick = [min_datenum:5:max_datenum]; + else + x_tick = [min_datenum:1:max_datenum]; + end + if (x_tick(end) < max_datenum) + x_tick = [ x_tick max_datenum ]; + end + x_tick_labels = cellstr(datestr(x_tick, 'yyyy-mm-dd')); + x_tick_labels_top = cellstr(datestr(x_tick, 'yyyy-mm-dd')); + + if (~isempty(x_tick_labels)) + x_tick_labels{end} = ''; + end + + set(current_plot, 'XLimMode', 'manual', ... + 'XLim', [min_datenum, max_datenum], ... + 'XTick', x_tick, ... + 'XTickLabel', x_tick_labels); + + end + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+plot/arrival_of_profiles_overview.m b/lib/vendor/nemo2profilelib/+nemo/+plot/arrival_of_profiles_overview.m new file mode 100644 index 0000000..54f6cf9 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+plot/arrival_of_profiles_overview.m @@ -0,0 +1,232 @@ +function [ boolean ] = arrival_of_profiles_overview( p_nemo_data, p_config ) +%ARRIVAL_OF_PROFILES Plots details about iridium data. +% +% Returns: +% boolean: False if something went wrong during the plotting process. +% +% Parameters: +% p_nemo_data: Struct, containing all float data (load with nemo.load.data()). +% p_config: Global configuration file. Load using nemo.load.config(). + +%% Initialize return variable +boolean = false; + +%% Parameter check +parameter_error = false; + +if (~iscell(p_nemo_data) || ~isstruct(p_config)) + parameter_error = true; +end + +if (parameter_error) % display error message + disp([ mfilename ': Wrong parameter! Please check your input parameters carefully!']); + return; +else % do some cleanup + clear parameter_error; +end + +%% Setup directories +directory = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_ARRIVAL_OF_PROFILES_OVERVIEW.storage_directory_image); +directory_fig = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_ARRIVAL_OF_PROFILES_OVERVIEW.storage_directory_fig); +image_type = p_config.PLOT_ARRIVAL_OF_PROFILES_OVERVIEW.image_type; +if ~exist(directory, 'dir') + system(['mkdir ' directory]); +end +if ~exist(directory_fig, 'dir') + mkdir(directory_fig); +end + + +%% Collect required data + +first_deployment_datenum = NaN; % contains the earliest deployment date of all floats +latest_profile_datenum = NaN; % contains the latest profile that has been transmitted + +best_guess_datenum = {}; +transmitted_profiles = {}; +iridium_mean_times = {}; +float_serials = []; + +float_count = length(p_nemo_data); + +for o_float = 1:float_count + + % get serial number + float_serials = [float_serials; p_nemo_data{o_float}{1}.FLOAT_IDENTIFICATION.float_serial_number]; + + % get the deployment dates + current_deployment_datenum = datenum( ... + [ ... + p_nemo_data{o_float}{1}.DEPLOYMENT_INFO.deployment_date ... + p_nemo_data{o_float}{1}.DEPLOYMENT_INFO.deployment_time ... + 0 ... + ] ... + ); + if isnan(first_deployment_datenum) || (current_deployment_datenum < first_deployment_datenum) + first_deployment_datenum = current_deployment_datenum; + end + current_latest_profile_datenum = datenum(p_nemo_data{o_float}{end}.SECOND_ORDER_INFORMATION.best_guess_datevec); + % get latest profile date + if ~all(isnan(current_latest_profile_datenum)) && (isnan(latest_profile_datenum) || (current_latest_profile_datenum > latest_profile_datenum)) + latest_profile_datenum = current_latest_profile_datenum; + end + + % check if gps data available + gps_datetime = nemo.float.collect(p_nemo_data{o_float}, ... + 'SURFACE_GPS_DATA', ... + '' ... + ); + if isempty(gps_datetime) + continue; + end + + + % collect required data + + best_guess_datevec = nemo.float.collect(p_nemo_data{o_float}, ... + 'SECOND_ORDER_INFORMATION', ... + 'best_guess_datevec' ... + ); + best_guess_datenum{o_float} = datenum(best_guess_datevec); + + % get numbers of the profiles that have been received + transmitted_profiles{o_float} = nemo.float.collect(p_nemo_data{o_float}, ... + 'PROFILE_TECHNICAL_DATA', ... + 'xmit_profile_number' ... + ); + + % average time when the profile has been transmitted + iridium_mean_times{o_float} = nemo.float.collect( ... + p_nemo_data{o_float}, ... + 'IRIDIUM_DATA', ... + '' ... + ); + % extract only datevec from it + iridium_mean_times{o_float} = datetime(iridium_mean_times{o_float}(:, 4:9)); + + +end + +% get min and max datevec +if (first_deployment_datenum == latest_profile_datenum) + latest_profile_datenum = latest_profile_datenum + 1; +end +diff_datenum = latest_profile_datenum - first_deployment_datenum; +if (diff_datenum > 365) + x_tick = [first_deployment_datenum:365:latest_profile_datenum]; +elseif (diff_datenum > 30) + x_tick = [first_deployment_datenum:30:latest_profile_datenum]; +elseif (diff_datenum > 5) + x_tick = [first_deployment_datenum:5:latest_profile_datenum]; +else + x_tick = [first_deployment_datenum:1:latest_profile_datenum]; +end +x_tick = [ x_tick(1:end-1) latest_profile_datenum ]; + +% get plot details +width = p_config.PLOT_ARRIVAL_OF_PROFILES_OVERVIEW.image_width; +height = p_config.PLOT_ARRIVAL_OF_PROFILES_OVERVIEW.image_height; +paperunit = p_config.PLOT_ARRIVAL_OF_PROFILES_OVERVIEW.image_paperunit; + +% plot collected data + +figure_id = figure('Visible', 'off'); % create figure +% initialize figure +set(figure_id,'InvertHardcopy','on'); +set(figure_id,'PaperUnits', paperunit); +papersize = get(figure_id, 'PaperSize'); +left = (papersize(1)- width)/2; +bottom = (papersize(2)- height)/2; +myfiguresize = [left, bottom, width, height]; +set(figure_id,'PaperPosition', myfiguresize); +clear height width left bottom papersize myfiguresize + +%set(gcf, 'Position', get(0,'ScreenSize'), 'color', [1 1 1]); +sub = {}; + +sub{1} = subplot(2,1,1); +hold on +% set(gca, 'XLimMode', 'manual', 'XLim', [min_datenum, max_datenum]); +% set_x_axis(); +for o_float = 1:float_count + y = repmat(float_serials(o_float), length(transmitted_profiles{o_float}), 1); + plot(transmitted_profiles{o_float}, y,'marker','.','color',[0 0 0],'linestyle','none','linewidth',1,'markersize',20); + clear y +end + +t = title([ 'Arrival of profiles overview @ ' ... + datestr(datetime('now', 'Format', 'dd-MMM-yyyy HH:mm:SS')) ... + ]); +set(t, 'FontSize', 18); +clear t +ylabel('Float serial number'); +xlabel('Profile number'); +grid on +box on +hold off + + + +sub{2} = subplot(2,1,2); +hold on +% set(gca, 'XLimMode', 'manual', 'XLim', [min_datenum, max_datenum]); +% set_x_axis(); +for o_float = 1:float_count + + disp([' Processing arrival overview information of float number ',num2str(p_nemo_data{o_float}{1}.FLOAT_IDENTIFICATION.float_serial_number)]) % OB + + y = repmat(float_serials(o_float), length(best_guess_datenum{o_float}), 1); + plot(best_guess_datenum{o_float}, y,'marker','.','color',[0.5 0.5 0.5],'linestyle','none','linewidth',1,'markersize',20); + y = repmat(float_serials(o_float), length(transmitted_profiles{o_float}), 1); + + % OB START: for float 173 iridium_mean_times is of type datetime, which does not + % comply with plot command + % Old Code: + % plot(iridium_mean_times{o_float}, y,'marker','.','color',[0 0 0],'linestyle','none','linewidth',1,'markersize',20); + % New Code + if isa(iridium_mean_times{o_float},'double') + plot(iridium_mean_times{o_float}, y,'marker','.','color',[0 0 0],'linestyle','none','linewidth',1,'markersize',20); + elseif isa(iridium_mean_times{o_float},'datetime') + plot(datenum(iridium_mean_times{o_float}), y,'marker','.','color',[0 0 0],'linestyle','none','linewidth',1,'markersize',20); + end + % OB END + + clear y +end + +ylabel('Float serial number'); +xlabel('Iridium timestamp'); +grid on +box on +hold off + +% set_x_axis(sub{2}); + + +% store plot + +image_filename = p_config.PLOT_ARRIVAL_OF_PROFILES_OVERVIEW.image_filename; +figure_filename = p_config.PLOT_ARRIVAL_OF_PROFILES_OVERVIEW.figure_filename; + +print(figure_id, [directory image_filename], image_type); + + if (p_config.PLOT_ARRIVAL_OF_PROFILES_OVERVIEW.save_figure) + saveas(figure_id, ... + [directory_fig figure_filename]); + end + +close(figure_id); + +boolean = true; + + + function current_plot = set_x_axis(current_plot) + set(current_plot, 'XLimMode', 'manual', ... + 'XLim', [first_deployment_datenum, latest_profile_datenum], ... + 'XTick', x_tick); + datetick(current_plot, 'x', 'yyyy-mm-dd', 'keepticks'); + + end + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+plot/ascent.m b/lib/vendor/nemo2profilelib/+nemo/+plot/ascent.m new file mode 100644 index 0000000..21cdcbc --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+plot/ascent.m @@ -0,0 +1,207 @@ +function [ boolean ] = ascent( p_nemo_data, p_config ) +%ASCENT Generates plots showing the ascent data of the float. +% Generates a plot for every float in p_nemo_data containing the depth on +% recorded time of day, profile recording time on profile number as well +% as 0 to 50m depth on profile number. +% +% Parameters: +% p_nemo_data: The float data. +% p_config: The nemo configuration. +% +% Returns: +% boolean: True if successful. + +%% Initialize return variable +boolean = false; + +%% Parameter check %TODO + +%% Get config parameter +% get plot details +width = p_config.PLOT_ASCENT.image_width; +height = p_config.PLOT_ASCENT.image_height; +paperunit = p_config.PLOT_ASCENT.image_paperunit; +image_type = p_config.PLOT_ASCENT.image_type; +directory = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_ASCENT.storage_directory_image); +directory_fig = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_ASCENT.storage_directory_fig); +ylabel_fontsize = p_config.PLOT_ASCENT.ylabel_fontsize; +xlabel_fontsize = p_config.PLOT_ASCENT.ylabel_fontsize; +axis_fontsize = p_config.PLOT_ASCENT.axis_fontsize; +title_fontsize = p_config.PLOT_ASCENT.title_fontsize; + +if (~isdir(directory)) + mkdir(directory); +end +if ~exist(directory_fig, 'dir') + mkdir(directory_fig); +end + +%% Collect required data + +switch (class(p_nemo_data{1})) + case 'struct' % only one float is to be processed + boolean = plot_float(p_nemo_data); + case 'cell' % more than one float has to be processed + boolean = all(cellfun(@plot_float, p_nemo_data)); + otherwise + disp([mfilename ': Class ' class(p_nemo_data{1}) ' not supported for plotting!']); +end + + function boolean = plot_float(p_float) + boolean = false; + profile_data = nemo.float.collect(p_float, 'PROFILE_DATA', ''); + profile_best_guess_datevec = nemo.float.collect(p_float, ... + 'SECOND_ORDER_INFORMATION', ... + 'best_guess_datevec'); +% profiles_best_guess_datenum = datenum([1970, 1, 1, profiles_best_guess_datevec(:, 4:6)]); + if (isempty(profile_data)) + boolean = true; + return + end + + % initialize figure + figure_id = figure('Visible', 'Off'); + set(figure_id,'InvertHardcopy','on'); + set(figure_id,'PaperUnits', paperunit, 'Position', [ 0 0 width height]); + papersize = get(figure_id, 'PaperSize'); + left = (papersize(1) - width)/2; + bottom = (papersize(2) - height)/2; + myfiguresize = [left, bottom, width, height]; + set(figure_id,'PaperPosition', myfiguresize); + + % Depth on time plot + sub(1) = subplot(2, 2, 1); + hold on + set(gca, 'YLimMode', 'manual', 'YLim', [0 2000], ... + 'YTick', [0:200:2000], 'YDir', 'reverse'); + xlimits = [datenum(0, 0, 0, 0, 0, 0) datenum(0, 0, 0, 24, 0, 0)]; + set(gca, 'XLim', xlimits, 'XTick', linspace(xlimits(1), xlimits(2), 7)); + for i = 1:length(profile_data) + plot_x = mod(datenum([ ... + repmat(1970, length(profile_data{i}), 1) ... + , ones(length(profile_data{i}), 1) ... + , ones(length(profile_data{i}), 1) ... + , profile_data{i}(:, 2:4)]), 1); + plot_y = profile_data{i}(:, 7); + plot_c = [0 0 1] * i/length(profile_data); + scatter(plot_x, plot_y, 30, plot_c, 'filled'); + end + datetick('x','HH:MM', 'keepticks', 'keeplimits'); + a = get(gca, 'YTickLabel'); + set(gca, 'YTickLabel', a, 'fontsize', axis_fontsize) + a = get(gca, 'XTickLabel'); + set(gca, 'XTickLabel', a, 'fontsize', axis_fontsize) + ylabel('Depth [m]', 'FontSize', ylabel_fontsize); + xlabel('Time of day', 'FontSize', xlabel_fontsize); + box on + grid on + hold off + + % Profile data time on profile number + sub(2) = subplot(2, 2, 3); + hold on + ylimits = [datenum(0, 0, 0, 0, 0, 0) datenum(0, 0, 0, 24, 0, 0)]; + set(gca, 'YLimMode', 'manual', 'YLim', ylimits, 'YTick', linspace(ylimits(1), ylimits(2), 7)); + % get required information about x-axis limits and ticks + profile_best_guess_datenum = datenum(profile_best_guess_datevec); +% start_datenum = min(profile_best_guess_datenum); +% end_datenum = max(profile_best_guess_datenum); +% x_diff = max(profile_best_guess_datenum) ... +% - min(profile_best_guess_datenum); +% if (start_datenum ~= end_datenum) +% set(gca, ... +% 'XLimMode', 'manual', ... +% 'XLim', [start_datenum, end_datenum]); +% end +% if (x_diff < 30) +% set(gca, 'XTick', [start_datenum:5:end_datenum]); +% elseif (x_diff < 365) +% set(gca, 'XTick', [start_datenum:30:end_datenum]); +% else +% set(gca, 'XTick', [start_datenum:365:end_datenum]); +% end + for i = 1:length(profile_data) + plot_x = ones(1, length(profile_data{i}(:, 1))) * i; + plot_y = datenum([zeros(length(profile_data{i}(:, 1)), 1), ... + zeros(length(profile_data{i}(:, 1)), 1), ... + zeros(length(profile_data{i}(:, 1)), 1), ... + profile_data{i}(:,2:4)]); + scatter(plot_x, plot_y, 30, [0 0 0], 'filled'); + end + datetick('y','HH:MM', 'keepticks', 'keeplimits'); +% datetick('x','yyyy-mm-dd', 'keepticks', 'keeplimits'); + set(gca, 'fontsize', axis_fontsize) + ylabel('Profile recording time [HH:MM]', 'FontSize', ylabel_fontsize); + xlabel('Profile number', 'FontSize', xlabel_fontsize); + box on + grid on + hold off + + % Zoom on upper 50m water column + sub(3) = subplot(1, 2, 2); + hold on + set(gca, 'YLimMode', 'manual', 'YDir', 'reverse', 'YLim', [0 50], ... + 'XLimMode', 'auto'); + set(gca, 'fontsize', axis_fontsize) + ylabel('Depth [m]', 'FontSize', ylabel_fontsize); + xlabel('Profile number', 'FontSize', xlabel_fontsize); + % get required information about x-axis limits and ticks + for i = 1:length(profile_data) + temperature = profile_data{i}(:, 5); + pressure = profile_data{i}(:, 7); + % erase all invalid values + valid_points = (pressure >= 0) & (pressure <= 50); + temperature = temperature(valid_points); + pressure = pressure(valid_points); + x_axis = ones(size(pressure, 1), 1) * i; + scatter(x_axis, pressure, 45, temperature, 'filled'); + end + c = colorbar; + cmocean('thermal'); + %colormap(gca, 'jet'); + %caxis([-5, 15]); + c.Label.String = 'Temperature [°C]'; + clear c + + + % create main title + if (p_float{1}.SECOND_ORDER_INFORMATION.best_guess_position_lat > 0) + deployment_string = ' (Arctic)'; + else + deployment_string = ' (Antarctica)'; + end + t = title([ 'Float ' ... + num2str(p_float{1}.FLOAT_IDENTIFICATION.float_serial_number) ... + ' @ ' ... + datestr(datetime('now', 'Format', 'dd-MMM-yyyy HH:mm:SS')) ... + deployment_string ... + ]); + set(t, 'FontSize', title_fontsize); + clear t + + box on + grid on + hold off + + set(gcf, 'Renderer', 'painters'); + print(figure_id, ... + [directory p_config.PLOT_ASCENT.image_filename ... + num2str(p_float{1}.FLOAT_IDENTIFICATION.float_serial_number)] ... + , image_type); + + if (p_config.PLOT_ASCENT.save_figure) + set(gcf,'CreateFcn','set(gcf,''Visible'',''on'')'); + saveas(figure_id, ... + [directory_fig ... + p_config.PLOT_ASCENT.figure_filename ... + num2str(p_float{1}.FLOAT_IDENTIFICATION.float_serial_number)]); + end + + close(figure_id); + clear figure_id f + boolean = true; + + end + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+plot/categorized_timings.m b/lib/vendor/nemo2profilelib/+nemo/+plot/categorized_timings.m new file mode 100644 index 0000000..bef2ee2 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+plot/categorized_timings.m @@ -0,0 +1,327 @@ +function [ boolean ] = categorized_timings( p_nemo_data, p_config ) +%TIMING_INFORMATION Plots clock details. +% +% Returns: +% boolean: False if something went wrong during the plotting process. +% +% Parameters: +% p_nemo_data: Struct, containing all float data (load with nemo.load.data()). +% p_config: Global configuration file. Load using nemo.load.config(). + +%% Initialize return variable +boolean = false; + +%% Parameter check +parameter_error = false; + +if (~iscell(p_nemo_data) || ~isstruct(p_config)) + parameter_error = true; +end + +if (parameter_error) % display error message + disp([ mfilename ': Wrong parameter! Please check your input parameters carefully!']); + return; +else % do some cleanup + clear parameter_error; +end + +%% Setup directories +directory = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_CATEGORIZED_TIMINGS.storage_directory_image); +directory_fig = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_CATEGORIZED_TIMINGS.storage_directory_fig); +image_type = p_config.PLOT_CATEGORIZED_TIMINGS.image_type; +if ~exist(directory, 'dir') + system(['mkdir ' directory]); +end +if ~exist(directory_fig, 'dir') + mkdir(directory_fig); +end + +%% Process every float + +float_count = length(p_nemo_data); + +for o_float = 1:float_count + + current_float = p_nemo_data{o_float}; + + start_date = current_float{end}.OVERALL_MISSION_INFORMATION.start_date; + start_time = current_float{end}.OVERALL_MISSION_INFORMATION.start_time; + xmit_ascent_start_time = nemo.float.collect(current_float, 'PROFILE_TECHNICAL_DATA', 'xmit_ascent_start_time'); + xmit_ascent_end_time = nemo.float.collect(current_float, 'PROFILE_TECHNICAL_DATA', 'xmit_ascent_end_time'); + xmit_descent_start_time = nemo.float.collect(current_float, 'PROFILE_TECHNICAL_DATA', 'xmit_descent_start_time'); + xmit_parking_start_time = nemo.float.collect(current_float, 'PROFILE_TECHNICAL_DATA', 'xmit_parking_start_time'); + xmit_upcast_start_time = nemo.float.collect(current_float, 'PROFILE_TECHNICAL_DATA', 'xmit_upcast_start_time'); + xmit_surface_start_time = nemo.float.collect(current_float, 'PROFILE_TECHNICAL_DATA', 'xmit_surface_start_time'); + xmit_profile_number = nemo.float.collect(current_float, 'PROFILE_TECHNICAL_DATA', 'xmit_profile_number'); + + combined_xmit_times = [ ... + xmit_descent_start_time, ... + xmit_parking_start_time, ... + xmit_upcast_start_time, ... + xmit_ascent_start_time, ... + xmit_ascent_end_time, ... + xmit_surface_start_time ... + ]; + + combined_diff_times = [ ... +% NaN(length(xmit_descent_start_time), 1), ... + (xmit_descent_start_time - xmit_parking_start_time), ... + (xmit_parking_start_time - xmit_upcast_start_time), ... + (xmit_upcast_start_time - xmit_ascent_start_time), ... + (xmit_ascent_start_time - xmit_ascent_end_time), ... + (xmit_ascent_end_time - xmit_surface_start_time), ... + [NaN; (xmit_surface_start_time(1:end -1) - xmit_descent_start_time(2:end))] + ]; + + [p_row, ~] = size(combined_xmit_times); + tmp_xmit_times = []; + tmp_diff_times = []; + for o_row = 1:p_row + tmp_xmit_times = [ tmp_xmit_times; combined_xmit_times(o_row, :)']; + tmp_diff_times = [ tmp_diff_times; combined_diff_times(o_row, :)']; + end + % categorize them + combined_xmit_times = [repmat([3:6, 2, 1]', length(tmp_xmit_times)/6, 1), tmp_xmit_times]; + combined_diff_times = [repmat([3:6, 2, 1]', length(tmp_diff_times)/6, 1), tmp_diff_times]; + clear tmp_xmit_times tmp_diff_times + + + % get plot details + width = p_config.PLOT_CATEGORIZED_TIMINGS.image_width; + height = p_config.PLOT_CATEGORIZED_TIMINGS.image_height; + paperunit = p_config.PLOT_CATEGORIZED_TIMINGS.image_paperunit; + + % plot collected data + + figure_id = figure('Visible', 'off'); % create figure + % initialize figure + set(figure_id,'InvertHardcopy','on'); + set(figure_id,'PaperUnits', paperunit); + papersize = get(figure_id, 'PaperSize'); + left = (papersize(1)- width)/2; + bottom = (papersize(2)- height)/2; + myfiguresize = [left, bottom, width, height]; + set(figure_id,'PaperPosition', myfiguresize); + + clear height width left bottom papersize myfiguresize + + %set(gcf, 'Position', get(0,'ScreenSize'), 'color', [1 1 1]); + sub = {}; + + sub{1} = subplot(2,1,1); + hold on + + + plot(combined_xmit_times(:, 2), combined_xmit_times(:, 1), ... + 'color', [0 0 0], ... + 'linestyle', '-', ... + 'linewidth', 1 ... + ); + scatter(combined_xmit_times(combined_xmit_times(:, 1) == 3, 2), combined_xmit_times(combined_xmit_times(:, 1) == 3, 1), ... + 'marker', 'o', ... + 'MarkerFaceColor', [1 0 0], ... + 'MarkerEdgeColor', [1 0 0] ... + ); + scatter(combined_xmit_times(combined_xmit_times(:, 1) == 4, 2), combined_xmit_times(combined_xmit_times(:, 1) == 4, 1), ... + 'marker', 'o', ... + 'MarkerFaceColor', [0 1 0], ... + 'MarkerEdgeColor', [0 1 0] ... + ); + scatter(combined_xmit_times(combined_xmit_times(:, 1) == 5, 2), combined_xmit_times(combined_xmit_times(:, 1) == 5, 1), ... + 'marker', 'o', ... + 'MarkerFaceColor', [0 0 1], ... + 'MarkerEdgeColor', [0 0 1] ... + ); + scatter(combined_xmit_times(combined_xmit_times(:, 1) == 6, 2), combined_xmit_times(combined_xmit_times(:, 1) == 6, 1), ... + 'marker', 'o', ... + 'MarkerFaceColor', [1 1 0], ... + 'MarkerEdgeColor', [1 1 0] ... + ); + scatter(combined_xmit_times(combined_xmit_times(:, 1) == 2, 2), combined_xmit_times(combined_xmit_times(:, 1) == 2, 1), ... + 'marker', 'o', ... + 'MarkerFaceColor', [0 1 1], ... + 'MarkerEdgeColor', [0 1 1] ... + ); + scatter(combined_xmit_times(combined_xmit_times(:, 1) == 1, 2), combined_xmit_times(combined_xmit_times(:, 1) == 1, 1), ... + 'marker', 'o', ... + 'MarkerFaceColor', [1 0 1], ... + 'MarkerEdgeColor', [1 0 1] ... + ); + + ylabel('Category'); + xlabel('Seconds since start'); + axis ij; +% legend('SurfaceStart-GPS', 'SurfaceStart-IRIDIUM', 'AscentStart-RTC2000'); + leg1 = legend('TimesConnected', 'DescentStart', 'ParkingStart', 'UpcastStart', 'AscentStart', 'AscentEnd', 'SurfaceStart'); + set(leg1, 'Location', 'eastoutside', 'Orientation', 'vertical'); + clear leg1 + t = title(... + [ 'Float ' ... + num2str(p_nemo_data{o_float}{1}.FLOAT_IDENTIFICATION.float_serial_number) ... + ' @ ' ... + datestr(datetime('now', 'Format', 'dd-MMM-yyyy HH:mm:SS')) ... + ]); + set(t, 'FontSize', 18); + clear t + grid on + box on + hold off + + sub{2} = subplot(2,1,2); + hold on + + + plot(combined_xmit_times(:, 2), combined_diff_times(:, 2), ... + 'color', [0 0 0], ... + 'linestyle', '-', ... + 'linewidth', 1 ... + ); + scatter(combined_xmit_times(combined_xmit_times(:, 1) == 3, 2), combined_diff_times(combined_diff_times(:, 1) == 3, 2), ... + 'marker', 'o', ... + 'MarkerEdgeColor', [1 0 0] ... + ); + scatter(combined_xmit_times(combined_xmit_times(:, 1) == 4, 2), combined_diff_times(combined_diff_times(:, 1) == 4, 2), ... + 'marker', 'o', ... + 'MarkerEdgeColor', [0 1 0] ... + ); + scatter(combined_xmit_times(combined_xmit_times(:, 1) == 5, 2), combined_diff_times(combined_diff_times(:, 1) == 5, 2), ... + 'marker', 'o', ... + 'MarkerEdgeColor', [0 0 1] ... + ); + scatter(combined_xmit_times(combined_xmit_times(:, 1) == 6, 2), combined_diff_times(combined_diff_times(:, 1) == 6, 2), ... + 'marker', 'o', ... + 'MarkerEdgeColor', [1 1 0] ... + ); + scatter(combined_xmit_times(combined_xmit_times(:, 1) == 2, 2), combined_diff_times(combined_diff_times(:, 1) == 2, 2), ... + 'marker', 'o', ... + 'MarkerEdgeColor', [0 1 1] ... + ); + scatter(combined_xmit_times(combined_xmit_times(:, 1) == 1, 2), combined_diff_times(combined_diff_times(:, 1) == 1, 2), ... + 'marker', 'o', ... + 'MarkerEdgeColor', [1 0 1] ... + ); + + + % plot all times > 0 and hide them from legend + selector = combined_diff_times(:, 1) == 3 & combined_diff_times(:, 2) > 0; + scat1 = scatter(combined_xmit_times(selector, 2), combined_diff_times(selector, 2), ... + 'marker', 'o', ... + 'MarkerFaceColor', [1 0 0], ... + 'MarkerEdgeColor', [1 0 0] ... + ); + hasbehavior(scat1, 'legend', false); + selector = combined_diff_times(:, 1) == 4 & combined_diff_times(:, 2) > 0; + scat1 = scatter(combined_xmit_times(selector, 2), combined_diff_times(selector, 2), ... + 'marker', 'o', ... + 'MarkerFaceColor', [0 1 0], ... + 'MarkerEdgeColor', [0 1 0] ... + ); + hasbehavior(scat1, 'legend', false); + selector = combined_diff_times(:, 1) == 5 & combined_diff_times(:, 2) > 0; + scat1 = scatter(combined_xmit_times(selector, 2), combined_diff_times(selector, 2), ... + 'marker', 'o', ... + 'MarkerFaceColor', [0 0 1], ... + 'MarkerEdgeColor', [0 0 1] ... + ); + hasbehavior(scat1, 'legend', false); + selector = combined_diff_times(:, 1) == 6 & combined_diff_times(:, 2) > 0; + scat1 = scatter(combined_xmit_times(selector, 2), combined_diff_times(selector, 2), ... + 'marker', 'o', ... + 'MarkerFaceColor', [1 1 0], ... + 'MarkerEdgeColor', [1 1 0] ... + ); + hasbehavior(scat1, 'legend', false); + selector = combined_diff_times(:, 1) == 2 & combined_diff_times(:, 2) > 0; + scat1 = scatter(combined_xmit_times(selector, 2), combined_diff_times(selector, 2), ... + 'marker', 'o', ... + 'MarkerFaceColor', [0 1 1], ... + 'MarkerEdgeColor', [0 1 1] ... + ); + hasbehavior(scat1, 'legend', false); + selector = combined_diff_times(:, 1) == 1 & combined_diff_times(:, 2) > 0; + scat1 = scatter(combined_xmit_times(selector, 2), combined_diff_times(selector, 2), ... + 'marker', 'o', ... + 'MarkerFaceColor', [1 0 1], ... + 'MarkerEdgeColor', [1 0 1] ... + ); + hasbehavior(scat1, 'legend', false); + clear scat1 + + + ylabel('Time difference [sec]'); + xlabel('Seconds since start'); +% axis ij; +% legend('SurfaceStart-GPS', 'SurfaceStart-IRIDIUM', 'AscentStart-RTC2000'); + leg1 = legend('DiffsConnected', 'DescentStart-ParkingStart', 'ParkingStart-UpcastStart', 'UpcastStart-AscentStart', 'AscentStart-AscentEnd', 'AscentEnd-SurfaceStart', 'SurfaceStart-DescentStart'); + set(leg1, 'Location', 'eastoutside', 'Orientation', 'vertical'); + clear leg1 + grid on + box on + hold off + + sub1_pos = get(sub{1}, 'Position'); + sub2_pos = get(sub{2}, 'Position'); +% leg1_pos = get(leg1, 'Position'); + set(sub{1}, 'Position', [sub1_pos(1:2) 0.6652 sub1_pos(4)]); + set(sub{2}, 'Position', [sub2_pos(1:2) 0.6652 sub2_pos(4)]); + + % store plot + + image_filename = [p_config.PLOT_CATEGORIZED_TIMINGS.image_filename ... + num2str(p_nemo_data{o_float}{1}.FLOAT_IDENTIFICATION.float_serial_number)]; + figure_filename = [p_config.PLOT_CATEGORIZED_TIMINGS.figure_filename ... + num2str(p_nemo_data{o_float}{1}.FLOAT_IDENTIFICATION.float_serial_number)]; + + if (p_config.PLOT_CATEGORIZED_TIMINGS.save_figure) + set(gcf,'CreateFcn','set(gcf,''Visible'',''on'')'); + saveas(figure_id, ... + [directory_fig figure_filename]); + end + + + print(figure_id, [directory image_filename], image_type); + + close(figure_id); + +end + +boolean = true; + + + function current_plot = set_x_axis(current_plot) + + best_guess_datevec = nemo.float.collect(current_float, 'SECOND_ORDER_INFORMATION', 'best_guess_datevec'); + % get min and max datevec + min_datenum = min(datenum(best_guess_datevec)); + max_datenum = max(datenum(best_guess_datevec)); + if (min_datenum == max_datenum) + max_datenum = max_datenum + 1; + end + diff_datenum = max_datenum - min_datenum; + if (diff_datenum > 365) + x_tick = [min_datenum:365:max_datenum]; + elseif (diff_datenum > 30) + x_tick = [min_datenum:30:max_datenum]; + elseif (diff_datenum > 5) + x_tick = [min_datenum:5:max_datenum]; + else + x_tick = [min_datenum:1:max_datenum]; + end + if (x_tick(end) < max_datenum) + x_tick = [ x_tick max_datenum ]; + end + x_tick_labels = cellstr(datestr(x_tick, 'yyyy-mm-dd')); + + if (~isempty(x_tick_labels)) + x_tick_labels{end} = ''; + end + + set(current_plot, 'XLimMode', 'manual', ... + 'XLim', [min_datenum, max_datenum], ... + 'XTick', x_tick, ... + 'XTickLabel', x_tick_labels); + + end + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+plot/float_to_template_south.m b/lib/vendor/nemo2profilelib/+nemo/+plot/float_to_template_south.m new file mode 100644 index 0000000..dc66496 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+plot/float_to_template_south.m @@ -0,0 +1,137 @@ +function [ modification_history ] = float_to_template_south( p_nemo_float, p_template, p_config, p_single_float ) +%FLOAT_TO_TEMPLATE_SOUTH Plots the float positions to the given template. +% +% Parameters: +% p_nemo_float: The float data, can be more than one. +% p_template: The template generated by +% nemo.plots.templates.create_south function +% p_config: The nemo configuration. +% p_single_float: If true, just one float is being plotted to the +% template at a time. If false, all floats will +% be plotted into the plot. +% +% Returns: +% modification_history: The modifications made by this function to the +% template. + +% make template the current figure +set(0, 'currentfigure', p_template); + +% get style of plot +plot_style = p_config.PLOT_STYLE_SOUTH; + +if p_single_float + show_profile_numbers = p_config.PLOT_SINGLE_FLOAT_SOUTH.show_profile_numbers; +else + show_profile_numbers = p_config.PLOT_OVERVIEW_SOUTH.show_profile_numbers; +end + +modification_history = []; + +profile_count = length(p_nemo_float); +bgp_lat = nan(profile_count, 1); +bgp_lon = nan(profile_count, 1); + +% store the first position manually, because then its possible to save +% the source of the previous index +% best guess positions (abbr. bgp) +bgp_lat(1) = p_nemo_float{1}.SECOND_ORDER_INFORMATION.best_guess_position_lat; +bgp_lon(1) = p_nemo_float{1}.SECOND_ORDER_INFORMATION.best_guess_position_lon; +bgp_src{1} = p_nemo_float{1}.SECOND_ORDER_INFORMATION.best_guess_position_src; +profile_numbers(1, 1) = p_nemo_float{1}.PROFILE_TECHNICAL_DATA.xmit_profile_number; +measured_ranges = []; % ranges of measured positions will be stored in here + +if (strcmp(bgp_src{1}, 'gps') || strcmp(bgp_src{1}, 'iridium')) + % it is a measured position, so save the index! + measured_start_index = 1; +else + measured_start_index = 0; % because zero is not a valid index +end + +for ii=2:profile_count + + % best guess positions (abbr. bgp) + bgp_lat(ii) = p_nemo_float{ii}.SECOND_ORDER_INFORMATION.best_guess_position_lat; + bgp_lon(ii) = p_nemo_float{ii}.SECOND_ORDER_INFORMATION.best_guess_position_lon; + bgp_src{ii} = p_nemo_float{ii}.SECOND_ORDER_INFORMATION.best_guess_position_src; + profile_numbers(ii, 1) = p_nemo_float{ii}.PROFILE_TECHNICAL_DATA.xmit_profile_number; + + % get all pairs of measured positions + if (measured_start_index == 0) && (strcmp(bgp_src{ii}, 'gps') || strcmp(bgp_src{ii}, 'iridium')) + % no starting index is set, but a measured position has been + % found + measured_start_index = ii; + elseif (measured_start_index > 0) && ((~strcmp(bgp_src{ii}, 'gps') && ~strcmp(bgp_src{ii}, 'iridium'))) + % starting index valid, but a not measured position has been + % found, so store endpoint + measured_ranges = [measured_ranges; [measured_start_index ii-1]]; + measured_start_index = 0; % set invalid start index + elseif ((measured_start_index > 0) && (ii == profile_count)) + % the last position is also valid + measured_ranges = [measured_ranges; [measured_start_index ii]]; + measured_start_index = 0; % set invalid start index + end + +end + +% draw all bgp positions first +modification_history(end+1) = plotm(bgp_lat,bgp_lon,'marker',plot_style.gps_marker,'markersize',plot_style.gps_markersize,'color',plot_style.gps_color_interpolated, 'linestyle', '-'); % GPS positions + +% get deployment position +dep_lat = p_nemo_float{1}.DEPLOYMENT_INFO.deployment_position(1); +dep_lon = p_nemo_float{1}.DEPLOYMENT_INFO.deployment_position(2); + +if (~isempty(measured_ranges)) && (measured_ranges(1, 1) == 1) % first position is a measured position + % plot a black line from deployment position to first profile + % position + modification_history(end+1) = plotm([dep_lat; bgp_lat(1)], [dep_lon; bgp_lon(1)],'marker',plot_style.gps_marker,'markersize',plot_style.gps_markersize,'color',plot_style.gps_color, 'linestyle', '-'); % GPS positions +else + % plot a interpolated line + modification_history(end+1) = plotm([dep_lat; bgp_lat(1)], [dep_lon; bgp_lon(1)],'marker',plot_style.gps_marker,'markersize',plot_style.gps_markersize,'color',plot_style.gps_color_interpolated, 'linestyle', '-'); +end + +% draw all measured (iridium or gps) positions above it +measured_length = size(measured_ranges, 1); +for o_measured_index = 1:measured_length + modification_history(end+1) = ... + plotm(bgp_lat(measured_ranges(o_measured_index, 1):measured_ranges(o_measured_index, 2)), ... + bgp_lon(measured_ranges(o_measured_index, 1):measured_ranges(o_measured_index, 2)), ... + 'marker',plot_style.gps_marker,... + 'markersize',plot_style.gps_markersize, ... + 'color', plot_style.gps_color, 'linestyle', '-'); % GPS positions +end + +% plot deployment position +modification_history(end+1) = plotm(dep_lat,dep_lon,150,'marker',plot_style.deploy_marker,'markersize',plot_style.deploy_markersize,'color',plot_style.deploy_color,'linestyle','none'); % plot deplyoment position as crossed square + +% plot last transmittion +modification_history(end+1) = plotm(bgp_lat(end),bgp_lon(end),150,'marker',plot_style.gps_marker,'markersize',plot_style.gps_markersize,'color',plot_style.last_transmittion_color,'linestyle','none'); % plot final gps position +% plot serial number of float to the last transmitted profile +text_serial_number = textm(bgp_lat(end)+0.2,bgp_lon(end)+0.2,num2str(p_nemo_float{end}.FLOAT_IDENTIFICATION.float_serial_number)); +set(text_serial_number,'interpreter','none', 'FontSize', 6,'FontWeight','bold'); +modification_history(end+1) = text_serial_number; +clear measured_ranges measured_length measured_start_index profile_count text_serial_number + +% change figure title +if p_single_float + set(get(gca, 'title'), 'String', ['Float ' num2str(p_nemo_float{end}.FLOAT_IDENTIFICATION.float_serial_number) ', ' datestr(now) ]); +else + set(get(gca, 'title'), 'String', datestr(now)); +end + + +if show_profile_numbers == 1 + % plot profile numbers + text_profile_numbers = textm(bgp_lat(10:10:end)+0.2,bgp_lon(10:10:end)+0.2,num2str(profile_numbers(10:10:end))); + set(text_profile_numbers,'interpreter','none', 'FontSize', 6,'FontWeight','bold'); + if length(text_profile_numbers) > 1 + modification_history = [modification_history, text_profile_numbers']; + elseif length(text_profile_numbers) == 1 + modification_history(end+1) = text_profile_numbers; + end +end + +clear bgp_lat bgp_lon dep_lat dep_lon bgp_src profile_numbers text_profile_numbers + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+plot/floats.m b/lib/vendor/nemo2profilelib/+nemo/+plot/floats.m new file mode 100644 index 0000000..d991185 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+plot/floats.m @@ -0,0 +1,128 @@ +function [ boolean ] = floats(p_nemo_data, p_config, p_hemisphere, p_single_float) +%FLOATS Creates a template figure and plots the given floats on it. +% +% Returns: +% boolean: True if everything went successful. +% +% Parameters: +% p_nemo_data: Struct containing float data, load with nemo.load.data(). +% p_config: Struct containing the global configuration file. Load +% using nemo.load.config(). +% p_hemisphere: String, either 'south' or 'north'. If it is given an +% empty string, both hemispheres are being plotted. +% WARNING: North is not implemented yet. +% p_single_float: If true, every float will be plotted in a separate plot. + + +%% Initialize return variable +boolean = false; + +%% Update sea ice coverage +if p_config.GENERAL.update_sea_ice_coverage + try + websave(p_config.ADDITIONAL_CONTENT.antarctic_sea_ice_coverage_file, ... + p_config.REMOTE_CONTENT.antarctic_sea_ice_coverage); + catch ex + warning([mfilename ': Updating sea ice coverage failed with error: ' ... + getReport(ex)]); + end +end + +% run through every float and generate an overview plot +float_count = length(p_nemo_data); + +switch p_hemisphere + case 'south' + % prepare directory and parameters + if p_single_float + directory = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_SINGLE_FLOAT_SOUTH.storage_directory_image); + directory_fig = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_SINGLE_FLOAT_SOUTH.storage_directory_fig); + image_type = p_config.PLOT_SINGLE_FLOAT_SOUTH.image_type; + width = p_config.PLOT_SINGLE_FLOAT_SOUTH.image_width; + height = p_config.PLOT_SINGLE_FLOAT_SOUTH.image_height; + paperunit = p_config.PLOT_SINGLE_FLOAT_SOUTH.image_paperunit; + else + directory = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_OVERVIEW_SOUTH.storage_directory_image); + directory_fig = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_OVERVIEW_SOUTH.storage_directory_fig); + image_type = p_config.PLOT_OVERVIEW_SOUTH.image_type; + image_filename = p_config.PLOT_OVERVIEW_SOUTH.image_filename; + figure_filename = p_config.PLOT_OVERVIEW_SOUTH.figure_filename; + width = p_config.PLOT_OVERVIEW_SOUTH.image_width; + height = p_config.PLOT_OVERVIEW_SOUTH.image_height; + paperunit = p_config.PLOT_OVERVIEW_SOUTH.image_paperunit; + end + if ~exist(directory,'dir') + mkdir(directory); + end + if ~exist(directory_fig,'dir') + mkdir(directory_fig); + end + + template_id_south = nemo.plot.templates.create_south(p_config); + for o_float = 1:float_count + if p_nemo_data{o_float}{1}.DEPLOYMENT_INFO.deployment_position(1) > 0 + continue + end + float_serial_number = ... + p_nemo_data{o_float}{1}.FLOAT_IDENTIFICATION.float_serial_number; + modification_history = ... + nemo.plot.float_to_template_south(p_nemo_data{o_float}, ... + template_id_south, p_config, p_single_float); + + if p_single_float + %saveas(template_id_south,[directory num2str(float_serial_number) '.' image_type], image_type); + set(template_id_south,'InvertHardcopy','on'); + set(template_id_south,'PaperUnits', paperunit); + papersize = get(template_id_south, 'PaperSize'); + left = (papersize(1)- width)/2; + bottom = (papersize(2)- height)/2; + myfiguresize = [left, bottom, width, height]; + set(template_id_south,'PaperPosition', myfiguresize); + + print(template_id_south, ... + [directory num2str(float_serial_number)], image_type); + + if (p_config.PLOT_SINGLE_FLOAT_SOUTH.save_figure) + set(gcf,'CreateFcn','set(gcf,''Visible'',''on'')'); + saveas(template_id_south,[directory_fig num2str(float_serial_number)]); + end + delete(modification_history); + end + clear modification_history + end + + if ~p_single_float + + set(template_id_south,'InvertHardcopy','on'); + set(template_id_south,'PaperUnits', paperunit); + papersize = get(template_id_south, 'PaperSize'); + left = (papersize(1)- width)/2; + bottom = (papersize(2)- height)/2; + myfiguresize = [left, bottom, width, height]; + set(template_id_south,'PaperPosition', myfiguresize); + + print(template_id_south, [directory image_filename], image_type); + + if (p_config.PLOT_OVERVIEW_SOUTH.save_figure) + set(gcf,'CreateFcn','set(gcf,''Visible'',''on'')'); + saveas(template_id_south, ... + [directory_fig ... + figure_filename]); + end + end + + close(template_id_south); + case 'north' + warning('Northern overview plot is not implemented yet...'); + otherwise + % if anything else is given, just plot both hemispheres + nemo.plot.floats(p_nemo_data, p_config, 'south', p_single_float); + nemo.plot.floats(p_nemo_data, p_config, 'north', p_single_float); +end + +boolean = true; + +return + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+plot/rafos.m b/lib/vendor/nemo2profilelib/+nemo/+plot/rafos.m new file mode 100644 index 0000000..b07b39d --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+plot/rafos.m @@ -0,0 +1,186 @@ +function [ boolean ] = rafos( p_nemo_data, p_config ) +%RAFOS Plots the RAFOS data. of the given nemo floats. +% +% Parameters: +% p_nemo_data: The nemo data which will be plotted. +% p_config: The nemo configuration. +% +% Returns: +% boolean: True if successful. + +%% Initialize return variable +boolean = false; + +%% Parameter check %TODO + +%% Get config parameter +% get plot details +width = p_config.PLOT_RAFOS.image_width; +height = p_config.PLOT_RAFOS.image_height; +paperunit = p_config.PLOT_RAFOS.image_paperunit; +image_type = p_config.PLOT_RAFOS.image_type; +directory = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_RAFOS.storage_directory_image); +directory_fig = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_RAFOS.storage_directory_fig); +ylabel_fontsize = p_config.PLOT_RAFOS.ylabel_fontsize; +xlabel_fontsize = p_config.PLOT_RAFOS.xlabel_fontsize; +axis_fontsize = p_config.PLOT_RAFOS.axis_fontsize; +title_fontsize = p_config.PLOT_RAFOS.title_fontsize; + +if (~isdir(directory)) + mkdir(directory); +end +if ~exist(directory_fig, 'dir') + mkdir(directory_fig); +end + +%% Collect required data + +switch (class(p_nemo_data{1})) + case 'struct' % only one float is to be processed + rafos_names = p_nemo_data{1}.RAFOS_VALUES_FORMAT; + boolean = plot_float(p_nemo_data); + case 'cell' % more than one float has to be processed + rafos_names = p_nemo_data{1}{1}.RAFOS_VALUES_FORMAT; + boolean = all(cellfun(@plot_float, p_nemo_data)); + otherwise + disp([mfilename ': Class ' class(p_nemo_data{1}) ' not supported for plotting!']); +end + + function boolean = plot_float(p_float) + boolean = false; + rafos_data = nemo.float.collect(p_float, 'RAFOS_VALUES', '', 'combine'); + + if (isempty(rafos_data)) + boolean = true; + return + end + + + plot_c = []; + plot_y = []; + plot_x = []; + plot_status = []; + for i = 8:2:18 + rafos_correlation = rafos_data(:, i); + + plot_c = [plot_c; rafos_correlation]; + plot_y = [plot_y; rafos_data(:, i + 1)]; + plot_x = [plot_x; datenum(rafos_data(:, 2:7))]; + plot_status = [plot_status; rafos_data(:, 1)]; + end + + % sort rows by color + sorted_plot = [plot_x, plot_y, plot_c]; + sorted_plot = flipud(sortrows(sorted_plot, 3)); + plot_x = sorted_plot(:, 1); + plot_y = sorted_plot(:, 2); + plot_c = sorted_plot(:, 3); + clear sorted_plot; + + + % initialize figure + figure_id = figure('Visible', 'off'); % create figure + %figure_id = figure(); % create figure + set(figure_id,'InvertHardcopy','on'); + set(figure_id,'PaperUnits', paperunit); + papersize = get(figure_id, 'PaperSize'); + left = (papersize(1)- width)/2; + bottom = (papersize(2)- height)/2; + myfiguresize = [left, bottom, width, height]; + set(figure_id,'PaperPosition', myfiguresize); + + % plot data + sub(1) = subplot(5, 1, 1:4); % travel time + hold on + box on + grid on + % get required information about x-axis limits and ticks + x_diff = max(plot_x) - min(plot_x); + set(gca, 'XLimMode', 'manual', 'XLim', [min(plot_x), max(plot_x)]); + if (x_diff < 30) + set(gca, 'XTick', [min(plot_x):5:max(plot_x)]); + elseif (x_diff < 365) + set(gca, 'XTick', [min(plot_x):30:max(plot_x)]); + else + set(gca, 'XTick', [min(plot_x):365:max(plot_x)]); + end + + s = scatter3(plot_x, plot_y, plot_c, 12, plot_c, 'filled'); + view(0, 90); + c = colorbar; + caxis([0 255]); + colormap(flipud(colormap)); + c.Label.String = 'Correlation Height (ch)'; + + ylim(p_config.PLOT_RAFOS.ylim); + ylabel('RAFOS Time of Arrival [s]', 'FontSize', ylabel_fontsize); +% xlabel('Date of profile', 'FontSize', xlabel_fontsize); + if (p_float{1}.SECOND_ORDER_INFORMATION.best_guess_position_lat > 0) + deployment_string = ' (Arctic)'; + else + deployment_string = ' (Antarctica)'; + end + title(sub(1), [ 'Float ' ... + num2str(p_float{1}.FLOAT_IDENTIFICATION.float_serial_number) ... + ' @ ' ... + datestr(datetime('now', 'Format', 'dd-MMM-yyyy HH:mm:SS')) ... + deployment_string ... + ], 'FontSize', title_fontsize); + datetick('x','yyyy-mm-dd', 'keepticks', 'keeplimits'); + set(gca, 'fontsize', axis_fontsize) + clear s + hold off + + + sub(2) = subplot(5, 1, 5); % rafos status + hold on + box on + grid on + % get required information about x-axis limits and ticks + x_diff = max(plot_x) - min(plot_x); + set(gca, 'XLimMode', 'manual', 'XLim', [min(plot_x), max(plot_x)]); + if (x_diff < 30) + set(gca, 'XTick', [min(plot_x):5:max(plot_x)]); + elseif (x_diff < 365) + set(gca, 'XTick', [min(plot_x):30:max(plot_x)]); + else + set(gca, 'XTick', [min(plot_x):365:max(plot_x)]); + end + set(gca, 'YTickMode', 'manual', 'YLimMode', 'manual', ... + 'YTick', [0:2:10], 'YLim', [0, 10]); + + s = stairs(datenum(rafos_data(:, 2:7)), rafos_data(:, 1), ... + 'Marker', 'o'); + s.DisplayName = rafos_names{1}; + + clear s +% title(sub(2), 'RAFOS status', 'FontSize', p_config.PLOT_RAFOS.title_fontsize); + datetick('x','yyyy-mm-dd', 'keepticks', 'keeplimits'); + ylim([0 8]); + ylabel('Status', 'FontSize', ylabel_fontsize); + xlabel('Date of profile', 'FontSize', xlabel_fontsize); + set(gca, 'fontsize', axis_fontsize) + clear a + hold off + + print(figure_id, ... + [directory p_config.PLOT_RAFOS.image_filename ... + num2str(p_float{1}.FLOAT_IDENTIFICATION.float_serial_number)] ... + , image_type); + + if (p_config.PLOT_RAFOS.save_figure) + set(gcf,'CreateFcn','set(gcf,''Visible'',''on'')'); + saveas(figure_id, ... + [directory_fig ... + p_config.PLOT_RAFOS.figure_filename ... + num2str(p_float{1}.FLOAT_IDENTIFICATION.float_serial_number)]); + end + + close(figure_id); + boolean = true; + end + + + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+plot/temp_sal.m b/lib/vendor/nemo2profilelib/+nemo/+plot/temp_sal.m new file mode 100644 index 0000000..e7426bf --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+plot/temp_sal.m @@ -0,0 +1,191 @@ +function [ boolean ] = temp_sal( p_nemo_data, p_config ) +%TEMP_SAL Generates a temperature/salinity plot for every float given. +% Generates a plot with two subplots, depth on time. +% The colors of the first plot are given by the temperature, the second +% one shows the salinity. +% +% Parameters: +% p_nemo_data: The nemo data. +% p_config: The nemo configuration. +% +% Returns: +% boolean: False on fail. + +%% Initialize return variable +boolean = false; + +%% Parameter check %TODO + +%% Get config parameter +% get plot details +width = p_config.PLOT_TEMP_SAL.image_width; +height = p_config.PLOT_TEMP_SAL.image_height; +paperunit = p_config.PLOT_TEMP_SAL.image_paperunit; +image_type = p_config.PLOT_TEMP_SAL.image_type; +directory = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_TEMP_SAL.storage_directory_image); +directory_fig = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_TEMP_SAL.storage_directory_fig); +ylabel_fontsize = p_config.PLOT_TEMP_SAL.ylabel_fontsize; +xlabel_fontsize = p_config.PLOT_TEMP_SAL.ylabel_fontsize; +axis_fontsize = p_config.PLOT_TEMP_SAL.axis_fontsize; +title_fontsize = p_config.PLOT_TEMP_SAL.title_fontsize; + +if (~isdir(directory)) + mkdir(directory); +end +if ~exist(directory_fig, 'dir') + mkdir(directory_fig); +end + +%% Collect required data + +switch (class(p_nemo_data{1})) + case 'struct' % only one float is to be processed + boolean = plot_float(p_nemo_data); + case 'cell' % more than one float has to be processed + boolean = all(cellfun(@plot_float, p_nemo_data)); + otherwise + disp([mfilename ': Class ' class(p_nemo_data{1}) ' not supported for plotting!']); +end + + function boolean = plot_float(p_float) + boolean = false; + profiles_data = nemo.float.collect(p_float, 'PROFILE_DATA', ''); + profiles_data_combined = nemo.float.collect(p_float, 'PROFILE_DATA', '', 'combine'); + max_pressure = max(profiles_data_combined(:, 7)); + profiles_datetime = nemo.float.collect(p_float, ... + 'SECOND_ORDER_INFORMATION', ... + 'best_guess_datevec'); + profiles_datetime = datenum(profiles_datetime(:, 1:6)); + if (isempty(profiles_data)) + boolean = true; + return + end + + % initialize figure + figure_id = figure('Visible', 'Off'); + set(figure_id,'InvertHardcopy','on'); + set(figure_id,'PaperUnits', paperunit, 'Position', [ 0 0 width height]); + papersize = get(figure_id, 'PaperSize'); + left = (papersize(1) - width)/2; + bottom = (papersize(2) - height)/2; + myfiguresize = [left, bottom, width, height]; + set(figure_id,'PaperPosition', myfiguresize); + + % Temperature plot + sub(1) = subplot(2, 1, 1); + hold on + box on + grid on + set(gca, 'YLimMode', 'manual', 'YDir', 'reverse', 'YLim', [0 max_pressure]); + set(gca, 'fontsize', axis_fontsize) + ylabel('Depth [m]', 'FontSize', ylabel_fontsize); + % get required information about x-axis limits and ticks + x_diff = max(profiles_datetime) - min(profiles_datetime); + set(gca, 'XLimMode', 'auto'); + if (x_diff < 30) + set(gca, 'XTick', [min(profiles_datetime):5:max(profiles_datetime)]); + elseif (x_diff < 365) + set(gca, 'XTick', [min(profiles_datetime):30:max(profiles_datetime)]); + else + set(gca, 'XTick', [min(profiles_datetime):365:max(profiles_datetime)]); + end + time_combined = []; + for i = 1:size(profiles_datetime, 1) + temperature = profiles_data{i}(:, 5); + pressure = profiles_data{i}(:, 7); + % erase all invalid values + valid_points = (pressure >= 0) & (pressure <= 2000) & (temperature < 10); + temperature = temperature(valid_points); + pressure = pressure(valid_points); + time = repmat(profiles_datetime(i, :), size(pressure, 1), 1); + time_combined = [time_combined; time]; + scatter(time, pressure, 45, temperature, 'filled'); + end + datetick('x','yyyy-mm-dd', 'keepticks', 'keeplimits'); +% xlabel('Date of profile', 'FontSize', xlabel_fontsize); + + + c = colorbar; + cmocean('thermal'); + %colormap(gca, 'jet'); + %caxis([-5, 15]); + c.Label.String = ['Temperature [' char(176) 'C]']; + clear c + % create main title + if (p_float{1}.SECOND_ORDER_INFORMATION.best_guess_position_lat > 0) + deployment_string = ' (Arctic)'; + else + deployment_string = ' (Antarctica)'; + end + t = title(... + [ 'Float ' ... + num2str(p_float{1}.FLOAT_IDENTIFICATION.float_serial_number) ... + ' @ ' ... + datestr(datetime('now', 'Format', 'dd-MMM-yyyy HH:mm:SS')) ... + deployment_string ... + ]); + set(t, 'FontSize', title_fontsize); + clear t + hold off + + % Salinity plot + sub(2) = subplot(2, 1, 2); + hold on + box on + grid on + set(gca, 'YLimMode', 'manual', 'YDir', 'reverse', 'YLim', [0 max_pressure]); + set(gca, 'fontsize', axis_fontsize) + ylabel('Depth [m]', 'FontSize', ylabel_fontsize); + xlabel('Date of profile', 'FontSize', xlabel_fontsize); + + % get required information about x-axis limits and ticks + x_diff = max(profiles_datetime) - min(profiles_datetime); + set(gca, 'XLimMode', 'auto'); + if (x_diff < 30) + set(gca, 'XTick', [min(profiles_datetime):5:max(profiles_datetime)]); + elseif (x_diff < 365) + set(gca, 'XTick', [min(profiles_datetime):30:max(profiles_datetime)]); + else + set(gca, 'XTick', [min(profiles_datetime):365:max(profiles_datetime)]); + end + for i = 1:size(profiles_datetime, 1) + salinity = profiles_data{i}(:, 9); + pressure = profiles_data{i}(:, 7); + % erase all invalid values + valid_points = (pressure >= 0) & (pressure <= 2000) & (salinity > 10); + time = repmat(profiles_datetime(i, :), size(pressure(valid_points), 1), 1); + scatter(time, ... + pressure(valid_points), 45, salinity(valid_points), 'filled'); + end + datetick('x','yyyy-mm-dd', 'keepticks', 'keeplimits'); + c = colorbar; + cmocean('haline'); + %colormap(gca, [0:0.001:1; repmat(0, 1, 1001); fliplr(0:0.001:1)]'); + %caxis([33, 36]); + c.Label.String = 'Salinity'; + hold off + + + set(gcf, 'Renderer', 'painters'); + print(figure_id, ... + [directory p_config.PLOT_TEMP_SAL.image_filename ... + num2str(p_float{1}.FLOAT_IDENTIFICATION.float_serial_number)] ... + , image_type); + + if (p_config.PLOT_TEMP_SAL.save_figure) + set(gcf,'CreateFcn','set(gcf,''Visible'',''on'')'); + saveas(figure_id, ... + [directory_fig ... + p_config.PLOT_TEMP_SAL.figure_filename ... + num2str(p_float{1}.FLOAT_IDENTIFICATION.float_serial_number)]); + end + + close(figure_id); + + clear figure_id f + boolean = true; + + end + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+plot/temp_sal_ptemp.m b/lib/vendor/nemo2profilelib/+nemo/+plot/temp_sal_ptemp.m new file mode 100644 index 0000000..c278403 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+plot/temp_sal_ptemp.m @@ -0,0 +1,211 @@ +function [ boolean ] = temp_sal_ptemp( p_nemo_data, p_config ) +%TEMP_SAL_PTEMP Plots the temperature, salinity and the potential temperature. +% +% Parameters: +% p_nemo_data: The nemo data to be plotted. +% p_config: The nemo configuration. +% +% Returns: +% boolean: True if successful. + + +%% Initialize return variable +boolean = false; + +%% Parameter check %TODO + +%% Get config parameter +% get plot details +width = p_config.PLOT_TEMP_SAL_PTEMP.image_width; +height = p_config.PLOT_TEMP_SAL_PTEMP.image_height; +paperunit = p_config.PLOT_TEMP_SAL_PTEMP.image_paperunit; +image_type = p_config.PLOT_TEMP_SAL_PTEMP.image_type; +directory = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_TEMP_SAL_PTEMP.storage_directory_image); +directory_fig = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_TEMP_SAL_PTEMP.storage_directory_fig); +ylabel_fontsize = p_config.PLOT_TEMP_SAL_PTEMP.ylabel_fontsize; +xlabel_fontsize = p_config.PLOT_TEMP_SAL_PTEMP.xlabel_fontsize; +axis_fontsize = p_config.PLOT_TEMP_SAL_PTEMP.axis_fontsize; + +if (~isdir(directory)) + mkdir(directory); +end +if ~exist(directory_fig, 'dir') + mkdir(directory_fig); +end + +%% Collect required data + +switch (class(p_nemo_data{1})) + case 'struct' % only one float is to be processed + boolean = plot_float(p_nemo_data); + case 'cell' % more than one float has to be processed + boolean = all(cellfun(@plot_float, p_nemo_data)); + otherwise + disp([mfilename ': Class ' class(p_nemo_data{1}) ' not supported for plotting!']); +end + + function boolean = plot_float(p_float) + boolean = false; + profile_data = nemo.float.collect(p_float, 'PROFILE_DATA', ''); + combined_profile_data = nemo.float.collect(p_float, 'PROFILE_DATA', '', 'combine'); + best_guess_lat = nemo.float.collect(p_float, 'SECOND_ORDER_INFORMATION', 'best_guess_position_lat'); + best_guess_lon = nemo.float.collect(p_float, 'SECOND_ORDER_INFORMATION', 'best_guess_position_lon'); + + max_pressure = max(combined_profile_data(:, 7)); + max_salinity = max(combined_profile_data(:, 9)); + max_temperature = max(combined_profile_data(:, 5)); + min_pressure = min(combined_profile_data(:, 7)); + min_salinity = min(combined_profile_data(:, 9)); + min_temperature = min(combined_profile_data(:, 5)); + + if (isempty(profile_data)) + boolean = true; + return + end + + % initialize figure + figure_id = figure('Visible', 'Off'); + set(figure_id,'InvertHardcopy','on'); + set(figure_id,'PaperUnits', paperunit, 'Position', [ 0 0 width height]); + papersize = get(figure_id, 'PaperSize'); + left = (papersize(1) - width)/2; + bottom = (papersize(2) - height)/2; + myfiguresize = [left, bottom, width, height]; + set(figure_id,'PaperPosition', myfiguresize); + + % Salinity plot + sub(1) = subplot(1, 3, 1); + hold on + set(gca, 'YLimMode', 'manual', ... + 'YLim', [min_pressure max_pressure], ... + 'YTick', [min_pressure:200:max_pressure], ... + 'YDir', 'reverse', ... + 'XLimMode', 'manual', ... + 'XLim', [min_salinity max_salinity], ... + 'XTickMode', 'auto'); + for i = 1:length(profile_data) + pressure = profile_data{i}(:, 7); + salinity = profile_data{i}(:, 9); + plot(salinity, pressure, ... + 'color', [0 0 1] * i/length(profile_data)); + end + box on + grid on + hold off + + % create main title + t = title(... + [ 'Float ' ... + num2str(p_float{1}.FLOAT_IDENTIFICATION.float_serial_number) ... + ' @ ' ... + datestr(datetime('now', 'Format', 'dd-MMM-yyyy HH:mm:SS')) ... + ]); + set(t, 'FontSize', 18); + clear t + xlabel('Salinity [PSU]', 'FontSize', xlabel_fontsize); + ylabel('Depth [m]', 'FontSize', ylabel_fontsize); + set(gca, 'fontsize', axis_fontsize); +% set(gca, 'YDir', 'reverse', 'YTick', [0:2000]); +% ylim([-2000 0]); + +% set(gca, 'YLimMode', 'manual', 'YLim', [-2000 0], 'YTick', [-2000:200:0]); + + clear salinity pressure a + + % Temperature plot + sub(2) = subplot(1, 3, 2); + hold on + set(gca, 'YLimMode', 'manual', ... + 'YLim', [min_pressure max_pressure], ... + 'YTick', [min_pressure:200:max_pressure], ... + 'YDir', 'reverse', ... + 'XLimMode', 'manual', ... + 'XLim', [min_temperature, max_temperature], ... + 'XTickMode', 'auto'); + for i = 1:length(profile_data) + pressure = profile_data{i}(:, 7); + temperature = profile_data{i}(:, 5); + plot(temperature, pressure, ... + 'color', [1 0 0] * i/length(profile_data)); + end + xlabel('Temperature [°C]', 'FontSize', xlabel_fontsize); + ylabel('Depth [m]', 'FontSize', ylabel_fontsize); + set(gca, 'fontsize', axis_fontsize); + box on + grid on + hold off + clear pressure temperature a + + % Potential temperature plot + sub(3) = subplot(2, 3, 3); + hold on + set(gca, 'XLimMode', 'manual', ... + 'XLim', [min_salinity max_salinity], ... + 'XTick', [min_salinity:max_salinity], ... + 'XTickMode', 'auto', ... + 'YLimMode', 'manual', ... + 'YTickMode', 'auto', ... + 'YLim', [min_temperature, max_temperature]); + for i = 1:length(profile_data) + pressure = profile_data{i}(:, 7); + temperature = profile_data{i}(:, 5); + salinity = profile_data{i}(:, 9); + plot(salinity, sw_ptmp(salinity, temperature, pressure, 0), ... + 'Linestyle', 'none', ... + 'color', [0 1 0] * i/length(profile_data), ... + 'marker','.' ... + ); + end + ylabel('\theta (Potential temperature) [°C]', 'FontSize', xlabel_fontsize); + xlabel('Salinity [PSU]', 'FontSize', ylabel_fontsize); + set(gca, 'fontsize', axis_fontsize); + box on + grid on + hold off + clear a + + sub(4) = subplot(2, 3, 6); + hold on + latlim = [-80 80]; + lonlim = [-90 70]; + worldmap(latlim,lonlim); + setm(gca, 'MLabelParallel', 'south') + if (~exist('map', 'var')) + map = load('coast'); + end + plotm(map.lat,map.long,'k'); + plot3m(best_guess_lat,best_guess_lon,150,'marker','.','markersize',8,'color',[0 1 0],'linestyle','none'); + xlabel('Longitude', 'FontSize', xlabel_fontsize); + ylabel('Latitude', 'FontSize', ylabel_fontsize); + set(gca, 'fontsize', axis_fontsize); + box on + grid on + hold off + clear a + + + set(gcf, 'Renderer', 'painters'); + print(figure_id, ... + [directory p_config.PLOT_TEMP_SAL_PTEMP.image_filename ... + num2str(p_float{1}.FLOAT_IDENTIFICATION.float_serial_number)] ... + , image_type); + + if (p_config.PLOT_TEMP_SAL_PTEMP.save_figure) + set(gcf,'CreateFcn','set(gcf,''Visible'',''on'')'); + saveas(figure_id, ... + [directory_fig ... + p_config.PLOT_TEMP_SAL_PTEMP.figure_filename ... + num2str(p_float{1}.FLOAT_IDENTIFICATION.float_serial_number)]); + end + + close(figure_id); + clear figure_id f + boolean = true; + + end + + + + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+plot/timing_information.m b/lib/vendor/nemo2profilelib/+nemo/+plot/timing_information.m new file mode 100644 index 0000000..3c53bbd --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+plot/timing_information.m @@ -0,0 +1,507 @@ +function [ boolean ] = timing_information( p_nemo_data, p_config ) +%TIMING_INFORMATION Plots clock details. +% +% Returns: +% boolean: False if something went wrong during the plotting process. +% +% Parameters: +% p_nemo_data: Struct, containing all float data (load with nemo.load.data()). +% p_config: Global configuration file. Load using nemo.load.config(). + +%% Initialize return variable +boolean = false; + +%% Parameter check +parameter_error = false; + +if (~iscell(p_nemo_data) || ~isstruct(p_config)) + parameter_error = true; +end + +if (parameter_error) % display error message + disp([ mfilename ': Wrong parameter! Please check your input parameters carefully!']); + return; +else % do some cleanup + clear parameter_error; +end + +%% Setup directories +directory = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_TIMING_INFORMATION.storage_directory_image); +directory_fig = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_TIMING_INFORMATION.storage_directory_fig); +image_type = p_config.PLOT_TIMING_INFORMATION.image_type; +if ~exist(directory, 'dir') + system(['mkdir ' directory]); +end +if ~exist(directory_fig, 'dir') + mkdir(directory_fig); +end + +%% Process every float + +% create dummy legend item to make the boxes be all of the same size + +float_count = length(p_nemo_data); + +for o_float = 1:float_count + + + gps_profile_data = struct(); + all_profile_data = struct(); + all_profile_data.profiles = p_nemo_data{o_float}; + all_profile_data.best_guess_position_src = nemo.float.collect(p_nemo_data{o_float}, 'SECOND_ORDER_INFORMATION', 'best_guess_position_src'); + all_profile_data.best_guess_datevec = nemo.float.collect(p_nemo_data{o_float}, 'SECOND_ORDER_INFORMATION', 'best_guess_datevec'); + + gps_profile_data.profiles = {}; + for i = 1:length(all_profile_data.best_guess_position_src) + if (strcmp(all_profile_data.best_guess_position_src{i}, 'gps')) + gps_profile_data.profiles{end + 1} = p_nemo_data{o_float}{i}; + end + end +% +% if isempty(gps_profile_data.profiles) +% continue +% end + + gps_profile_data.best_guess_datevec = nemo.float.collect(gps_profile_data.profiles, 'SECOND_ORDER_INFORMATION', 'best_guess_datevec'); + + all_profile_data.start_date = p_nemo_data{o_float}{end}.OVERALL_MISSION_INFORMATION.start_date; + all_profile_data.start_time = p_nemo_data{o_float}{end}.OVERALL_MISSION_INFORMATION.start_time; + gps_profile_data.surface_gps_data = nemo.float.collect(gps_profile_data.profiles, 'SURFACE_GPS_DATA'); + + plot_data = struct(); + % required data for plot 1 + + % SURFACESTART - GPSFix + if ~isempty(gps_profile_data.profiles) + plot_data.surfaceStart_GPS = struct(); + gps_profile_data.xmit_surface_start_datevec = nemo.float.collect(gps_profile_data.profiles, 'xmitDatevec', 'xmit_surface_start_time'); + plot_data.surfaceStart_GPS.y = ( ... + datenum(gps_profile_data.xmit_surface_start_datevec) - ... + datenum(gps_profile_data.surface_gps_data(:, 8:13)) ... + ) * 86400; + plot_data.surfaceStart_GPS.x = datenum(gps_profile_data.best_guess_datevec); + end + + % RTC0015 - GPSFix + if ~(isempty(gps_profile_data.profiles)) + gps_profile_data.profile_data = nemo.float.collect(gps_profile_data.profiles, 'PROFILE_DATA'); + plot_data.RTC0015_GPSFix = struct(); + % get values near to 15m depth + [~, indices] = cellfun(@(x) find_nearest_value_to(x(:, 7), 15), gps_profile_data.profile_data, 'UniformOutput', false); + time_15m = cellfun(@(x,y) x(y, 2:4), gps_profile_data.profile_data, indices, 'UniformOutput', false); + time_15m = cell2mat(time_15m'); + gps_profile_data.RTC0015_datevec = gps_profile_data.surface_gps_data(:, 8:13); + gps_profile_data.RTC0015_datevec(:, 4:6) = time_15m; + tmp_subtract_one_day = (gps_profile_data.surface_gps_data(:, 11) >= 21 & gps_profile_data.surface_gps_data(:, 11) <= 23) ... + & (time_15m(:, 1) >= 0 & time_15m(:, 1) <= 3); + tmp_add_one_day = (gps_profile_data.surface_gps_data(:, 11) >= 0 & gps_profile_data.surface_gps_data(:, 11) <= 3) ... + & (time_15m(:, 1) >= 21 & time_15m(:, 1) <= 23); + + if (any(tmp_subtract_one_day)) + % GPS is more trustworthy, so reduce the RTC date by 1 + tmp_indices = find(tmp_subtract_one_day); + for i = 1:length(tmp_indices) + gps_profile_data.RTC0015_datevec(tmp_indices(i), :) = datevec(addtodate(datenum([gps_profile_data.RTC0015_datevec(tmp_indices(i), 1:3), time_15m(tmp_indices(i), :)]), -1, 'day')); + end + end + if (any(tmp_add_one_day)) + % same here, but now add 1 day + tmp_indices = find(tmp_add_one_day); + for i = 1:length(tmp_indices) + gps_profile_data.RTC0015_datevec(tmp_indices(i), :) = datevec(addtodate(datenum([gps_profile_data.RTC0015_datevec(tmp_indices(i), 1:3), time_15m(tmp_indices(i), :)]), +1, 'day')); + end + end + clear tmp_indices tmp_subtract_one_day tmp_add_one_day + + plot_data.RTC0015_GPSFix.y = ( ... + datenum(gps_profile_data.RTC0015_datevec) - ... + datenum(gps_profile_data.surface_gps_data(:, 8:13)) ... + ) * 86400; + plot_data.RTC0015_GPSFix.x = datenum(gps_profile_data.best_guess_datevec); + end + + + % SURFACESTART - IRIDIUM + if ~isempty(gps_profile_data.profiles) + plot_data.surfaceStart_Iridium = struct(); + gps_profile_data.iridium_data = nemo.float.collect(gps_profile_data.profiles, 'IRIDIUM_DATA'); + plot_data.surfaceStart_Iridium.y = ( ... + datenum(gps_profile_data.xmit_surface_start_datevec) - ... + datenum(gps_profile_data.iridium_data(:, 4:9)) ... + ) * 86400; + plot_data.surfaceStart_Iridium.x = datenum(gps_profile_data.best_guess_datevec); + end + + % ASCENTSTART - RTC2000 + plot_data.ascentStart_RTC2000 = struct(); + all_profile_data.xmit_ascent_start_datevec = nemo.float.collect(all_profile_data.profiles, 'xmitDatevec', 'xmit_ascent_start_time'); + all_profile_data.profile_data = nemo.float.collect(all_profile_data.profiles, 'PROFILE_DATA'); + all_profile_data.profile_data_2000 = cellfun(@(x) x(end, 2:4), all_profile_data.profile_data, 'UniformOutput', false); + all_profile_data.profile_data_2000 = cell2mat(all_profile_data.profile_data_2000'); + all_profile_data.profile_data_2000_datevec = datevec(datetime([all_profile_data.xmit_ascent_start_datevec(:, 1:3), all_profile_data.profile_data_2000])); + + tmp_subtract_one_day = (all_profile_data.profile_data_2000(:, 1) >= 21 & all_profile_data.profile_data_2000(:, 1) <= 23) ... + & (all_profile_data.xmit_ascent_start_datevec(:, 4) >= 0 & all_profile_data.xmit_ascent_start_datevec(:, 4) <= 3); + tmp_add_one_day = (all_profile_data.profile_data_2000(:, 1) >= 0 & all_profile_data.profile_data_2000(:, 1) <= 3) ... + & (all_profile_data.xmit_ascent_start_datevec(:, 4) >= 21 & all_profile_data.xmit_ascent_start_datevec(:, 4) <= 23); + + if (any(tmp_subtract_one_day)) + % RTC is more trustworthy, so reduce the software date by 1 + tmp_indices = find(tmp_subtract_one_day); + for i = 1:length(tmp_indices) + all_profile_data.profile_data_2000_datevec(tmp_indices(i), :) = datevec(addtodate(datenum([all_profile_data.xmit_ascent_start_datevec(tmp_indices(i), 1:3), all_profile_data.profile_data_2000(tmp_indices(i), :)]), -1, 'day')); + end + end + if (any(tmp_add_one_day)) + % same here, but now add 1 day + tmp_indices = find(tmp_add_one_day); + for i = 1:length(tmp_indices) + all_profile_data.profile_data_2000_datevec(tmp_indices(i), :) = datevec(addtodate(datenum([all_profile_data.xmit_ascent_start_datevec(tmp_indices(i), 1:3), all_profile_data.profile_data_2000(tmp_indices(i), :)]), +1, 'day')); + end + end + clear tmp_indices tmp_subtract_one_day tmp_add_one_day + plot_data.ascentStart_RTC2000.y = ( ... + datenum(all_profile_data.xmit_ascent_start_datevec) - ... + datenum(all_profile_data.profile_data_2000_datevec) ... + ) * 86400; + plot_data.ascentStart_RTC2000.x = datenum(all_profile_data.best_guess_datevec); + + % SURFACESTART - RTC0000 + plot_data.surfaceStart_RTC0000 = struct(); + all_profile_data.xmit_surface_start_datevec = nemo.float.collect(all_profile_data.profiles, 'xmitDatevec', 'xmit_surface_start_time'); + all_profile_data.profile_data_0000 = cellfun(@(x) x(1, 2:4), all_profile_data.profile_data, 'UniformOutput', false); + all_profile_data.profile_data_0000 = cell2mat(all_profile_data.profile_data_0000'); + all_profile_data.profile_data_0000_datevec = datevec(datetime([all_profile_data.xmit_surface_start_datevec(:, 1:3), all_profile_data.profile_data_0000])); + + tmp_subtract_one_day = (all_profile_data.profile_data_0000(:, 1) >= 21 & all_profile_data.profile_data_0000(:, 1) <= 23) ... + & (all_profile_data.xmit_surface_start_datevec(:, 4) >= 0 & all_profile_data.xmit_surface_start_datevec(:, 4) <= 3); + tmp_add_one_day = (all_profile_data.profile_data_0000(:, 1) >= 0 & all_profile_data.profile_data_0000(:, 1) <= 3) ... + & (all_profile_data.xmit_surface_start_datevec(:, 4) >= 21 & all_profile_data.xmit_surface_start_datevec(:, 4) <= 23); + + if (any(tmp_subtract_one_day)) + % RTC is more trustworthy, so reduce the software date by 1 + tmp_indices = find(tmp_subtract_one_day); + for i = 1:length(tmp_indices) + all_profile_data.profile_data_0000_datevec(tmp_indices(i), :) = datevec(addtodate(datenum([all_profile_data.xmit_surface_start_datevec(tmp_indices(i), 1:3), all_profile_data.profile_data_0000(tmp_indices(i), :)]), -1, 'day')); + end + end + if (any(tmp_add_one_day)) + % same here, but now add 1 day + tmp_indices = find(tmp_add_one_day); + for i = 1:length(tmp_indices) + all_profile_data.profile_data_0000_datevec(tmp_indices(i), :) = datevec(addtodate(datenum([all_profile_data.xmit_surface_start_datevec(tmp_indices(i), 1:3), all_profile_data.profile_data_0000(tmp_indices(i), :)]), +1, 'day')); + end + end + clear tmp_indices tmp_subtract_one_day tmp_add_one_day + plot_data.surfaceStart_RTC0000.y = ( ... + datenum(all_profile_data.xmit_surface_start_datevec) - ... + datenum(all_profile_data.profile_data_0000_datevec) ... + ) * 86400; + plot_data.surfaceStart_RTC0000.x = datenum(all_profile_data.best_guess_datevec); + + + % required data for plot 2 + % get values near to 15m depth + clear indices + [~, indices] = cellfun(@(x) find_nearest_value_to(x(:, 7), 15), all_profile_data.profile_data, 'UniformOutput', false); + all_time_15m = cellfun(@(x,y) x(y, 2:4), all_profile_data.profile_data, indices, 'UniformOutput', false); + all_time_15m = cell2mat(all_time_15m'); + + % required data for plot 3 + % GPS - IRIDIUM + if ~isempty(gps_profile_data.profiles) + plot_data.GPS_Iridium = struct(); + plot_data.GPS_Iridium.y = ( ... + datenum(gps_profile_data.surface_gps_data(:, 8:13)) - ... + datenum(gps_profile_data.iridium_data(:, 4:9)) ... + ) * 86400; + plot_data.GPS_Iridium.x = datenum(gps_profile_data.best_guess_datevec); + + % GPS - GPSRTC + plot_data.GPS_GPSRTC = struct(); + plot_data.GPS_GPSRTC.y = ( ... + datenum(gps_profile_data.surface_gps_data(:, 8:13)) - ... + datenum(gps_profile_data.surface_gps_data(:, 2:7)) ... + ) * 86400; + plot_data.GPS_GPSRTC.x = datenum(gps_profile_data.best_guess_datevec); + end + + % get plot details + width = p_config.PLOT_TIMING_INFORMATION.image_width; + height = p_config.PLOT_TIMING_INFORMATION.image_height; + paperunit = p_config.PLOT_TIMING_INFORMATION.image_paperunit; + + % plot collected data + + figure_id = figure('Visible', 'off'); % create figure + % initialize figure + set(figure_id,'InvertHardcopy','on'); + set(figure_id,'PaperUnits', paperunit); + papersize = get(figure_id, 'PaperSize'); + left = (papersize(1)- width)/2; + bottom = (papersize(2)- height)/2; + myfiguresize = [left, bottom, width, height]; + set(figure_id,'PaperPosition', myfiguresize); + + clear height width left bottom papersize myfiguresize + + %set(gcf, 'Position', get(0,'ScreenSize'), 'color', [1 1 1]); + sub = {}; + figure_count = 3; + + sub{1} = subplot(figure_count,1,1); + hold on + + %yyaxis left + if ~isempty(gps_profile_data.profiles) + plot(plot_data.surfaceStart_GPS.x, plot_data.surfaceStart_GPS.y, ... + 'color', [1 0 0], ... + 'marker', 'o', ... + 'MarkerFaceColor', [1 0 0], ... + 'MarkerEdgeColor', [1 0 0], ... + 'linestyle', '-', ... + 'linewidth', 1,... + 'markersize', 5); + plot(plot_data.surfaceStart_Iridium.x, plot_data.surfaceStart_Iridium.y, ... + 'color', [0 0.5 0], ... + 'marker', 'x', ... + 'MarkerEdgeColor', [0 0.5 0], ... + 'linestyle', '-', ... + 'linewidth', 1, ... + 'markersize', 10); + plot(plot_data.RTC0015_GPSFix.x, plot_data.RTC0015_GPSFix.y, ... + 'color', [0 0.5 1], ... + 'marker', 'd', ... + 'MarkerEdgeColor', [0 0.5 1], ... + 'linestyle', '-', ... + 'linewidth', 1, ... + 'markersize', 10); + end + plot(plot_data.ascentStart_RTC2000.x, plot_data.ascentStart_RTC2000.y, ... + 'color', [1 0 1], ... + 'marker', '+', ... + 'MarkerEdgeColor', [1 0 1], ... + 'linestyle', '-', ... + 'linewidth', 1, ... + 'markersize', 10); + plot(plot_data.surfaceStart_RTC0000.x, plot_data.surfaceStart_RTC0000.y, ... + 'color', [0 0 1], ... + 'marker', 's', ... + 'MarkerEdgeColor', [0 0 1], ... + 'linestyle', '-', ... + 'linewidth', 1, ... + 'markersize', 10); + + + t = title([ 'Float ' ... + num2str(p_nemo_data{o_float}{1}.FLOAT_IDENTIFICATION.float_serial_number) ... + ' @ ' ... + datestr(datetime('now', 'Format', 'dd-MMM-yyyy HH:mm:SS')) ... + ]); + set(t, 'FontSize', 18); + ylabel('Time differences [sec]'); + if ~isempty(gps_profile_data.profiles) + leg1 = legend('SurfaceStart-GPSFix', 'SurfaceStart-IRIDIUM', 'RTC0015 - GPSFix', 'AscentStart-RTC2000', 'SurfaceStart-RTC0000'); + else + leg1 = legend('AscentStart-RTC2000', 'SurfaceStart-RTC0000'); + end + set(leg1, 'Location', 'eastoutside', 'Orientation', 'vertical'); + grid on + box on + hold off + + sub{2} = subplot(figure_count,1,2); + hold on + ylabel('Time of day [HH:MM]'); + + if ~isempty(gps_profile_data.profiles) + scatter(datenum(gps_profile_data.surface_gps_data(:, 8:13)), mod(datenum(gps_profile_data.surface_gps_data(:, 8:13)), 1), 20, ... + 'marker', 'o', ... + 'MarkerFaceColor', '1 0 0', ... + 'MarkerEdgeColor', '1 0 0'); + scatter(datenum(gps_profile_data.best_guess_datevec), mod(datenum(gps_profile_data.iridium_data(:, 4:9)), 1), 50, ... + 'marker', 's', ... + 'MarkerEdgeColor', '0 0.5 0'); + + end + + scatter(datenum(all_profile_data.best_guess_datevec), mod(datenum([repmat([1 1 1970], size(all_time_15m, 1), 1), all_time_15m]), 1), 50, ... + 'marker', '^', ... + 'MarkerEdgeColor', '0 0.5 1'); + scatter(datenum(all_profile_data.best_guess_datevec), mod(datenum(all_profile_data.xmit_surface_start_datevec), 1), 50, ... + 'marker', '+', ... + 'MarkerEdgeColor', '0 0 1'); + + set(gca, 'YLimMode', 'manual',... + 'YLim', [0, 1], ... + 'YTick', [0:6:24]./24); + datetick('y','HH:mm', 'keepticks'); + + + if ~isempty(gps_profile_data.profiles) + legend('GPSFix', 'IRIDIUM', 'RTC0015', 'SurfaceStart'); + else + legend('RTC0015', 'SurfaceStart'); + end + legend('Location', 'eastoutside', 'Orientation', 'vertical'); + box on + grid on + hold off + + sub{3} = subplot(figure_count,1,3); + hold on + if ~isempty(gps_profile_data.profiles) + plot(plot_data.GPS_Iridium.x, plot_data.GPS_Iridium.y, ... + 'color', [0 0 1], ... + 'marker', '+', ... + 'MarkerEdgeColor', [0 0 1], ... + 'linestyle', '-', ... + 'linewidth', 1, ... + 'markersize', 10); + + plot(plot_data.GPS_GPSRTC.x, plot_data.GPS_GPSRTC.y, ... + 'color', [0 1 1], ... + 'marker', '.', ... + 'MarkerEdgeColor', [0 1 1], ... + 'linestyle', '-', ... + 'linewidth', 1, ... + 'markersize', 10); + + else + dim = [0.5 0 .3 .3]; + str = 'No data!'; + a = annotation('textbox', dim, 'String', str, 'FitBoxToText', 'on'); + a.FontSize = 16; + clear dim str a + end + + ylabel('Time differences [sec]'); + legend('GPSFix-IRIDIUM', 'GPSFix-GPSRTC'); + legend('Location', 'eastoutside', 'Orientation', 'vertical'); + grid on + box on + hold off + + sub1_pos = get(sub{1}, 'Position'); + sub2_pos = get(sub{2}, 'Position'); + sub3_pos = get(sub{3}, 'Position'); +% leg1_pos = get(leg1, 'Position'); + set(sub{1}, 'Position', [sub1_pos(1:2) 0.6652 sub1_pos(4)]); + set(sub{2}, 'Position', [sub2_pos(1:2) 0.6652 sub2_pos(4)]); + set(sub{3}, 'Position', [sub3_pos(1:2) 0.6652 sub3_pos(4)]); + +% % align all legends +% leg1_pos = get(hleg1, 'position'); +% leg2_pos = get(hleg2, 'position'); +% leg3_pos = get(hleg3, 'position'); +% legend_positions = sortrows([ leg1_pos; leg2_pos; leg3_pos ], 1); +% leg_width = max(legend_positions(:, 3)); +% max_height = max(legend_positions(:, 4)); +% +% set(hleg1, 'units', 'normalized', 'position', [ legend_positions(1, 1) leg1_pos(2) leg_width max_height ]); +% set(hleg2, 'units', 'normalized', 'position', [ legend_positions(1, 1) leg2_pos(2) leg_width max_height ]); +% set(hleg3, 'units', 'normalized', 'position', [ legend_positions(1, 1) leg3_pos(2) leg_width max_height ]); +% +% % set width of all plots to the same +% sub1_pos = get(sub{1}, 'position'); +% sub2_pos = get(sub{2}, 'position'); +% sub3_pos = get(sub{3}, 'position'); +% sub_positions = sortrows([ sub1_pos; sub2_pos; sub3_pos ], 3); +% max_width = max(sub_positions(:, 3)); +% set(sub{1}, 'units', 'normalized', 'position', [ sub1_pos(1:2) legend_positions(1, 1) sub1_pos(4) ]); +% set(sub{2}, 'units', 'normalized', 'position', [ sub2_pos(1:2) legend_positions(1, 1) sub2_pos(4) ]); +% set(sub{3}, 'units', 'normalized', 'position', [ sub3_pos(1:2) legend_positions(1, 1) sub3_pos(4) ]); + + sub = cellfun(@set_x_axis, sub, 'UniformOutput', false); + +% sub{3} = subplot(4, 1, 4); +% hold on +% %plot(combined_xmit_times); +% plot(xmit_profile_number, xmit_ascent_start_time_ori, 'marker', '+'); +% plot(xmit_profile_number, xmit_ascent_end_time, 'marker', 'o'); +% plot(xmit_profile_number, xmit_descent_start_time, 'marker', 'x'); +% plot(xmit_profile_number, xmit_parking_start_time, 'marker', '*'); +% plot(xmit_profile_number, xmit_upcast_start_time, 'marker', '+'); +% plot(xmit_profile_number, xmit_surface_start_time, 'marker', '+'); +% legend('ascentstart', 'ascentend', 'descentstart', 'parkingstart', 'upcaststart', 'surfacestart'); +% box on +% grid on +% hold off + + + + % create main title +% heading = utils.suplabel(... +% [ 'Float ' ... +% num2str(p_nemo_data{o_float}{1}.FLOAT_IDENTIFICATION.float_serial_number) ... +% ' @ ' ... +% datestr(datetime('now', 'Format', 'dd-MMM-yyyy HH:mm:SS')) ... +% ], 't'); +% set(heading, 'FontSize', 18); + + % store plot + + image_filename = [p_config.PLOT_TIMING_INFORMATION.image_filename ... + num2str(p_nemo_data{o_float}{1}.FLOAT_IDENTIFICATION.float_serial_number)]; + figure_filename = [p_config.PLOT_TIMING_INFORMATION.figure_filename ... + num2str(p_nemo_data{o_float}{1}.FLOAT_IDENTIFICATION.float_serial_number)]; + + if (p_config.PLOT_TIMING_INFORMATION.save_figure) + set(gcf,'CreateFcn','set(gcf,''Visible'',''on'')'); + saveas(figure_id, ... + [directory_fig figure_filename]); + end + + + print(figure_id, [directory image_filename], image_type); + + close(figure_id); + +end + +boolean = true; + + function current_plot = set_x_axis(current_plot) + + % get min and max datevec + min_datenum = min(datenum(all_profile_data.best_guess_datevec)); + max_datenum = max(datenum(all_profile_data.best_guess_datevec)); + if (min_datenum == max_datenum) + max_datenum = max_datenum + 1; + end + diff_datenum = max_datenum - min_datenum; + if (diff_datenum > 365) + x_tick = [min_datenum:365:max_datenum]; + elseif (diff_datenum > 30) + x_tick = [min_datenum:30:max_datenum]; + elseif (diff_datenum > 5) + x_tick = [min_datenum:5:max_datenum]; + else + x_tick = [min_datenum:1:max_datenum]; + end + if (x_tick(end) < max_datenum) + x_tick = [ x_tick max_datenum ]; + end + x_tick_labels = cellstr(datestr(x_tick, 'yyyy-mm-dd')); + + if (~isempty(x_tick_labels)) + x_tick_labels{end} = ''; + end + + set(current_plot, 'XLimMode', 'manual', ... + 'XLim', [min_datenum, max_datenum], ... + 'XTick', x_tick, ... + 'XTickLabel', x_tick_labels); + + end + + function [value, index] = find_nearest_value_to(p_matrix, p_value) + [value, index] = min(abs(p_matrix - p_value)); + if (length(value) > 1) + index = min(index); + value = p_matrix(index); + end + end + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+postprocessing/+datetime/add_software_profile_times2rtc.m b/lib/vendor/nemo2profilelib/+nemo/+postprocessing/+datetime/add_software_profile_times2rtc.m new file mode 100644 index 0000000..2209e7b --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+postprocessing/+datetime/add_software_profile_times2rtc.m @@ -0,0 +1,100 @@ +function [ extended_nemo_data ] = add_software_profile_times2rtc( p_nemo_data ) +%ADD_SOFTWARE_PROFILE_TIMES_IN_RTC Summary of this function goes here +% Detailed explanation goes here + +%% Parameter check +if ~iscell(p_nemo_data) + disp([mfilename ': Please check your input parameter!']); +end + +%% Variables to convert +variable_names_to_convert = { ... + 'xmit_descent_start_time', ... + 'xmit_parking_start_time', ... + 'xmit_upcast_start_time', ... + 'xmit_ascent_end_time', ... + 'xmit_surface_start_time' ... +}; + +%% Calculate datevec + +extended_nemo_data = cellfun(@(x) extend_float(x), p_nemo_data, 'UniformOutput', false); + + function [ extended_float ] = extend_float(p_float) + extended_float = cellfun(@(x) extend_profile(x), p_float, 'UniformOutput', false); + end + + function [ extended_profile ] = extend_profile(p_profile) + + extended_profile = p_profile; + if isempty(p_profile) + return + end + if ~isfield(extended_profile, 'SECOND_ORDER_INFORMATION') + extended_profile.SECOND_ORDER_INFORMATION = struct(); + end + + % initialize variables + for i = 1:length(variable_names_to_convert) + extended_profile.SECOND_ORDER_INFORMATION.(variable_names_to_convert{i}) = NaN(1, 6); + end + extended_profile.SECOND_ORDER_INFORMATION.xmit_ascent_start_time = NaN(1, 6); + + % calculate software <-> rtc correction + % xmit_ascent_start_time + 10s = RTC @ 2000m + [rtc_datevec, software_datevec] = find_rtc_datevec(p_profile); + extended_profile.SECOND_ORDER_INFORMATION.xmit_ascent_start_time = datevec(datetime(rtc_datevec) - seconds(10)); + + drift_rtc_software = etime(rtc_datevec, software_datevec) + 10; + for i = 1:length(variable_names_to_convert) + extended_profile.SECOND_ORDER_INFORMATION.(variable_names_to_convert{i}) = ... + datevec(datetime([ ... + fliplr(extended_profile.OVERALL_MISSION_INFORMATION.start_date), ... + extended_profile.OVERALL_MISSION_INFORMATION.start_time 0 ... + ]) ... + + seconds(extended_profile.PROFILE_TECHNICAL_DATA.(variable_names_to_convert{i})) ... + + seconds(drift_rtc_software)); + + end + extended_profile.SECOND_ORDER_INFORMATION.difference_rtc_software = drift_rtc_software; + + + end + + function [ rtc_datevec, software_datevec ] = find_rtc_datevec(profile) + software_datevec = ... + datevec( ... + datetime([ ... + fliplr(profile.OVERALL_MISSION_INFORMATION.start_date), ... + profile.OVERALL_MISSION_INFORMATION.start_time 0 ... + ]) ... + + seconds(profile.PROFILE_TECHNICAL_DATA.xmit_ascent_start_time) ... + ); + software_time = software_datevec(4:6); + % get internal clock (RTC) @ 2000m + rtc_time = profile.PROFILE_DATA( ... + profile.PROFILE_DATA(:, 7) == max(profile.PROFILE_DATA(:, 7)), ... + 2:4); + % if there are multiple pressure levels get the one with the + % earliest time + rtc_time = sortrows(rtc_time, [1:3]); + if isempty(rtc_time) || isempty(software_datevec) % prevent errors if empty + rtc_datevec = NaN(1, 6); + software_datevec = NaN(1, 6); + return; + end + rtc_time = rtc_time(end, :); + + rtc_datevec = datevec(datetime([software_datevec(1:3), rtc_time])); + + if (any(rtc_time(1) == 21:23) && any(software_time(1) == 0:3)) + % RTC is more trustworthy, so reduce the software date by 1 + rtc_datevec = datevec(datetime([software_datevec(1:3), rtc_time]) - days(1)); + elseif (any(rtc_time(1) == 0:3) && any(software_time(1) == 21:23)) + % same here, but now add 1 day + rtc_datevec = datevec(datetime([software_datevec(1:3), rtc_time]) + days(1)); + end + end + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+postprocessing/+datetime/create_best_guess.m b/lib/vendor/nemo2profilelib/+nemo/+postprocessing/+datetime/create_best_guess.m new file mode 100644 index 0000000..eb6fa3d --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+postprocessing/+datetime/create_best_guess.m @@ -0,0 +1,148 @@ +function [ nemo_data ] = create_best_guess( p_nemo_data ) +%CREATE_BEST_GUESS Estimates and writes the best guess datetime to the given structure. +% The Iridium time is only being taken if no GPS time is available, and it is +% not the first profile after a series with xmit_upcast_status="ICE DETECTION". +% +% Priority that is being used: +% 1) GPS time +% 2) Iridium time +% 3) Internal float clock time +% +% +% Returns: p_nemo_data with an additional struct containing the best +% guess datetime. +% +% Parameters: +% p_nemo_data: Struct, containing all floats that should be edited. + +%% Initialize return variables +nemo_data = false; + +%% Parameter check +parameter_error = false; % indicates if a parameter error was raised + +if (ischar(p_nemo_data)) % read profiles using read_profiles.m + if (~isdir(p_nemo_data)) % parameter is not a directory + parameter_error = true; + else % read profiles + nemo_data = nemo.load.data(p_nemo_data); + end +else if (~iscell(p_nemo_data)) % raise error + parameter_error = true; + else + nemo_data = p_nemo_data; % store nemo data in return variable + end +end + +if (parameter_error) % display error message + disp([ mfilename ': Wrong parameter! Please provide either a ' ... + 'directory path or a struct created by nemo.load.data!']); + return; +else % do some cleanup + clear parameter_error; +end + +%% Estimate best datetime + +% run through every float/profile +nemo_data = p_nemo_data; +float_count = length(p_nemo_data); +for o_float = 1:float_count + + current_float = p_nemo_data{o_float}; + profile_count = length(current_float); + + for o_profile_index = 1:profile_count + % get internal clock (software) + software_datevec = ... + datevec( ... + datetime([ ... + fliplr(current_float{o_profile_index}.OVERALL_MISSION_INFORMATION.start_date), ... + current_float{o_profile_index}.OVERALL_MISSION_INFORMATION.start_time 0 ... + ]) ... + + seconds(current_float{o_profile_index}.PROFILE_TECHNICAL_DATA.xmit_surface_start_time) ... + ); + software_time = software_datevec(4:6); + % get internal clock (RTC) + rtc_time = current_float{o_profile_index}.PROFILE_DATA( ... + current_float{o_profile_index}.PROFILE_DATA(:, 7) == min(current_float{o_profile_index}.PROFILE_DATA(:, 7)), ... + 2:4); + % if there are multiple pressure levels get the one with the latest + % time + rtc_time = sortrows(rtc_time, [1:3]); + + if isempty(rtc_time) || isempty(software_datevec) % prevent errors if empty + current_float{o_profile_index}.SECOND_ORDER_INFORMATION.best_guess_datevec = NaN(1, 6); + current_float{o_profile_index}.SECOND_ORDER_INFORMATION.best_guess_datevec_src = 'none'; + continue; + end + + rtc_time = rtc_time(1, :); + + current_datevec = datevec(datetime([software_datevec(1:3), rtc_time])); + + if (any(rtc_time(1) == 21:23) && any(software_time(1) == 0:3)) + % RTC is more trustworthy, so reduce the software date by 1 + current_datevec = datevec(datetime([software_datevec(1:3), rtc_time]) - days(1)); + elseif (any(rtc_time(1) == 0:3) && any(software_time(1) == 21:23)) + % same here, but now add 1 day + current_datevec = datevec(datetime([software_datevec(1:3), rtc_time]) + days(1)); + end + + current_float{o_profile_index}.SECOND_ORDER_INFORMATION.best_guess_datevec = current_datevec; + current_float{o_profile_index}.SECOND_ORDER_INFORMATION.best_guess_datevec_src = 'internal_rtc'; +% current_datevec = []; +% if o_profile_index ~= 1 +% previous_profile = current_float{o_profile_index - 1}; +% else +% previous_profile = {}; +% end +% +% if o_profile_index < profile_count +% next_profile = current_float{o_profile_index + 1}; +% else +% next_profile = {}; +% end +% +% % decide which time should be taken +% +% if nemo.profile.has_gps(current_float{o_profile_index}) ... +% && nemo.profile.has_status(current_float{o_profile_index}, 'DONE') +% % get gps datetime +% current_datevec = nemo.profile.get_gps(current_float{o_profile_index}); +% current_datevec = current_datevec(8:13); +% current_source = 'gps'; +% elseif nemo.profile.has_iridium(current_float{o_profile_index}) ... +% && nemo.profile.has_status(current_float{o_profile_index}, 'DONE') ... +% && nemo.profile.has_status(previous_profile, 'DONE') ... +% && (isempty(next_profile) || nemo.profile.verify_iridium(current_float{o_profile_index}, next_profile)) +% % get iridium datetime +% current_datevec = nemo.profile.get_iridium(current_float{o_profile_index}); +% current_datevec = current_datevec(4:9); +% current_source = 'iridium'; +% else +% % get internal clock +% current_datevec = ... +% datevec( ... +% datetime([ ... +% %fliplr(current_float{o_profile_index}.DEPLOYMENT_INFO.deployment_date), ... +% %current_float{o_profile_index}.DEPLOYMENT_INFO.deployment_time 0 ... +% fliplr(current_float{o_profile_index}.OVERALL_MISSION_INFORMATION.start_date), ... +% current_float{o_profile_index}.OVERALL_MISSION_INFORMATION.start_time 0 ... +% ]) ... +% + seconds(current_float{o_profile_index}.PROFILE_TECHNICAL_DATA.xmit_surface_start_time) ... +% ); +% current_source = 'internal'; +% end +% % store datetime +% if (~isfield(current_float{o_profile_index}, 'SECOND_ORDER_INFORMATION')) +% current_float{o_profile_index}.SECOND_ORDER_INFORMATION = {}; +% end +% current_float{o_profile_index}.SECOND_ORDER_INFORMATION.best_guess_datevec = current_datevec; +% current_float{o_profile_index}.SECOND_ORDER_INFORMATION.best_guess_datevec_src = current_source; + end + nemo_data{o_float} = current_float; +end + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+postprocessing/+position/create_best_guess.m b/lib/vendor/nemo2profilelib/+nemo/+postprocessing/+position/create_best_guess.m new file mode 100644 index 0000000..40fb8de --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+postprocessing/+position/create_best_guess.m @@ -0,0 +1,53 @@ +function [ nemo_data ] = create_best_guess( p_nemo_data, p_estimation_method ) +%CREATE_BEST_GUESS_POSITION Creates a struct "best_guess_position" in every profile. +% Calls the following functions to estimate the best position that is +% available from the given profile data: +% 1) nemo.estimate_best_guess_position +% 2) nemo.estimate_unknown_positions +% WARNING: Please take care that this is the only possible calling procedure if +% you want to have consistent position data! +% +% Returns: p_nemo_data with an additional struct containing the best +% position that could be estimated. +% +% Requirements: +% - nemo library (this function is part of the library, so you should have it) +% - Mapping Toolbox +% + +%% Initialize return variables +nemo_data = false; + +%% Parameter check +parameter_error = false; % indicates if a parameter error was raised + +if (ischar(p_nemo_data)) % read profiles using read_profiles.m + if (~isdir(p_nemo_data)) % parameter is not a directory + parameter_error = true; + else % read profiles + nemo_data = nemo.load.data(p_nemo_data); + end +else if (~iscell(p_nemo_data)) % raise error + parameter_error = true; + else + nemo_data = p_nemo_data; % store nemo data in return variable + end +end + +if (parameter_error) % display error message + disp([ mfilename ': Wrong parameter! Please provide either a ' ... + 'directory path or a struct created by nemo.load.data!']); + return; +else % do some cleanup + clear parameter_error; +end + +%% create struct and fill with data + +% create best_guess_position struct +nemo_data = nemo.postprocessing.position.estimate_best_guess(p_nemo_data); +% estimate all positions that have no gps coordinate +nemo_data = nemo.postprocessing.position.estimate_unknowns(nemo_data, p_estimation_method); + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+postprocessing/+position/estimate_best_guess.m b/lib/vendor/nemo2profilelib/+nemo/+postprocessing/+position/estimate_best_guess.m new file mode 100644 index 0000000..76e347a --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+postprocessing/+position/estimate_best_guess.m @@ -0,0 +1,142 @@ +function [ estimated_nemo_data ] = estimate_best_guess( p_nemo_data ) +%ESTIMATE_BEST_GUESS_POSITION Gets the most trusted position and stores it into SECOND_ORDER_INFORMATION struct. +% WARNING: Do not use this function alone! If you want to have consistent +% position data, please use +% nemo.postprocessing.create_best_guess_position(). +% +% Creates a struct called best_guess_position in every profile and copies +% either gps or iridium coordinates to it. If the status of the profile +% is "ICE DETECTION", NaN will be placed into the member of the struct. +% Same is being done, when there is neither gps nor iridium available. +% +% Returns: Input struct, extended by best_guess_position member in SECOND_ORDER_INFORMATION. +% +% Parameters: +% p_nemo_data: Either has to be a multidimensional struct (created by +% nemo.load.data()) or a string, where the nemo float data +% is stored. + +%% Initialize return variables +estimated_nemo_data = false; + +%% Parameter check +parameter_error = false; % default value if function fails + +if (ischar(p_nemo_data)) % load profile data from file + if (~isdir(p_nemo_data)) % parameter is not a directory + parameter_error = true; + else % load data + estimated_nemo_data = nemo.load.data(p_nemo_data); + end +else if (~iscell(p_nemo_data)) % raise error + parameter_error = true; + else + estimated_nemo_data = p_nemo_data; % store nemo data in return variable + end +end + +if (parameter_error) % display error message + disp([ mfilename ': Wrong parameter! Please provide either a ' ... + 'directory path or a struct created by nemo.load.data.m!']); + return; +else % do some cleanup + clear parameter_error; +end + +%% Estimate best position +% Priority: +% 1) GPS +% 2) Iridium + +% setup format structure +section_name = 'SECOND_ORDER_INFORMATION'; +gps_section_name = 'SURFACE_GPS_DATA'; +gps_section_format_name = 'SURFACE_GPS_DATA_FORMAT'; +iridium_section_name = 'IRIDIUM_DATA'; +iridium_section_format_name = 'IRIDIUM_DATA_FORMAT'; + +% process every single float +for o_float_index = 1:length(estimated_nemo_data) + % get profile count of current float + profile_count = length(estimated_nemo_data{o_float_index}); + + % run through every profile + for o_profile_index = 1:profile_count + % get current profile + current_profile = ... + estimated_nemo_data{o_float_index}{o_profile_index}; + if o_profile_index ~= 1 + % get previous profile + previous_profile = ... + estimated_nemo_data{o_float_index}{o_profile_index - 1}; + else + previous_profile = {}; + end + + if o_profile_index < profile_count + % get previous profile + next_profile = ... + estimated_nemo_data{o_float_index}{o_profile_index + 1}; + else + next_profile = {}; + end + + % check status first + if (nemo.profile.has_status(current_profile, 'ICE DETECTION') ... + || (~isempty(previous_profile) && nemo.profile.has_status(previous_profile, 'ICE DETECTION'))) + % no position is available + current_profile.(section_name).best_guess_position_lat = NaN; + current_profile.(section_name).best_guess_position_lon = NaN; + current_profile.(section_name).best_guess_position_src = NaN; + elseif (nemo.profile.has_gps(current_profile)) + % gps available so take it + % get indices of lat and lon + index_lat = find(not(cellfun('isempty', ... + strfind(current_profile.(gps_section_format_name), 'lat') ... + ))); + index_lon = find(not(cellfun('isempty', ... + strfind(current_profile.(gps_section_format_name), 'lon') ... + ))); + current_profile.(section_name).best_guess_position_lat = ... + current_profile.(gps_section_name)(index_lat); + current_profile.(section_name).best_guess_position_lon = ... + current_profile.(gps_section_name)(index_lon); + current_profile.(section_name).best_guess_position_src = ... + 'gps'; + + clear index_lon index_lat; + elseif (nemo.profile.has_no_gps_but_iridium(current_profile) ... + && (isempty(next_profile) || nemo.profile.verify_iridium(current_profile, next_profile)) ... + ) + % iridium available + % get indices of lat and lon + index_lat = find(not(cellfun('isempty', ... + strfind(current_profile.(iridium_section_format_name), 'mean_lat') ... + ))); + index_lon = find(not(cellfun('isempty', ... + strfind(current_profile.(iridium_section_format_name), 'mean_lon') ... + ))); + current_profile.(section_name).best_guess_position_lat = ... + current_profile.(iridium_section_name)(index_lat); + current_profile.(section_name).best_guess_position_lon = ... + current_profile.(iridium_section_name)(index_lon); + current_profile.(section_name).best_guess_position_src = ... + 'iridium'; + clear index_lon index_lat; + else + current_profile.(section_name).best_guess_position_lat = NaN; + current_profile.(section_name).best_guess_position_lon = NaN; + current_profile.(section_name).best_guess_position_src = NaN; + end + + % store updated profile + estimated_nemo_data{o_float_index}{o_profile_index} = current_profile; + + end + + +end + + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+postprocessing/+position/estimate_unknowns.m b/lib/vendor/nemo2profilelib/+nemo/+postprocessing/+position/estimate_unknowns.m new file mode 100644 index 0000000..aee1e44 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+postprocessing/+position/estimate_unknowns.m @@ -0,0 +1,185 @@ +function [ fixed_nemo_data ] = estimate_unknowns( p_nemo_data, p_estimation_method ) +%ESTIMATE_UNKNOWN_POSITIONS Calculates the "best guess" profile positions. +% WARNING: Do not use this function alone! If you want to have consistent +% position data, please use +% nemo.postprocessing.position.create_best_guess. +% +% This function iterates over all nemo floats, given in the parameter +% p_nemo_data, determining the missing GPS information of profiles where +% the float did not come to the water surface. This is done via gcwaypts +% (Mapping Toolbox) between the known GPS/Iridium positions. They have been +% stored in the SECOND_ORDER_INFORMATION struct by +% nemo.postprocessing.position.estimate_best_guess(). +% If p_nemo_data is a string, it reads the profiles with the +% read_profiles function. So be sure that you added read_profiles.m to +% your search path! +% +% Returns: p_nemo_data with interpolated positions, false on fail. +% +% Parameters: +% p_nemo_data: Either has to be a multidimensional struct (created by +% nemo.load.data()) or a string, where the nemo float data +% is stored. + + +%% Initialize return variables +fixed_nemo_data = false; % default value if function fails + +%% Check passed parameter and read profiles if necessary +parameter_error = false; % indicates if a parameter error was raised + +if (ischar(p_nemo_data)) % read profiles using read_profiles.m + if (~isdir(p_nemo_data)) % parameter is not a directory + parameter_error = true; + else % read profiles + fixed_nemo_data = nemo.load.data(p_nemo_data); + end +else if (~iscell(p_nemo_data)) % raise error + parameter_error = true; + else + fixed_nemo_data = p_nemo_data; % store nemo data in return variable + end +end + +if (parameter_error) % display error message + disp([ mfilename ': Wrong parameter! Please provide either a ' ... + 'directory path or a struct created by read_profiles.m!']); + return; +else % do some cleanup + clear parameter_error; +end + +% If estimation method nan is chosen, do nothing because there is already +% NaN given in the unknown positions by +% nemo.postprocessing.position.estimate_best_guess(). +if (strcmp(p_estimation_method, 'nan')) + return +end + +%% Position estimation + +for o_float_index = 1:length(fixed_nemo_data) + + current_float = fixed_nemo_data{o_float_index}; % stores current float in tmp var + profile_count = length(current_float); + + + % find all pairs that have incomplete positions in between + + o_profile_index = 1; + + while (o_profile_index <= profile_count) % process every profile + if (~isfield(current_float{o_profile_index}, 'SECOND_ORDER_INFORMATION') ... + || ~isfield(current_float{o_profile_index}.SECOND_ORDER_INFORMATION, 'best_guess_position_lat') ... + || ~isfield(current_float{o_profile_index}.SECOND_ORDER_INFORMATION, 'best_guess_position_lon') ... + || ~isfield(current_float{o_profile_index}.SECOND_ORDER_INFORMATION, 'best_guess_position_src')) % security check + disp([mfilename ': No best guess position available in float ' num2str(current_float{o_profile_index}.FLOAT_IDENTIFICATION.float_serial_number) ... + ' profile ' num2str(current_float{o_profile_index}.PROFILE_TECHNICAL_DATA.xmit_profile_number) '! Skipping...']); + o_profile_index = o_profile_index + 1; + continue + end + + % check for NaN, if it is the first index, the deployment position + % will be the first position for the interpolation + index_last_position = o_profile_index; + while (o_profile_index <= profile_count) ... + && isnan(current_float{o_profile_index}.SECOND_ORDER_INFORMATION.best_guess_position_lat) + o_profile_index = o_profile_index + 1; + end + index_next_position = o_profile_index; + + if (index_last_position == index_next_position) % no gap has been found + % increase iteration var and continue + o_profile_index = o_profile_index + 1; + continue; + else + % a gap has been found, so the last position index is wrong! + index_last_position = index_last_position - 1; + end + + % a gap has been found + if (index_last_position == 0) % use deployment position as last position + last_position.lat = current_float{1}.DEPLOYMENT_INFO.deployment_position(1); + last_position.lon = current_float{1}.DEPLOYMENT_INFO.deployment_position(2); + last_position.src = 'deployment'; + last_position.datetime = 0; + else + last_position.lat = current_float{index_last_position}.SECOND_ORDER_INFORMATION.best_guess_position_lat; + last_position.lon = current_float{index_last_position}.SECOND_ORDER_INFORMATION.best_guess_position_lon; + last_position.src = current_float{index_last_position}.SECOND_ORDER_INFORMATION.best_guess_position_src; + last_position.datetime = current_float{index_last_position}.PROFILE_TECHNICAL_DATA.xmit_surface_start_time; + end + + % if there is no position at all of the float, do not create best + % guess for it + if index_next_position > profile_count + break; + end + next_position.lat = current_float{index_next_position}.SECOND_ORDER_INFORMATION.best_guess_position_lat; + next_position.lon = current_float{index_next_position}.SECOND_ORDER_INFORMATION.best_guess_position_lon; + next_position.src = current_float{index_next_position}.SECOND_ORDER_INFORMATION.best_guess_position_src; + next_position.datetime = current_float{index_next_position}.PROFILE_TECHNICAL_DATA.xmit_surface_start_time; + + intermediate_profile_count = index_next_position - index_last_position; + + switch p_estimation_method + case 'position based' + position_based_estimation + case 'time based' + time_based_estimation + otherwise + warning([mfilename ': Estimation method not available. Using position based instead...']); + position_based_estimation + end + + + + end + + % store the corrected profiles into struct that will be returned + fixed_nemo_data{o_float_index} = current_float; + + +end + + function position_based_estimation + % calculate intermediate points + [intermediate_points_lat, intermediate_points_lon] = gcwaypts( ... + last_position.lat, ... + last_position.lon, ... + next_position.lat, ... + next_position.lon, ... + intermediate_profile_count ... % function requires legs, not points + ); + + % store all positions in best guess variable + for i = 1:intermediate_profile_count + current_float{index_last_position + i}.SECOND_ORDER_INFORMATION.best_guess_position_lat = intermediate_points_lat(i + 1); + current_float{index_last_position + i}.SECOND_ORDER_INFORMATION.best_guess_position_lon = intermediate_points_lon(i + 1); + current_float{index_last_position + i}.SECOND_ORDER_INFORMATION.best_guess_position_src = [ last_position.src '_gcwaypts_' next_position.src ]; + end + end + + function time_based_estimation + difference_in_seconds = next_position.datetime - last_position.datetime; + + [intermediate_points_lat, intermediate_points_lon] = gcwaypts( ... + last_position.lat, ... + last_position.lon, ... + next_position.lat, ... + next_position.lon, ... + difference_in_seconds + 1 ... % function requires legs, not points + ); + + for i = 1:intermediate_profile_count-1 + profile_datetime = current_float{index_last_position + i}.PROFILE_TECHNICAL_DATA.xmit_surface_start_time; + profile_to_last_position = profile_datetime - last_position.datetime; + current_float{index_last_position + i}.SECOND_ORDER_INFORMATION.best_guess_position_lat = intermediate_points_lat(profile_to_last_position); + current_float{index_last_position + i}.SECOND_ORDER_INFORMATION.best_guess_position_lon = intermediate_points_lon(profile_to_last_position); + current_float{index_last_position + i}.SECOND_ORDER_INFORMATION.best_guess_position_src = [ last_position.src '_gcwaypts_' next_position.src ]; + end + + end + +end % estimate_unknowns + diff --git a/lib/vendor/nemo2profilelib/+nemo/+postprocessing/delete_profile.m b/lib/vendor/nemo2profilelib/+nemo/+postprocessing/delete_profile.m new file mode 100644 index 0000000..2b772cd --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+postprocessing/delete_profile.m @@ -0,0 +1,68 @@ +function [ filtered_nemo_data ] = delete_profile( p_nemo_data, p_float_serial, p_profile_number ) +%DELETE_PROFILE Deletes the given profile from the given float. +% +% Parameters: +% p_nemo_data Nemo data, loaded by nemo.load.data(). +% p_float_number Input float serial number. +% p_profile_number Profile to be deleted. +% +% Returns: +% filtered_nemo_data Nemo data with deleted profile. +% +% +% TODO: SECURITY CHECKS MISSING + +% search float +float_count = length(p_nemo_data); +current_float = 1; +while current_float <= float_count + current_serial = ... + p_nemo_data{current_float}{1}.FLOAT_IDENTIFICATION.float_serial_number; + if current_serial == p_float_serial + break + else + current_float = current_float + 1; + end +end + +if current_float > float_count + filtered_nemo_data = p_nemo_data; + disp([mfilename ': Float could not be found in data, skipping...']); + return +end + +% found the right float, now find the profile +profile_count = length(p_nemo_data{current_float}); +current_profile = 1; +while current_profile <= profile_count + current_profile_number = ... + p_nemo_data{current_float}{current_profile}.PROFILE_TECHNICAL_DATA.xmit_profile_number; + if current_profile_number == p_profile_number + break + else + current_profile = current_profile + 1; + end +end + +if current_profile > profile_count + filtered_nemo_data = p_nemo_data; + disp([mfilename ': Profile could not be found in data, skipping...']); + return +end + +filtered_nemo_data = p_nemo_data; +if current_profile == profile_count + filtered_float = p_nemo_data{current_float}(1:end-1); +elseif current_profile == 1 + filtered_float = p_nemo_data{current_float}(2:end); +else + filtered_float = {}; + filtered_float{1:current_profile-1} = ... + p_nemo_data{current_float}(1:current_profile-1); + filtered_float{current_profile+1:end} = ... + p_nemo_data{current_float}(current_profile+1:end); +end +filtered_nemo_data{current_float} = filtered_float; + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+postprocessing/quality_check.m b/lib/vendor/nemo2profilelib/+nemo/+postprocessing/quality_check.m new file mode 100644 index 0000000..c8ef6e2 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+postprocessing/quality_check.m @@ -0,0 +1,105 @@ +function [ commented_nemo_data ] = quality_check( p_nemo_data ) +%QUALITY_CHECK Does several quality checks. +% 1) Adds two fields to the SECOND_ORDER_INFORMATION block: +% fieldname values +% xmit_transmittion_type nrt, delayed, unknown +% ice_detected 0, 1 +% +% 2) Removes profiles where the best_guess_datevec is before the +% deployment time. +% +% Parameters: +% p_nemo_data: The nemo data. +% +% Returns: +% commented_nemo_data: The nemo data with applied changes. + +%% Check passed parameter +parameter_error = false; + +if (~iscell(p_nemo_data)) + parameter_error = true; +end + +if (parameter_error) + disp([mfilename ': Parameter error. Please check your input parameters.']); +end +clear parameter_error; + +float_count = length(p_nemo_data); +commented_nemo_data = p_nemo_data; + +for o_float = 1:float_count + + profile_count = length(commented_nemo_data{o_float}); + current_float = commented_nemo_data{o_float}; + + for o_profile_index = 1:profile_count % process all profiles + + %% Check if profile has been sent directly after recording + + if (o_profile_index < profile_count ... + && ~nemo.profile.verify_iridium( ... + current_float{o_profile_index}, ... + current_float{o_profile_index + 1}) ... + ) + + switch (nemo.profile.get_status(current_float{o_profile_index})) + case 'DONE' + % it has been sent later on, no clue why + current_float{o_profile_index}.SECOND_ORDER_INFORMATION.xmit_transmission_type = 'delayed'; + current_float{o_profile_index}.SECOND_ORDER_INFORMATION.ice_detected = 0; + case 'ICE DETECTION' + current_float{o_profile_index}.SECOND_ORDER_INFORMATION.xmit_transmission_type = 'delayed'; + current_float{o_profile_index}.SECOND_ORDER_INFORMATION.ice_detected = 1; + otherwise + current_float{o_profile_index}.SECOND_ORDER_INFORMATION.xmit_transmission_type = 'unknown'; + current_float{o_profile_index}.SECOND_ORDER_INFORMATION.ice_detected = 0; + end + else + current_float{o_profile_index}.SECOND_ORDER_INFORMATION.xmit_transmission_type = 'nrt'; + switch (nemo.profile.get_status(current_float{o_profile_index})) + case 'DONE' + current_float{o_profile_index}.SECOND_ORDER_INFORMATION.ice_detected = 0; + case 'ICE DETECTION' + current_float{o_profile_index}.SECOND_ORDER_INFORMATION.ice_detected = 1; + otherwise + current_float{o_profile_index}.SECOND_ORDER_INFORMATION.ice_detected = 0; + end + end + + %% Check if profile time is before deployment time + deploy_datenum = datenum([ ... + fliplr(current_float{o_profile_index}.DEPLOYMENT_INFO.deployment_date), ... + current_float{o_profile_index}.DEPLOYMENT_INFO.deployment_time, ... + 0 ... + ]); + profile_datenum = datenum(current_float{o_profile_index}.SECOND_ORDER_INFORMATION.best_guess_datevec); + if (profile_datenum < deploy_datenum) + disp(['Removed profile ' num2str(current_float{o_profile_index}.PROFILE_TECHNICAL_DATA.xmit_profile_number) ... + ' of float ' num2str(current_float{o_profile_index}.FLOAT_IDENTIFICATION.float_serial_number) ... + ' because it was recorded before deployment!']); + current_float{o_profile_index} = {}; + end + +% if (nemo.profile.positions_differ_by(commented_nemo_data{o_profile_index}, 50)) +% commented_nemo_data{o_profile_index}.COMMENT.position = 'gps and iridium differ by more than 50km'; +% end + + end + + %% Remove all empty profiles + current_float = current_float(~cellfun(@(x) isempty(x), current_float)); + + %% Save current float + commented_nemo_data{o_float} = current_float; +end + +%% Manual quality checks + + +%% Remove all empty floats +commented_nemo_data = commented_nemo_data(~cellfun(@(x) isempty(x), commented_nemo_data)); + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+postprocessing/sort_profile_data.m b/lib/vendor/nemo2profilelib/+nemo/+postprocessing/sort_profile_data.m new file mode 100644 index 0000000..366b859 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+postprocessing/sort_profile_data.m @@ -0,0 +1,27 @@ +function [ sorted_nemo_data ] = sort_profile_data( p_nemo_data ) +%SORT_PROFILE_DATA Sorts the CTD profile data of the given floats. +% Uses nemo.float.sort_profuile_data function to sort each float +% individually. +% +% Parameters: +% p_nemo_data: The nemo data that will be sorted. +% +% Returns: +% sorted_nemo_data: A copy of p_nemo_data with sorted CTD data. + + +%% Initialize return variable +sorted_nemo_data = false; + +%% Parameter check +if (~iscell(p_nemo_data)) + disp([mfilename ': Unknown input type! Please check your input parameter!']); + return +end + +%% Sort profile data by time +sorted_nemo_data = cellfun(@nemo.float.sort_profile_data, p_nemo_data, 'UniformOutput', false); + + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/calc_rtc_software_difference.m b/lib/vendor/nemo2profilelib/+nemo/+profile/calc_rtc_software_difference.m new file mode 100644 index 0000000..de7019e --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+profile/calc_rtc_software_difference.m @@ -0,0 +1,40 @@ +function [ difference_rtc_software ] = calc_rtc_software_difference( p_profile ) +%CALC_RTC_SOFTWARE_DIFFERENCE Summary of this function goes here +% Detailed explanation goes here + +software_datevec = ... + datevec( ... + datetime([ ... + fliplr(p_profile.OVERALL_MISSION_INFORMATION.start_date), ... + p_profile.OVERALL_MISSION_INFORMATION.start_time 0 ... + ]) ... + + seconds(p_profile.PROFILE_TECHNICAL_DATA.xmit_ascent_start_time) ... +); +software_time = software_datevec(4:6); +% get internal clock (RTC) @ 2000m +rtc_time = p_profile.PROFILE_DATA( ... + p_profile.PROFILE_DATA(:, 7) == max(p_profile.PROFILE_DATA(:, 7)), ... + 2:4); +% if there are multiple pressure levels get the one with the +% earliest time +rtc_time = sortrows(rtc_time, [1:3]); +if isempty(rtc_time) || isempty(software_datevec) % prevent errors if empty + difference_rtc_software = NaN; + return; +end +rtc_time = rtc_time(end, :); + +rtc_datevec = datevec(datetime([software_datevec(1:3), rtc_time])); + +if (any(rtc_time(1) == 21:23) && any(software_time(1) == 0:3)) + % RTC is more trustworthy, so reduce the software date by 1 + rtc_datevec = datevec(datetime([software_datevec(1:3), rtc_time]) - days(1)); +elseif (any(rtc_time(1) == 0:3) && any(software_time(1) == 21:23)) + % same here, but now add 1 day + rtc_datevec = datevec(datetime([software_datevec(1:3), rtc_time]) + days(1)); +end + +difference_rtc_software = etime(rtc_datevec, software_datevec) + 10; + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/compare.m b/lib/vendor/nemo2profilelib/+nemo/+profile/compare.m new file mode 100644 index 0000000..0c2872c --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+profile/compare.m @@ -0,0 +1,79 @@ +function [ boolean ] = compare( p_profile_a, p_profile_b ) +%COMPARE Checks if the input profiles are equal. +% Both input profiles are compared to each other. If one of the +% underlying struct/cell/double values are not equal, it will return false. +% +% Parameters: +% p_profile_a, p_profile_b: Profiles to be compared. Ideally they are +% previously loaded by nemo.profile.load() +% +% Returns: +% boolean: True if both profiles are equal. + +%% Initialize return variables +boolean = true; +%% TODO: Parameter check + +%% Comparing profiles + +compare_variables(p_profile_a, p_profile_b); + + function compare_variables(p_a, p_b) + + switch class(p_a) + case {'double', 'logical'} + if (all(all(isnan(p_a))) && all(all(isnan(p_b)))) + return; + elseif (all(all(isnan(p_a))) && ~all(all(isnan(p_b)))) + boolean = false; + return; + end + if (~strcmp(num2str(p_a), num2str(p_b))) + boolean = false; + return; + end + case 'char' + if (~strcmp(p_a, p_b)) + boolean = false; + return; + end + case 'struct' + fieldnames_a = fieldnames(p_a); + fieldnames_b = fieldnames(p_b); + if (~isequaln(fieldnames_a, fieldnames_b)) + boolean = false; + return; + end + for i = 1:length(fieldnames_a) + compare_variables(p_a.(fieldnames_a{i}), p_b.(fieldnames_b{i})); + end + + case 'cell' + [la, ca] = size(p_a); + [lb, cb] = size(p_b); + if ((la ~= lb) || (ca ~= cb)) + boolean = false; + return; + end + if ((ca > 1) && (la > 1)) + for i = 1:la + for k = 1:ca + compare_variables(p_a{i}{k}, p_b{i}{k}); + end + end + else + la = size(p_a); + for i = 1:la + compare_variables(p_a{i}, p_b{i}); + end + end + end + + + + + + end + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/find.m b/lib/vendor/nemo2profilelib/+nemo/+profile/find.m new file mode 100644 index 0000000..8e3f442 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+profile/find.m @@ -0,0 +1,19 @@ +function [ profile, index ] = find( p_float, p_profile_number ) +%FIND Searches the profile with p_profile_number in the given float. +% Returns the profile and its index in the p_float structure. +% If not found, profile contains false. + +index = cellfun(@(x) x.PROFILE_TECHNICAL_DATA.xmit_profile_number == p_profile_number, p_float); +if (~any(index)) + profile = false; + return +elseif (sum(index) > 1) + disp(['Multiple profiles found: ' index]); + profile = false; + return +end + +profile = p_float{index}; + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/get_gps.m b/lib/vendor/nemo2profilelib/+nemo/+profile/get_gps.m new file mode 100644 index 0000000..331c589 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+profile/get_gps.m @@ -0,0 +1,32 @@ +function [ gps_data ] = get_gps( p_profile ) +%GET_GPS Returns gps data block of the given profile. +% +% Returns: Double of size 1x15 if gps coordinates are available. +% Displays a warning if parameters are wrong, but returns false! +% +% Parameters: +% p_profile: The nemo profile where gps data is being extracted. + +%% Initializing return variables +gps_data = false; + +%% Parameter check + +if (~isstruct(p_profile)) + disp([mfilename ': Wrong parameter. Please check your input. Returning false!']); + return +end + +%% Check if GPS section is available +if (~isfield(p_profile, 'SURFACE_GPS_DATA')) + gps_data = false; + return +else + % store all values + gps_data = p_profile.SURFACE_GPS_DATA; +end + +return; + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/get_iridium.m b/lib/vendor/nemo2profilelib/+nemo/+profile/get_iridium.m new file mode 100644 index 0000000..4a2fea0 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+profile/get_iridium.m @@ -0,0 +1,32 @@ +function [ iridium_mean_data ] = get_iridium( p_profile ) +%GET_IRIDIUM Returns iridium data block of the given profile. +% +% Returns: Double of size 1x9 if iridium information is available. +% Displays a warning if parameters are wrong, but returns false! +% +% Parameters: +% p_profile: The nemo profile where gps data is being extracted. + +%% Initializing return variables +iridium_mean_data = false; + +%% Parameter check + +if (~isstruct(p_profile)) + disp([mfilename ': Wrong parameter. Please check your input. Returning false!']); + return +end + +%% Check if iridium section is available +if (~isfield(p_profile, 'IRIDIUM_DATA')) + iridium_mean_data = false; + return +else + % store all values + iridium_mean_data = p_profile.IRIDIUM_DATA; +end + +return; + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/get_rtc_datevec_of_ctd_sample.m b/lib/vendor/nemo2profilelib/+nemo/+profile/get_rtc_datevec_of_ctd_sample.m new file mode 100644 index 0000000..2546ed0 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+profile/get_rtc_datevec_of_ctd_sample.m @@ -0,0 +1,84 @@ +function [ sample_datevec, rtc_datevecs ] = get_rtc_datevec_of_ctd_sample( p_profile, p_sample_index ) +%GET_TIMESTAMP_OF_CTD_SAMPLE Summary of this function goes here +% Detailed explanation goes here + +%% Parameter check +if ~exist('p_sample_index', 'var') + p_sample_index = 1; +end + +if ~isstruct(p_profile) || ~isa(p_sample_index, 'double') + disp([mfilename ': Please check your input parameter!']); + return; +end + +%% Initialize return variable +sample_datevec = NaN(1, 6); + +%% Find CTD sample + +ctd_profile = []; + +if ~isempty(p_profile.PROFILE_DATA) + p = nemo.profile.sort_profile_data(p_profile); + ctd_profile = p.PROFILE_DATA; + clear p +else + return +end + +software_datevec = ... + datevec( ... + datetime([ ... + fliplr(p_profile.OVERALL_MISSION_INFORMATION.start_date), ... + p_profile.OVERALL_MISSION_INFORMATION.start_time 0 ... + ]) ... + + seconds(p_profile.PROFILE_TECHNICAL_DATA.xmit_ascent_start_time) ... +); +software_time = software_datevec(4:6); +% get internal clock (RTC) @ 2000m +rtc_time = p_profile.PROFILE_DATA( ... + p_profile.PROFILE_DATA(:, 7) == max(p_profile.PROFILE_DATA(:, 7)), ... + 2:4); +% if there are multiple pressure levels get the one with the +% earliest time +rtc_time = sortrows(rtc_time, [1:3]); +if isempty(rtc_time) || isempty(software_datevec) % prevent errors if empty + return; +end +rtc_time = rtc_time(end, :); + +rtc_datevec = datevec(datetime([software_datevec(1:3), rtc_time])); + +if (any(rtc_time(1) == 21:23) && any(software_time(1) == 0:3)) + % RTC is more trustworthy, so reduce the software date by 1 + rtc_datevec = datevec(datetime([software_datevec(1:3), rtc_time]) - days(1)); +elseif (any(rtc_time(1) == 0:3) && any(software_time(1) == 21:23)) + % same here, but now add 1 day + rtc_datevec = datevec(datetime([software_datevec(1:3), rtc_time]) + days(1)); +end + + + +% get ctd, rtc time +rtc_times = p_profile.PROFILE_DATA(:, 2:4); +% add pseudo date to profile samples +pseudo_datenums = datenum([ ... + zeros(size(ctd_profile, 1), 2), ... + flipud((1:size(ctd_profile, 1))'), ... + rtc_times ... +]); + +diff_in_seconds = mod(flipud(diff(flipud(pseudo_datenums))), 1) * 86400; + +rtc_datevecs = [rtc_datevec]; +for i = 1:length(diff_in_seconds) + rtc_datevecs = [ rtc_datevecs; datevec(datetime(rtc_datevecs(i, :)) + seconds(diff_in_seconds(end - i + 1)))]; +end +rtc_datevecs = flipud(rtc_datevecs); + +sample_datevec = rtc_datevecs(p_sample_index); + + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/get_status.m b/lib/vendor/nemo2profilelib/+nemo/+profile/get_status.m new file mode 100644 index 0000000..b4fc659 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+profile/get_status.m @@ -0,0 +1,13 @@ +function [ status ] = get_status( p_profile ) +%GET_STATUS Returns the xmit_upcast_status value of the given profile. +% +% Returns: String, containing the profiles xmit_upcast_status. +% +% Parameters: +% p_profile: The nemo profile that is being checked. + +status = strtrim(p_profile.PROFILE_TECHNICAL_DATA.xmit_upcast_status); + + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/has_best_guess.m b/lib/vendor/nemo2profilelib/+nemo/+profile/has_best_guess.m new file mode 100644 index 0000000..fbf8b41 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+profile/has_best_guess.m @@ -0,0 +1,36 @@ +function [ boolean ] = has_best_guess( p_profile ) +%HAS_BEST_GUESS Checks if profile has best guess coordinates as well as time. +% +% Returns: True if best guess information is available. +% Displays a warning if parameters are wrong, but returns false! +% +% Parameters: +% p_profile: The nemo profile that is being checked. + +%% Initializing return variables +boolean = false; + +%% Parameter check + +if (~isstruct(p_profile)) + disp([mfilename ': Wrong parameter. Please check your input. Returning false!']); +end + +%% Check if GPS section is available +if (~isfield(p_profile, 'SECOND_ORDER_INFORMATION') ... + || ~isfield(p_profile.SECOND_ORDER_INFORMATION, 'best_guess_position_lat') ... + || ~isfield(p_profile.SECOND_ORDER_INFORMATION, 'best_guess_position_lon') ... + || ~isfield(p_profile.SECOND_ORDER_INFORMATION, 'best_guess_position_src')) + || ~isfield(p_profile.SECOND_ORDER_INFORMATION, 'best_guess_datevec')) + || ~isfield(p_profile.SECOND_ORDER_INFORMATION, 'best_guess_datevec_src')) + boolean = false; + return +else + % check if the values are available + boolean = ~isnan(p_profile.SECOND_ORDER_INFORMATION.best_guess_position_lat); +end + +return; + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/has_gps.m b/lib/vendor/nemo2profilelib/+nemo/+profile/has_gps.m new file mode 100644 index 0000000..1e7a706 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+profile/has_gps.m @@ -0,0 +1,31 @@ +function [ boolean ] = has_gps( p_profile ) +%HAS_GPS Checks if profile has gps coordinates. +% +% Returns: True if gps coordinates are available. +% Displays a warning if parameters are wrong, but returns false! +% +% Parameters: +% p_profile: The nemo profile that is being checked. + +%% Initializing return variables +boolean = false; + +%% Parameter check + +if (~isstruct(p_profile)) + disp([mfilename ': Wrong parameter. Please check your input. Returning false!']); +end + +%% Check if GPS section is available +if (~isfield(p_profile, 'SURFACE_GPS_DATA')) + boolean = false; + return +else + % check if the values are available + boolean = (~any(isnan(p_profile.SURFACE_GPS_DATA)) && ~isempty(p_profile.SURFACE_GPS_DATA)); +end + +return; + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/has_iridium.m b/lib/vendor/nemo2profilelib/+nemo/+profile/has_iridium.m new file mode 100644 index 0000000..707128b --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+profile/has_iridium.m @@ -0,0 +1,31 @@ +function [ boolean ] = has_iridium( p_profile ) +%HAS_IRIDIUM Checks if profile has iridium coordinates. +% +% Returns: True if iridium coordinates are available. +% Displays a warning if parameters are wrong, but returns false! +% +% Parameters: +% p_profile: The nemo profile that is being checked. + +%% Initializing return variables +boolean = false; + +%% Parameter check + +if (~isstruct(p_profile)) + disp([mfilename ': Wrong parameter. Please check your input. Returning false!']); +end + +%% Check coordinates +if (~isfield(p_profile, 'IRIDIUM_DATA')) + boolean = false; + return +else + % check if the values are available + boolean = (~any(isnan(p_profile.IRIDIUM_DATA))); +end + +return; + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/has_no_gps_but_iridium.m b/lib/vendor/nemo2profilelib/+nemo/+profile/has_no_gps_but_iridium.m new file mode 100644 index 0000000..7f4c56b --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+profile/has_no_gps_but_iridium.m @@ -0,0 +1,52 @@ +function [ boolean ] = has_no_gps_but_iridium( p_profile ) +%HAS_NO_GPS_BUT_IRIDIUM Checks if profile has iridium coordinates, but no gps. +% +% Returns: True if iridium but no gps coordinates are available. +% Displays a warning if parameters are wrong, but returns false! +% +% Parameters: +% p_profile: The nemo profile that is being checked. + +%% Initializing return variables +boolean = false; + +%% Parameter check + +if (~isstruct(p_profile)) + disp([mfilename ': Wrong parameter. Please check your input. Returning false!']); +end + +%% Check if gps section and values are available +if (isfield(p_profile, 'SURFACE_GPS_DATA')) + % check if the values are available + gps_available = (~any(isnan(p_profile.SURFACE_GPS_DATA))) && ~isempty(p_profile.SURFACE_GPS_DATA); +else + gps_available = false; +end + +%% Check if iridium is available +if (isfield(p_profile, 'IRIDIUM_DATA') && ~isempty(p_profile.IRIDIUM_DATA)) + % check if the values are available + % get indices of lat and lon + index_lat = find(not(cellfun('isempty', ... + strfind(p_profile.('IRIDIUM_DATA_FORMAT'), 'mean_lat') ... + ))); + index_lon = find(not(cellfun('isempty', ... + strfind(p_profile.('IRIDIUM_DATA_FORMAT'), 'mean_lon') ... + ))); + boolean = (~isnan(p_profile.('IRIDIUM_DATA')(index_lat)) && ~isnan(p_profile.('IRIDIUM_DATA')(index_lon))); + + if (boolean && ~gps_available) + boolean = true; + return + else + boolean = false; + return; + end +else + boolean = false; + return; +end + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/has_no_iridium_but_gps.m b/lib/vendor/nemo2profilelib/+nemo/+profile/has_no_iridium_but_gps.m new file mode 100644 index 0000000..d2debcd --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+profile/has_no_iridium_but_gps.m @@ -0,0 +1,31 @@ +function [ boolean ] = has_no_iridium_but_gps( p_profile ) +%HAS_NO_IRIDIUM_BUT_GPS Checks if profile has gps coordinates, but no iridium. +% +% Returns: True if gps but no iridium coordinates are available. +% Displays a warning if parameters are wrong, but returns false! +% +% Parameters: +% p_profile: The nemo profile that is being checked. + +%% Initializing return variables +boolean = false; + +%% Parameter check + +if (~isstruct(p_profile)) + disp([mfilename ': Wrong parameter. Please check your input. Returning false!']); +end + +%% Check coordinates + +boolean = ( ... + isnan(p_profile.iridium_mean_lat) ... + && isnan(p_profile.iridium_mean_lon) ... + && ~isnan(p_profile.gps_lat) ... + && ~isnan(p_profile.gps_lon) ... + ); + +return; + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/has_status.m b/lib/vendor/nemo2profilelib/+nemo/+profile/has_status.m new file mode 100644 index 0000000..16800bf --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+profile/has_status.m @@ -0,0 +1,36 @@ +function [ boolean ] = has_status( p_profile, p_status ) +%HAS_STATUS Returns true if given profile has given status, false otherwise. +% Compares xmit_upcast_status of the given profile with the p_status +% parameter. +% +% Returns: True or false. +% +% Parameters: +% p_profile: The nemo profile that is being checked. +% p_status: Expected xmit_upcast_status. + +%% Initializing return variables +boolean = false; + +%% Parameter check + +if isempty(p_profile) + return; % because no values given +end + +if (~isstruct(p_profile) ... + || ~ischar(p_status)) + disp([mfilename ': Wrong parameter. Please check your input. Returning false!']); +end + +%% Check status +if isnan(p_profile.PROFILE_TECHNICAL_DATA.xmit_upcast_status) + boolean = false; +else + boolean = (strcmp(p_status, strtrim(p_profile.PROFILE_TECHNICAL_DATA.xmit_upcast_status))); +end + +return; + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/has_status_and_coordinates.m b/lib/vendor/nemo2profilelib/+nemo/+profile/has_status_and_coordinates.m new file mode 100644 index 0000000..3db8a96 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+profile/has_status_and_coordinates.m @@ -0,0 +1,56 @@ +function [ boolean ] = has_status_and_coordinates( p_profile, p_status, p_max_coord_dist ) +%HAS_STATUS_AND_COORDINATES DEPRECATED! Not required anymore! Checks if profile has valid coordinates and given status. +% Checks if the given profile has the given status as well as valid iridium and +% gps coordinates. If the distance between iridium and gps exceeds +% p_max_coord_dist, false is being returned. The distance is being +% calculated via build in MATLAB distance function. +% +% Returns: True if all conditions are met. Displays a warning if +% parameters are wrong, but returns false! +% +% Parameters: +% p_profile: The nemo profile that is being checked. +% p_status: Expected xmit_upcast_status. +% p_max_coord_dist: Maximum distance in km between iridium and gps. + +%% Initializing return variables +boolean = false; + +%% Parameter check + +if (~isstruct(p_profile) ... + || ~ischar(p_status) ... + || ~isnumeric(p_max_coord_dist)) + disp([mfilename ': Wrong parameter. Please check your input. Returning false!']); +end + +%% Check status, skip if '' is given +if (~strcmp(p_status, strtrim(p_profile.PROFILE_TECHNICAL_DATA.xmit_upcast_status)) ... + && ~strcmp(p_status, '')) + return; +end + +%% Check coordinates + +if (~nemo.profile.has_gps(p_profile) || ~nemo.profile.has_iridium(p_profile)) + return; +end + +% get distance in between +difference = deg2km( ... + distance('gc', ... % convert to km for easier handling + [p_profile.SURFACE_GPS_DATA(14), p_profile.SURFACE_GPS_DATA(15)], ... + [p_profile.IRIDIUM_DATA(1), p_profile.IRIDIUM_DATA(2)] ... + ) ... +); + +if (difference > p_max_coord_dist) + return; +end + +boolean = true; + +return; + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/has_status_and_gps.m b/lib/vendor/nemo2profilelib/+nemo/+profile/has_status_and_gps.m new file mode 100644 index 0000000..250b57d --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+profile/has_status_and_gps.m @@ -0,0 +1,42 @@ +function [ boolean ] = has_status_and_gps( p_profile, p_status ) +%HAS_STATUS_AND_GPS Checks if profile has gps coordinates and given status. +% Checks if the given profile has the given status as well as +% gps coordinates. +% +% Returns: True if all conditions are met. Displays a warning if +% parameters are wrong, but returns false! +% +% Parameters: +% p_profile: The nemo profile that is being checked. +% p_status: Expected xmit_upcast_status. + +%% Initializing return variables +boolean = false; + +%% Parameter check + +if (~isstruct(p_profile) ... + || ~ischar(p_status)) + disp([mfilename ': Wrong parameter. Please check your input. Returning false!']); +end + +%% Check status +if (~strcmp(p_status, strtrim(p_profile.xmit_upcast_status))) + return; +end + +%% Check coordinates + +if ( ... % one of them is NaN + isnan(p_profile.gps_lat) ... + || isnan(p_profile.gps_lon) ... + ) + return; +end + +boolean = true; + +return; + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/load.m b/lib/vendor/nemo2profilelib/+nemo/+profile/load.m new file mode 100644 index 0000000..004318a --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+profile/load.m @@ -0,0 +1,165 @@ +function [ profile ] = load( p_filename ) +%LOAD Loads a .profile file into memory. +% +% Returns: A struct containing all parameter and data that has been read from +% the datafile. WARNING: Does not check if something went wrong while reading +% the file. +% +% Parameters: +% p_filename: Absolute filename of the file to read. +% + +%% TODO: +% - remove sections and make this function recognize automatically which type +% of variable should be used, same as in nemo.profile.save(). + +%% Initialize input parameter +profile = false; + +%% Parameter check +parameter_error = false; + +if (~ischar(p_filename)) + warning([mfilename ': Please check input parameter. p_filename is not a string!']); + parameter_error = true; +else + % get filename details + [pathstr, name, ext] = fileparts(p_filename); + if (~strcmp(ext, '.profile')) + warning([mfilename ': Please check input parameter. Wrong file extension! Should be .profile!']); + parameter_error = true; + end +end + +if parameter_error + return; % return if parameter check has not been passed successfully +else + clear parameter_error; +end + +%% Load profile into variable + +% initialize all required variables +profile = {}; +current_section = ''; +comment_char = '%'; % character to indicate comments +section_char = '['; % character indicating new section + +% open file +file_id = fopen(p_filename); +fseek(file_id, 0, 'bof'); + + +% read file line by line +current_line = fgetl(file_id); +while (ischar(current_line)) + if length(current_line) == 0 + current_line = fgetl(file_id); + continue + end + + switch current_line(1) + case section_char % this indicates a new section + current_line = strtrim(current_line); + current_section = current_line(2:end-1); + read_section + otherwise + current_line = fgetl(file_id); + end + +end + +fclose(file_id); + + function read_section + % read complete section + current_line = fgetl(file_id); + + if isempty(current_line) + % initialize an empty array to prevent errors when trying to + % access in another function + profile.(current_section) = []; + end + + while ischar(current_line) && ~isempty(current_line) ... + && ~strcmp(current_line(1), section_char) + + if strcmp(current_line(1), comment_char) % ignore commented lines + current_line = fgetl(file_id); + continue + end + + % if it contains a comment char anywhere, just take the string + % before it + found_comment = strfind(current_line, comment_char); + if ~isempty(found_comment) + current_line = current_line(1:found_comment(1)-1); + end + + switch (current_section) + case { ... + 'FLOAT_IDENTIFICATION', ... + 'OVERALL_MISSION_INFORMATION', ... + 'DEPLOYMENT_INFO', ... + 'PROFILE_TECHNICAL_DATA', ... + 'BOTTOM_VALUES_DURING_DRIFT', ... + 'PROFILE_HEADER', ... + 'QUALITY_CONTROL_HEADER', ... + 'SECOND_ORDER_INFORMATION' ... + } + % get variable name and value + [token, remain] = strtok(current_line); + % try to convert remain to a number + remain_as_num = str2num(strtrim(remain)); + if (~isempty(remain_as_num)) + profile.(current_section).(token(2:end)) = remain_as_num; + else + profile.(current_section).(token(2:end)) = strtrim(remain); + end + case { ... + 'RAFOS_VALUES_FORMAT', ... + 'PROFILE_DATA_FORMAT', ... + 'SURFACE_GPS_DATA_FORMAT', ... + 'IRIDIUM_POSITIONS_FORMAT', ... + 'IRIDIUM_DATA_FORMAT', ... + 'PROFILE_DATA_HEADER' ... + } + % remove -format, split afterwards + [token, remain] = strtok(current_line); + if (~strcmp(token, '-format')) + profile.(current_section) = [ token, ... + strsplit(strtrim(remain), {' ', '\t'}, 'CollapseDelimiters', true)]; + else + profile.(current_section) = strsplit( ... + strtrim(remain), {' ', '\t'}, 'CollapseDelimiters', true); + end + %break + case { ... + 'RAFOS_VALUES', ... + 'PROFILE_DATA', ... + 'SURFACE_GPS_DATA', ... + 'IRIDIUM_POSITIONS', ... + 'IRIDIUM_DATA' ... + } + % store into matrix + % read values and append them into the same var as long + % as no empty line occured (convention) + profile.(current_section) = []; + while (ischar(current_line) && ~strcmp(current_line, '')) + profile.(current_section) = [ ... + profile.(current_section); ... + str2double(strsplit(current_line, {'\t', ' '}, 'CollapseDelimiters', true)) ... + ]; + current_line = fgetl(file_id); + end + break + + end + + current_line = fgetl(file_id); + end + end + + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/map_to.m b/lib/vendor/nemo2profilelib/+nemo/+profile/map_to.m new file mode 100644 index 0000000..26e7317 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+profile/map_to.m @@ -0,0 +1,75 @@ +function [ mapped_profile ] = map_to( p_profile, p_mapping, p_skip_undefined ) +%MAP_TO Summary of this function goes here +% Detailed explanation goes here + +%% Initialize return variable +mapped_profile = false; + +%% Parameter check +if ~isstruct(p_profile) || ~isstruct(p_mapping) + disp([mfilename ': Please check your input parameter!']); + return +end + +%% Map parameters +mapped_profile = struct; + +fnames_profile = fieldnames(p_profile); + +for i = 1:length(fnames_profile) + + % check if mapping exists for this field + if ~isfield(p_mapping, fnames_profile{i}) + if exist('p_skip_undefined', 'var') && ~p_skip_undefined + mapped_profile.(fnames_profile{i}) = p_profile.(fnames_profile{i}); + end + continue + end + + current_field = p_profile.(fnames_profile{i}); + + if ~isstruct(current_field) % we only want to modify structs atm + continue + end + + mapped_profile.(fnames_profile{i}) = struct; + + % get the fieldnames in the struct + subfnames = fieldnames(current_field); + for k = 1:length(subfnames) + + if ~isfield(p_mapping.(fnames_profile{i}), subfnames{k}) + if exist('p_skip_undefined', 'var') && ~p_skip_undefined + mapped_profile.(fnames_profile{i}).(subfnames{k}) = p_profile.(fnames_profile{i}).(subfnames{k}); + end + continue + end + + % check if the previous value is the same + if isfield(mapped_profile.(fnames_profile{i}), (p_mapping.(fnames_profile{i}).(subfnames{k}))) + % append the value instead of copying + mapped_profile.(fnames_profile{i}).(p_mapping.(fnames_profile{i}).(subfnames{k})) = ... + [ ... + mapped_profile.(fnames_profile{i}).(p_mapping.(fnames_profile{i}).(subfnames{k})) ... + p_profile.(fnames_profile{i}).(subfnames{k}) + ]; + continue + end + + % check if there are multiple values in the field + values = strsplit(p_mapping.(fnames_profile{i}).(subfnames{k}), ','); + if length(values) > 1 + for m = 1:length(values) + mapped_profile.(fnames_profile{i}).(values{m}) = p_profile.(fnames_profile{i}).(subfnames{k})(m); + end + continue + end + + mapped_profile.(fnames_profile{i}).(p_mapping.(fnames_profile{i}).(subfnames{k})) = p_profile.(fnames_profile{i}).(subfnames{k}); + + end + +end + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/positions_differ_by.m b/lib/vendor/nemo2profilelib/+nemo/+profile/positions_differ_by.m new file mode 100644 index 0000000..1409e19 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+profile/positions_differ_by.m @@ -0,0 +1,38 @@ +function [ boolean ] = positions_differ_by( p_profile, p_distance ) +%POSITIONS_DIFFER_BY Checks if gps and iridium coordinates differ by given distance. +% +% Returns: True if the distance exceeds p_distance [km]. +% +% Parameters: +% p_profile: The nemo profile that is being checked. +% p_distance: The maximum distance that is being accepted. + +%% Initializing return variables +boolean = false; + +%% Parameter check + +if (~isstruct(p_profile)) + disp([mfilename ': Wrong parameter. Please check your input. Returning false!']); +end + +%% Check coordinates + +if (~nemo.profile.has_gps(p_profile) || ~nemo.profile.has_iridium(p_profile)) + return; +end + +% get distance in between +difference = deg2km( ... + distance('gc', ... % convert to km for easier handling + [p_profile.SURFACE_GPS_DATA(14), p_profile.SURFACE_GPS_DATA(15)], ... + [p_profile.IRIDIUM_DATA(1), p_profile.IRIDIUM_DATA(2)] ... + ) ... +); + +if (difference > p_distance) + boolean = true; +end + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/save.m b/lib/vendor/nemo2profilelib/+nemo/+profile/save.m new file mode 100644 index 0000000..1aeddea --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+profile/save.m @@ -0,0 +1,179 @@ +function [ boolean ] = save( p_header_filename, p_profile, p_output_file, p_overwrite ) +%SAVE Writes the input profile to the output file. +% This function saves the p_profile structure to an ASCII textfile with +% .profile extension. The structure of the parameter is being transferred +% equally into the textfile so that it can be loaded by the load +% function. +% TO BE OPTIMIZED: This function does not recursively checks for the +% variable type. Only up to the second level. +% +% Returns: Boolean variable, being false if something went wrong during +% write. +% +% Parameters: +% p_header_file: Contains the absolute filename of the header being +% written to the output file. There is no comment +% character being added by this function. +% p_profile: Contains the profile structure that will be written to +% the output file. +% p_output_file: Absolute file path to the output file that will be +% written. +% p_overwrite: If set to true, the output file will be overwritten if +% it exists. +% + +%% Initialize return variable +boolean = false; + +%% Parameter check +parameter_error = false; + +if (~isstruct(p_profile) || ~ischar(p_header_filename) ... + || ~ischar(p_output_file)) + parameter_error = true; +end + +if (~exist('p_overwrite', 'var') || isempty(p_overwrite)) + p_overwrite = 1; % default setting is to overwrite existing profile files +end + +if (parameter_error) % display error message + disp([ mfilename ': Wrong parameter! Please check your input parameters carefully!']); + return; +else % do some cleanup + clear parameter_error; +end + +%% File existence checks + +if exist(p_output_file, 'file') == 2 && ~p_overwrite + % file will not be overwritten + boolean = true; + return +end + +header_file_id = 0; +if exist(p_header_filename, 'file') ~= 2 + disp([mfilename ': Header file does not exist! Skipping...']); + return; +else % read header file + header_file_id = fopen(p_header_filename, 'r'); + header_content = fscanf(header_file_id, '%c'); + fclose(header_file_id); + clear header_file_id +end + +%% Write file + +% prepare for writing +help_tabs = ['\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t']; +desired_tab_count = 12; +section_names = fieldnames(p_profile); % will be stored in between [] +section_count = length(section_names); +output_file_id = fopen(p_output_file, 'w'); % open file for writing +double_format = '%.15g'; + +% write header +fprintf(output_file_id, '%s', header_content); % write header to file +fprintf(output_file_id, '\n\n'); % add 2 lines to separate data from header + +% write content +for o_section = 1:section_count % run through every section + + % write section name to file + fprintf(output_file_id, ['[' section_names{o_section} ']' '\n' ]); + + % check which type of data it is and decide how to output + switch class(p_profile.(section_names{o_section})) + case 'struct' + write_struct(p_profile.(section_names{o_section})); + + case 'cell' + write_cell(p_profile.(section_names{o_section})); + + case 'double' + write_double(p_profile.(section_names{o_section})); + + otherwise + end + + fprintf(output_file_id, '\n'); + + +end + +% close file +fclose(output_file_id); + +boolean = true; % update return variable + + + function [ tabs ] = get_tabs_to_insert(p_member_name) + % this function computes the required tabs to insert, making the + % textfile easier to read. + tablength = 4; + tab_count = desired_tab_count - floor((length(p_member_name)+1)/tablength); + tabs = help_tabs(1:(tab_count*2)); + end + + function write_struct(p_input_struct) + % get all fieldnames and write them to the file + member_names = fieldnames(p_input_struct); + member_count = length(member_names); + for o_member = 1:member_count % run through all members + % calculate how many tabs have to be inserted to have a + % good table layout + tabstring = get_tabs_to_insert(member_names{o_member}); + fprintf(output_file_id, ['-' member_names{o_member} tabstring]); + current_member = p_input_struct.(member_names{o_member}); + current_member_class = class(current_member); + switch current_member_class + case {'double', 'logical'} + write_double(current_member); + case 'char' + fprintf(output_file_id, [current_member '\n']); + otherwise + disp(['Unknown class in file: ' class(current_member)]); + end + + end + clear o_member + end + + function write_cell(p_input_cell) + % run through every cell member and check the class + for o_cell_line = 1:size(p_input_cell, 1) + for o_cell_column = 1:size(p_input_cell, 2) + current_value = p_input_cell{o_cell_line, o_cell_column}; + switch class(current_value) + case 'char' + fprintf(output_file_id, current_value); + case {'double', 'logical'} + fprintf(output_file_id, double_format, current_value); + otherwise + disp(['Unknown class in file: ' class(current_value)]); + end + if (o_cell_column ~= size(p_input_cell, 2)) + fprintf(output_file_id, '\t'); + end + end + + fprintf(output_file_id, '\n'); + end + end + + function write_double(p_input_double) + + for o_double = 1:size(p_input_double, 1) + if length(p_input_double(o_double, :)) > 1 + fprintf(output_file_id, sprintf([double_format '\t'], p_input_double(o_double, 1:end-1))); + end + fprintf(output_file_id, [double_format '\n'], p_input_double(o_double, end)); + end + clear o_double + + end + + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/sort_profile_data.m b/lib/vendor/nemo2profilelib/+nemo/+profile/sort_profile_data.m new file mode 100644 index 0000000..b32f7c6 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+profile/sort_profile_data.m @@ -0,0 +1,27 @@ +function [ sorted_profile ] = sort_profile_data( p_profile ) +%SORT_PROFILE_DATA Sorts the CTD profile data ascending by pressure. +% See nemo.float.sort_profile_data for more details. +% +% Parameters: +% p_profile: The profile to be sorted. +% +% Returns: +% sorted_profile: A copy of the profile with sorted CTD data. + + +%% Initialize return variable +sorted_profile = false; + +%% Parameter check +if (~isstruct(p_profile) || ~isfield(p_profile, 'PROFILE_DATA')) + disp([mfilename ': Unknown input type! Please check your input parameter!']); + return +end + +%% Sort profile data by time +sorted_profile = p_profile; +sorted_profile.PROFILE_DATA = sortrows(p_profile.PROFILE_DATA, 7); + + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/verify_iridium.m b/lib/vendor/nemo2profilelib/+nemo/+profile/verify_iridium.m new file mode 100644 index 0000000..352fb45 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+profile/verify_iridium.m @@ -0,0 +1,38 @@ +function [ boolean ] = verify_iridium( p_profile, p_next_profile ) +%VERIFY_IRIDIUM Checks if the Iridium data represents profile time and position. +% +% Returns: True if Iridium time of current profile is smaller than the +% one of the next profile. +% +% Parameters: +% p_profile: The current nemo profile. +% p_next_profile: The nemo profile with profile no +1. + +%% Initializing return variables +boolean = false; + +%% Parameter check + +if (~isstruct(p_profile) || ~isstruct(p_next_profile)) + disp([mfilename ': Wrong parameters. Please check your input. Returning false!']); + return +end + +%% Check if iridium section is available +if (~isfield(p_profile, 'IRIDIUM_DATA') || ~isfield(p_next_profile, 'IRIDIUM_DATA')) + boolean = false; + return +else % compare it to next profile + % the current profile datetime has to be lower than the one of the next + % profile + boolean = ( ... + datenum(p_profile.IRIDIUM_DATA(4:9)) ... + < ... + datenum(p_next_profile.IRIDIUM_DATA(4:9)) ... + ); + +end + +return; + +end \ No newline at end of file diff --git a/lib/vendor/nemo2profilelib/+nemo/+rfb/create.m b/lib/vendor/nemo2profilelib/+nemo/+rfb/create.m new file mode 100644 index 0000000..28ed1cd --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+rfb/create.m @@ -0,0 +1,239 @@ +function [ rfb, metadata ] = create( p_nemo_float, p_float_metadata ) +%CREATE Creates the rfb structure of the given floats. +% +% Parameters: +% p_nemo_float: The floats to process. +% p_float_metadata: The floats metadata. +% +% Returns: +% rfb: The rfb structure. +% metadata: The corresponding metadata. Floats without +% RAFOS have been sorted out. + +%% Initialize return variables +rfb = false; + +%% Parameter check +if (~iscell(p_nemo_float)) % display error message + disp([ mfilename ': Wrong parameter! Please check your input parameters carefully!']); + return; +end + +% if it is the first profile, it is a struct, otherwise a cell +switch (class(p_nemo_float{1})) + case 'cell' + % sort out all floats without rafos data +% sending_floats_metadata = {}; +% for o = 1:length(p_nemo_float) % run through every float +% current_serial = p_nemo_float{o}{1}.FLOAT_IDENTIFICATION.float_serial_number; +% for i = 1:length(p_float_metadata) +% if (p_float_metadata{i}.GENERAL_INFO.SERIAL == current_serial) +% sending_floats_metadata{o} = p_float_metadata{i}; +% end +% end +% end +% rfb = cellfun(@create_rfb_float, p_nemo_float, sending_floats_metadata', 'UniformOutput', false); + rfb = cellfun(@create_rfb_float, p_nemo_float, p_float_metadata, 'UniformOutput', false); +% metadata = sending_floats_metadata; + metadata = p_float_metadata; + case 'struct' + rfb = create_rfb_float(p_nemo_float, p_float_metadata); + metadata = p_float_metadata; + otherwise + disp([mfilename ': Type ' class(p_nemo_float{1}) ' not supported!' ]); + return +end + + + function rfb_float = create_rfb_float(float, metadata) + + if(~isfield(float{1}, 'SECOND_ORDER_INFORMATION')) + disp([ mfilename ': Generation of rfb file requires field SECOND_ORDER_INFORMATION!']); + return; + end + + rfb_float = struct(); + + %% Create header information + FLOAT = struct( ... + 'floatname', float{1}.FLOAT_IDENTIFICATION.float_serial_number, ... + 'type', '', ... + 'projectname', metadata.RAFOS.GENERAL_INFO.projectname, ... + 'pttdec', '', ... + 'ptthex', '', ... + 'pttrep', metadata.RAFOS.GENERAL_INFO.pttrep, ... + 'launchpos', float{1}.DEPLOYMENT_INFO.deployment_position, ... + 'launchtime', [ ... + fliplr(float{1}.DEPLOYMENT_INFO.deployment_date), ... + float{1}.DEPLOYMENT_INFO.deployment_time ... + ], ... + 'recoverpos', [ ... + float{end}.SECOND_ORDER_INFORMATION.best_guess_position_lat, ... + float{end}.SECOND_ORDER_INFORMATION.best_guess_position_lon ... + ], ... + 'recovertime', float{end}.SECOND_ORDER_INFORMATION.best_guess_datevec, ... + 'offset', float{end}.SECOND_ORDER_INFORMATION.best_guess_datevec, ... + 'cycle', [ ... + 1, ... + float{1}.DEPLOYMENT_INFO.deployment_position, ... + fliplr(float{1}.DEPLOYMENT_INFO.deployment_date), ... + float{1}.DEPLOYMENT_INFO.deployment_time, ... + float{end}.SECOND_ORDER_INFORMATION.best_guess_position_lat, ... + float{end}.SECOND_ORDER_INFORMATION.best_guess_position_lon, ... + float{end}.SECOND_ORDER_INFORMATION.best_guess_datevec ... + ], ... + 'phasespercycle', 0, ... % is being set later, not available up to this point + 'schedule', metadata.RAFOS.GENERAL_INFO.schedule, ... + 'phasereftime', metadata.RAFOS.GENERAL_INFO.phasereftime, ... + 'windowsperphase', metadata.RAFOS.GENERAL_INFO.windowsperphase, ... + 'toaperwindow', metadata.RAFOS.GENERAL_INFO.toaperwindow, ... + 'toaperphase', metadata.RAFOS.GENERAL_INFO.toaperphase, ... + 'correlationrange', metadata.RAFOS.GENERAL_INFO.correlationrange, ... + 'windowrange', metadata.RAFOS.GENERAL_INFO.windowrange, ... + 'windowstart', metadata.RAFOS.GENERAL_INFO.windowstart, ... + 'windowlength', metadata.RAFOS.GENERAL_INFO.windowlength, ... + 'progdepth', metadata.RAFOS.GENERAL_INFO.progdepth, ... + 'progtemp', metadata.RAFOS.GENERAL_INFO.progtemp, ... + 'tempequation', metadata.RAFOS.GENERAL_INFO.tempequation, ... + 'tempcoeff', metadata.RAFOS.GENERAL_INFO.tempcoeff, ... + 'presequation', metadata.RAFOS.GENERAL_INFO.presequation, ... + 'prescoeff', metadata.RAFOS.GENERAL_INFO.prescoeff, ... + 'signal_length', metadata.RAFOS.GENERAL_INFO.signal_length, ... + 'comment', metadata.RAFOS.GENERAL_INFO.comment, ... + 'edited_by', metadata.RAFOS.GENERAL_INFO.edited_by, ... + 'edited_on', datevec(now), ... + 'variables', metadata.RAFOS.GENERAL_INFO.variables ... + ); + + SAT_FORMAT = struct( ... + 'lat_sat', metadata.RAFOS.GPS_FORMAT.lat, ... + 'lon_sat', metadata.RAFOS.GPS_FORMAT.lon, ... + 'src', metadata.RAFOS.GPS_FORMAT.src, ... + 'year_sat', metadata.RAFOS.GPS_FORMAT.year, ... + 'month_sat', metadata.RAFOS.GPS_FORMAT.month, ... + 'day_sat', metadata.RAFOS.GPS_FORMAT.day, ... + 'hour_sat', metadata.RAFOS.GPS_FORMAT.hour, ... + 'minutes_sat', metadata.RAFOS.GPS_FORMAT.minutes, ... + 'seconds_sat', metadata.RAFOS.GPS_FORMAT.seconds, ... + 'year_rtc', metadata.RAFOS.GPS_FORMAT.year_rtc, ... + 'month_rtc', metadata.RAFOS.GPS_FORMAT.month_rtc, ... + 'day_rtc', metadata.RAFOS.GPS_FORMAT.day_rtc, ... + 'hour_rtc', metadata.RAFOS.GPS_FORMAT.hour_rtc', ... + 'minutes_rtc', metadata.RAFOS.GPS_FORMAT.minutes_rtc, ... + 'seconds_rtc', metadata.RAFOS.GPS_FORMAT.seconds_rtc ... + ); + + VARIABLE_LIST = struct( ... + 'line_number', metadata.RAFOS.VARIABLE_LIST.line_number, ... + 'start_listen_yr', metadata.RAFOS.VARIABLE_LIST.start_listen_yr, ... + 'start_listen_mon', metadata.RAFOS.VARIABLE_LIST.start_listen_mon, ... + 'start_listen_day', metadata.RAFOS.VARIABLE_LIST.start_listen_day, ... + 'start_listen_hr', metadata.RAFOS.VARIABLE_LIST.start_listen_hr, ... + 'start_listen_min', metadata.RAFOS.VARIABLE_LIST.start_listen_min, ... + 'start_listen_sec', metadata.RAFOS.VARIABLE_LIST.start_listen_sec, ... + 'time_of_arrival', metadata.RAFOS.VARIABLE_LIST.time_of_arrival, ... + 'correlation_height', metadata.RAFOS.VARIABLE_LIST.correlation_height, ... + 'pressure', metadata.RAFOS.VARIABLE_LIST.pressure, ... + 'temperature', metadata.RAFOS.VARIABLE_LIST.temperature, ... + 'pres_counts', metadata.RAFOS.VARIABLE_LIST.pres_counts, ... + 'temp_counts', metadata.RAFOS.VARIABLE_LIST.temp_counts ... + ); + + + %% Collect all data information + + SAT_DATA = nemo.float.collect(float, 'SURFACE_GPS_DATA', '', 'combine'); + + rafos_data = nemo.float.collect(float, 'RAFOS_VALUES', '', 'combine'); + if (isempty(rafos_data)) + rfb_float = false; + return; + end + % apply some corrections + hour_scheduled = 30; + travel_correction = -60 * (rafos_data(:, 6) - hour_scheduled) ... + + rafos_data(:, 7); + % the following line throws an error in 2016a + % rafos_data(:, 9:2:19) = rafos_data(:, 9:2:19) + travel_correction; + rafos_data(:, 9) = rafos_data(:, 9) + travel_correction; + rafos_data(:, 11) = rafos_data(:, 11) + travel_correction; + rafos_data(:, 13) = rafos_data(:, 13) + travel_correction; + rafos_data(:, 15) = rafos_data(:, 15) + travel_correction; + rafos_data(:, 17) = rafos_data(:, 17) + travel_correction; + rafos_data(:, 19) = rafos_data(:, 19) + travel_correction; + % remove all unknown dates + rafos_data = rafos_data(isfinite(datenum(rafos_data(:, 2:4))), :); + + % insert NaN for every missing row (compatibility fix for artoa) + % time_axis = datenum( ... + % datestr(FLOAT.launchtime,'dd-mmm-yyyy')) ... + % :p_rfb_config.GENERAL_INFO.artoa_expected_cycle ... + % :datenum(datestr(FLOAT.recovertime,'dd-mmm-yyyy') ... + % - 1 ... + % ); + time_axis = [ ... + datenum(fliplr(float{1}.DEPLOYMENT_INFO.deployment_date)) ... + :metadata.RAFOS.GENERAL_INFO.artoa_expected_cycle ... + :datenum(float{end}.SECOND_ORDER_INFORMATION.best_guess_datevec(1:3)) ... + ]; + rafos_dates = datenum(rafos_data(:, 2:4)); + + [~, id1] = intersect(time_axis, rafos_dates); + [~, id2] = intersect(rafos_dates, time_axis); + + if (size(rafos_data, 1) > length(time_axis)) + disp([ mfilename ... + ': More RAFOS values available than possible for float number ' ... + num2str(float{1}.FLOAT_IDENTIFICATION.float_serial_number) '!' ... + ]); + end + + if (strcmp(float{end}.SECOND_ORDER_INFORMATION.best_guess_datevec_src, 'none')) + % create resulting matrix + RAFOS_VALUES = rafos_data; + %RAFOS_VALUES(id1, 1) = id1; + + % change order of columns and remove unnecessary ones + tmp = [ ... + RAFOS_VALUES(:, 1:19), ... + RAFOS_VALUES(:, 22), ... + RAFOS_VALUES(:, 20) ... + ]; + RAFOS_VALUES = tmp; + clear tmp + + %% Set values that were not available until now + FLOAT.phasespercycle = length(rafos_dates) + 1; + else + % create resulting matrix + RAFOS_VALUES = nan(length(time_axis), size(rafos_data, 2)); + RAFOS_VALUES(id1, :) = rafos_data(id2, :); + RAFOS_VALUES(id1, 1) = id1; + + % change order of columns and remove unnecessary ones + tmp = [ ... + RAFOS_VALUES(:, 1:19), ... + RAFOS_VALUES(:, 22), ... + RAFOS_VALUES(:, 20) ... + ]; + RAFOS_VALUES = tmp; + clear tmp + + %% Set values that were not available until now + FLOAT.phasespercycle = length(time_axis) + 1; + end + + %% Write resulting structure + + rfb_float.FLOAT = FLOAT; + rfb_float.VARIABLE_LIST = VARIABLE_LIST; + if ~isempty(SAT_DATA) + rfb_float.SAT_FORMAT = SAT_FORMAT; + rfb_float.SAT_DATA = SAT_DATA; + end + rfb_float.DATA = RAFOS_VALUES; + end + + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+rfb/create_and_save_collection.m b/lib/vendor/nemo2profilelib/+nemo/+rfb/create_and_save_collection.m new file mode 100644 index 0000000..910ab37 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+rfb/create_and_save_collection.m @@ -0,0 +1,49 @@ +function [ boolean, rfb_floats ] = create_and_save_collection( p_nemo_floats, p_config, p_metadata ) +%CREATE_AND_SAVE_COLLECTION Creates the rfb structure from a collection of nemo floats and saves them to disk. +% +% Parameters: +% p_nemo_floats: The nemo data. +% p_config: The nemo configuration. +% p_metadata: The nemo metadata. +% +% Returns: +% boolean: True, if successful. +% rfb_floats: The rfb structure. + +%% Initialize return variable +boolean = false; + +%% Parameter check +parameter_error = false; + +if (~iscell(p_nemo_floats) || ~isstruct(p_config) || ~iscell(p_metadata)) + parameter_error = true; +end + +if (parameter_error) % display error message + disp([ mfilename ': Wrong parameter! Please check your input parameters carefully!']); + return; +else % do some cleanup + clear parameter_error; +end + +%% Collect necessary information and save all rfb files + +output_files = cellfun(... + @(x) [ p_config.RFB.output_path sprintf('%04d', x{1}.FLOAT_IDENTIFICATION.float_serial_number) '.rfb'] ... + , p_nemo_floats, 'UniformOutput', false); + +p_metadata = nemo.sync_metadata_to_nemodata(p_nemo_floats, p_metadata); + +[rfb_floats, ~] = nemo.rfb.create(p_nemo_floats, p_metadata); + +booleans = cellfun(@nemo.rfb.save, rfb_floats, output_files, cell(length(rfb_floats), 1)); + +if (all(booleans)) + boolean = true; +else + boolean = false; +end + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+rfb/save.m b/lib/vendor/nemo2profilelib/+nemo/+rfb/save.m new file mode 100644 index 0000000..caa105e --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+rfb/save.m @@ -0,0 +1,174 @@ +function [ boolean ] = save( p_profile, p_output_file, p_overwrite ) +%SAVE Writes the input profile to the output file. +% This function saves the p_profile structure to an ASCII textfile with +% .profile extension. The structure of the parameter is being transferred +% equally into the textfile so that it can be loaded by the load +% function. +% TO BE OPTIMIZED: This function does not recursively checks for the +% variable type. Only up to the second level. +% +% Returns: Boolean variable, being false if something went wrong during +% write. +% +% Parameters: +% p_profile: Contains the profile structure that will be written to +% the output file. +% p_output_file: Absolute file path to the output file that will be +% written. +% p_overwrite: If set to true, the output file will be overwritten if +% it exists. +% + +%% Initialize return variable +boolean = false; + +%% Parameter check +parameter_error = false; + +if (isa(p_profile, 'logical') && ~p_profile) + boolean = true; + return +end + +if (~isstruct(p_profile) || ~ischar(p_output_file)) + parameter_error = true; +end + +if (isempty(p_overwrite)) + p_overwrite = 1; % default setting is to overwrite existing profile files +end + +if (parameter_error) % display error message + disp([ mfilename ': Wrong parameter! Please check your input parameters carefully!']); + return; +else % do some cleanup + clear parameter_error; +end + +%% File existence checks + +[directory, ~, ~] = fileparts(p_output_file); +if exist(directory, 'dir') ~= 7 + mkdir(directory) +end + +if exist(p_output_file, 'file') == 2 && ~p_overwrite + % file will not be overwritten + boolean = true; + return +end + +%% Write file + +% prepare for writing +help_tabs = ['\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t']; +desired_tab_count = 12; +section_names = fieldnames(p_profile); % will be stored in between [] +section_count = length(section_names); +output_file_id = fopen(p_output_file, 'w'); % open file for writing +double_format = '%.15g'; + +% write content +for o_section = 1:section_count % run through every section + + % write section name to file + fprintf(output_file_id, ['[' section_names{o_section} ']' '\n' ]); + + % check which type of data it is and decide how to output + switch class(p_profile.(section_names{o_section})) + case 'struct' + write_struct(p_profile.(section_names{o_section})); + + case 'cell' + write_cell(p_profile.(section_names{o_section})); + + case 'double' + write_double(p_profile.(section_names{o_section})); + + otherwise + end + + fprintf(output_file_id, '\n'); + + +end + +% close file +fclose(output_file_id); + +boolean = true; % update return variable + + + function [ tabs ] = get_tabs_to_insert(p_member_name) + % this function computes the required tabs to insert, making the + % textfile easier to read. + tablength = 4; + tab_count = desired_tab_count - floor((length(p_member_name)+1)/tablength); + tabs = help_tabs(1:(tab_count*2)); + end + + function write_struct(p_input_struct) + % get all fieldnames and write them to the file + member_names = fieldnames(p_input_struct); + member_count = length(member_names); + for o_member = 1:member_count % run through all members + % calculate how many tabs have to be inserted to have a + % good table layout + tabstring = get_tabs_to_insert(member_names{o_member}); + fprintf(output_file_id, ['-' member_names{o_member} tabstring]); + current_member = p_input_struct.(member_names{o_member}); + current_member_class = class(current_member); + switch current_member_class + case {'double', 'logical'} + if (isempty(current_member)) + fprintf(output_file_id, [current_member '\n']); + else + write_double(current_member); + end + case 'char' + fprintf(output_file_id, [current_member '\n']); + otherwise + disp(['Unknown class in file: ' class(current_member)]); + end + + end + clear o_member + end + + function write_cell(p_input_cell) + % run through every cell member and check the class + for o_cell_line = 1:size(p_input_cell, 1) + for o_cell_column = 1:size(p_input_cell, 2) + current_value = p_input_cell{o_cell_line, o_cell_column}; + switch class(current_value) + case 'char' + fprintf(output_file_id, current_value); + case {'double', 'logical'} + fprintf(output_file_id, double_format, current_value); + otherwise + disp(['Unknown class in file: ' class(current_value)]); + end + if (o_cell_column ~= size(p_input_cell, 2)) + fprintf(output_file_id, '\t'); + end + end + + fprintf(output_file_id, '\n'); + end + end + + function write_double(p_input_double) + + for o_double = 1:size(p_input_double, 1) + if length(p_input_double(o_double, :)) > 1 + fprintf(output_file_id, sprintf([double_format '\t'], p_input_double(o_double, 1:end-1))); + end + fprintf(output_file_id, [double_format '\n'], p_input_double(o_double, end)); + end + clear o_double + + end + + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+sbd/decode_float.m b/lib/vendor/nemo2profilelib/+nemo/+sbd/decode_float.m new file mode 100644 index 0000000..df6b840 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+sbd/decode_float.m @@ -0,0 +1,41 @@ +function [ decoded_sbd, sbd_messages_info ] = decode_float( p_config, p_imei ) +%DECODE_FLOAT Decodes the sbd messages of the floats that match the given IMEI. +% +% Parameters: +% p_config: The nemo configuration. +% p_imei: The IMEI of the float to be decoded. +% +% Returns: +% decoded_sbd: The decoded sbd messages. +% sbd_messages_info: The information about the sbd messages. + +%% Initialize return value +decoded_sbd = false; +sbd_messages_info = false; + +%% Create filepath string +sbd = ['sbd' p_config.GENERAL.folder_separator]; +if (~ischar(p_imei)) + imei = int2str(p_imei); +else + imei = p_imei; +end + +filepath = p_config.MAIL.storage_directory; + +% Do not change the order of the next two if conditions! +if (p_config.MAIL.sort_by_imei) + filepath = [filepath imei p_config.GENERAL.folder_separator]; +end +if (p_config.MAIL.separate_sbd_and_txt) + filepath = [filepath sbd]; +end + +if (p_config.MAIL.sort_by_imei) + [decoded_sbd,~,sbd_messages_info] = nemo.optimare.process_moms(filepath); +else + [decoded_sbd,~,sbd_messages_info] = nemo.optimare.process_moms(filepath, imei); +end + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/+test/best_guess_nan.m b/lib/vendor/nemo2profilelib/+nemo/+test/best_guess_nan.m new file mode 100644 index 0000000..f4dbcc2 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+test/best_guess_nan.m @@ -0,0 +1,23 @@ +function [] = best_guess_nan(bgp_profiles) +%BEST_GUESS_NAN Tests best guess positions for NaN's. + +% get size of bgp_profiles +float_count = length(bgp_profiles); + +for o_floats = 1:float_count + profile_count = length(bgp_profiles{o_floats}); + nan_positions = []; + for o_profiles = 1:profile_count + if isnan(bgp_profiles{o_floats}{o_profiles}.SECOND_ORDER_INFORMATION.best_guess_position_src) + nan_positions = [nan_positions; o_profiles]; + end + end + if (~isempty(nan_positions)) + disp(['Float index: ' num2str(o_floats) ' Float ID: ' num2str(bgp_profiles{o_floats}{1}.FLOAT_IDENTIFICATION.float_serial_number)]); + nan_positions + end + clear nan_positions + +end + +end \ No newline at end of file diff --git a/lib/vendor/nemo2profilelib/+nemo/+test/compare_nemo_data.m b/lib/vendor/nemo2profilelib/+nemo/+test/compare_nemo_data.m new file mode 100644 index 0000000..b3eb77f --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+test/compare_nemo_data.m @@ -0,0 +1,16 @@ +function compare_nemo_data(p_first_data, p_second_data) +%COMPARE_NEMO_DATA Compares two data sets of nemo data for differences. + +length_a = length(p_first_data); +length_b = length(p_second_data); + +for o_a = 1:length_a + for i = 1:length(p_first_data{o_a}) + [bool, string] = nemo.profile.compare(p_first_data{o_a}{i}, p_second_data{o_a}{i}); + if ~bool + disp(['float index ' num2str(o_a) ' profile ' num2str(i) ': ' string{:}]); + end + end +end + +clear o_a i length_a length_b first_floats second_floats \ No newline at end of file diff --git a/lib/vendor/nemo2profilelib/+nemo/+test/mail_development.m b/lib/vendor/nemo2profilelib/+nemo/+test/mail_development.m new file mode 100644 index 0000000..b9baf31 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+test/mail_development.m @@ -0,0 +1,24 @@ +% config = nemo.load_config('etc\nemo2profile.conf'); +% +% imapstore = nemo.mail.connect(config) +% messages = nemo.mail.fetch_messages(imapstore, 'Inbox'); +% +% +% msg = messages(end); +% +% +% +% [from, subject, inline, attachments] = nemo.mail.extract(msg); +% att = attachments{1,2}; +% +% +% nemo.mail.export_attachment(att, '\\filesrv1.awi.de\csys_ocean2\argo\data\nemo\mail\sbd.sbd') + +config = nemo.load.config('etc\nemo2profile.conf'); +if ~exist('metadata', 'var') + %metadata = nemo.load.xml_file('etc\float_metadata'); + metadata = nemo.load.float_metadata('etc\float_metadata'); +end +tic; +nemo.float.fetch_process_mails(config, metadata) +toc \ No newline at end of file diff --git a/lib/vendor/nemo2profilelib/+nemo/+test/process_nemo.m b/lib/vendor/nemo2profilelib/+nemo/+test/process_nemo.m new file mode 100644 index 0000000..9005b95 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+test/process_nemo.m @@ -0,0 +1,82 @@ +timer = tic; +disp([mfilename ': Adding dependencies ...']); +cd('C:\FLOAT DEPLOYMENT ON POLARSTERN\FLOAT DATA PROCESSING AND ANALYSIS'); +addpath(genpath('C:\FLOAT DEPLOYMENT ON POLARSTERN\FLOAT DATA PROCESSING AND ANALYSIS')); + +disp([mfilename ': Loading configuration file...']); +% load program config +config = nemo.load.config('etc\nemo2profile.conf'); +disp([mfilename ': ...done!']); +disp([mfilename ': Loading float metadata files...']); +% load all floats that have to be processed +metadata = nemo.load.float_metadata('etc\float_metadata'); +disp([mfilename ': ...done!']); + +disp([mfilename ': Downloading and processing mails...']); +% download and process mails +nemo.float.fetch_process_mails(config, metadata); +disp([mfilename ': ...done!']); +disp([mfilename ': Converting sbd attachments and extracting transmission information...']); +awi_struct = nemo.load.floats2nemostruct(config, metadata); +disp([mfilename ': ...done!']); + +%% Remove logical cells (without profiles) +awi_struct = awi_struct(~cellfun(@(x) isa(x, 'logical'), awi_struct)); + +%% Manual quality check +awi_struct = nemo.postprocessing.delete_profile(awi_struct, 239, 1); + +%% Process data + +disp([mfilename ': Creating second order information...']); +% possible estimation methods: +% 'time based': memory consumption EXTREMLY high, but most descriptive +% 'position based': less memory consumption, positions equally split +% 'nan': most leightweight estimation, using NaN for unknown positions +awi_struct = ... + nemo.postprocessing.position.create_best_guess( ... + awi_struct, 'time based' ... + ); +awi_struct = nemo.postprocessing.datetime.create_best_guess(awi_struct); +disp([mfilename ': ... done!' ]); +disp([mfilename ': Doing quality check...' ]); +awi_struct = nemo.postprocessing.quality_check(awi_struct); +disp([mfilename ': ... done!']); +disp([mfilename ': Generating RFB files and save to disk...']); +[success, rfb_floats] = nemo.rfb.create_and_save_collection(awi_struct, config, metadata); +if (~success) + disp([mfilename ': ... failed!']); +else + save([config.OUTPUT_FILES.output_path 'rfb_floats.mat'], 'rfb_floats'); + disp([mfilename ': ... done!']); +end +disp([mfilename ': Saving data to ' config.OUTPUT_FILES.output_path '...']); +nemo.save_data(awi_struct, config); +save([config.OUTPUT_FILES.output_path 'awi_struct.mat'], 'awi_struct'); +save([config.OUTPUT_FILES.output_path 'config.mat'], 'config'); +save([config.OUTPUT_FILES.output_path 'metadata.mat'], 'metadata'); +disp([mfilename ': ... done!']); + +disp([mfilename ': Generating analysis items...']); +if ~nemo.analysis.create_csv_table(awi_struct, config) + warning('Creating overview table failed!'); +end +if ~nemo.plot.floats(awi_struct, config, 'south', false) + warning('Creating overview south plot failed!'); +end +if ~nemo.plot.floats(awi_struct, config, 'south', true) + warning('Creating plot for every single float failed!'); +end +if ~nemo.plot.arrival_of_profiles(awi_struct, config) + warning('Creating arrival of profiles plot failed!'); +end +disp([mfilename ': ... done!']); + +disp([mfilename ': Uploading float data to server ...']); +% Upload data to server +if (~nemo.ftp.upload(config)) + disp([mfilename ': Uploading data failed!']); +else + disp([mfilename ': Uploading data successful!']); +end +disp([mfilename ': Processing nemo data took ' num2str(toc(timer)) ' seconds!']); \ No newline at end of file diff --git a/lib/vendor/nemo2profilelib/+nemo/+test/sbd2nemostruct.m b/lib/vendor/nemo2profilelib/+nemo/+test/sbd2nemostruct.m new file mode 100644 index 0000000..a125704 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/+test/sbd2nemostruct.m @@ -0,0 +1,7 @@ +config = nemo.load.config('etc\nemo2profile.conf'); +if ~exist('metadata', 'var') + %metadata = nemo.load.xml_file('etc\float_metadata'); + metadata = nemo.load.float_metadata('etc\float_metadata\sn_248_imei_300234011429370_metadata.xml'); +end + +nemo_struct = nemo.load.floats2nemostruct(config, metadata); \ No newline at end of file diff --git a/lib/vendor/nemo2profilelib/+nemo/find_float_in_metadata.m b/lib/vendor/nemo2profilelib/+nemo/find_float_in_metadata.m new file mode 100644 index 0000000..4911163 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/find_float_in_metadata.m @@ -0,0 +1,50 @@ +function [ float_metadata ] = find_float_in_metadata( p_metadata, p_imeis ) +%FIND_FLOAT_IN_METADATA Returns the metadata of the given IMEI +% Searches p_metadata for the given IMEI. +% +% Parameters: +% p_metadata: The metadata of the floats. +% p_imeis: The IMEI(s) to be searched for. +% +% Returns: +% float_metadata: False if not found. Returns copies of the float +% metadata of the given IMEIs otherwise. + +%% Initialize return value +float_metadata = false; + +%% Find the float + +if (isempty(p_imeis)) + return; +end +booleans = cellfun(@(x) x.GENERAL_INFO.IMEI == p_imeis(1), p_metadata); +for i=2:length(p_imeis) + tmp_booleans = cellfun(@(x) x.GENERAL_INFO.IMEI == p_imeis(i), p_metadata); + booleans = booleans + tmp_booleans; +end + +booleans(booleans > 1) = 1; +booleans = logical(booleans); + +float_metadata = p_metadata(booleans); + +if (~isempty(float_metadata)) + return +end + +% nothing has been found, so check all serials + +booleans = cellfun(@(x) x.GENERAL_INFO.SERIAL == p_imeis(1), p_metadata); +for i=2:length(p_imeis) + tmp_booleans = cellfun(@(x) x.GENERAL_INFO.SERIAL == p_imeis(i), p_metadata); + booleans = booleans + tmp_booleans; +end + +booleans(booleans > 1) = 1; +booleans = logical(booleans); + +float_metadata = p_metadata(booleans); + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/get_data_with_setting.m b/lib/vendor/nemo2profilelib/+nemo/get_data_with_setting.m new file mode 100644 index 0000000..e650a35 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/get_data_with_setting.m @@ -0,0 +1,35 @@ +function [ metadata, nemo_data ] = get_data_with_setting( p_metadata, p_nemo_data, p_setting_name, p_setting_value ) +%GET_DATA_FOR_OVERVIEW_PLOTS Summary of this function goes here +% Detailed explanation goes here + +%% Parameter check +if ~iscell(p_metadata) || ~iscell(p_nemo_data) || ~ischar(p_setting_name) || ~exist('p_setting_value', 'var') + disp([mfilename ': Please check your input!']); +end + +%% Get data that matches + + +metadata = cellfun(@(x) match_float(x), p_metadata, 'UniformOutput', false); +metadata = metadata(cellfun(@(x) ~isempty(x), metadata)); +nemo_data = cellfun(@(x) match_float_to_metadata(x), p_nemo_data, 'UniformOutput', false); +nemo_data = nemo_data(cellfun(@(x) ~isempty(x), nemo_data)); + + + function [ x ] = match_float(meta) + x = []; + if meta.SETTINGS.(p_setting_name) == p_setting_value + x = meta; + end + end + + function [ y ] = match_float_to_metadata(x) + if (isempty(nemo.find_float_in_metadata(metadata, x{1}.FLOAT_IDENTIFICATION.float_serial_number))) + y = []; + else + y = x; + end + end + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/get_profiles_in_polygon.m b/lib/vendor/nemo2profilelib/+nemo/get_profiles_in_polygon.m new file mode 100644 index 0000000..b0c0abb --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/get_profiles_in_polygon.m @@ -0,0 +1,88 @@ +function [ filtered_nemo_data ] = get_profiles_in_polygon( p_nemo_data, ... + p_xv, p_yv ) +%GET_PROFILES_IN_POLYGON Returns all profiles in the given polygon. +% This function returns all profiles that have been found in the given +% polygon. The check is being performed with the built in MATLAB function +% inpolygon. Please check documentation to be sure p_xv and p_yv is being +% set correctly. The structure of p_nemo_data is being held, all profiles +% that are not in the polygon are being removed. If p_nemo_data is a +% string, it reads the profiles using nemo.load_data(). +% +% Returns: p_nemo_data with filtered profiles, false on fail. +% +% Requirements: +% - read_profiles.m: Containing function that reads nemo profiles from a +% file into a multidimensional struct. +% +% Parameters: +% p_nemo_data: Either has to be a multidimensional struct (created by +% nemo.load.data()) or a string, where the nemo float data +% is stored. +% p_xv, p_yv: X and Y coordinates of the polygon. To get examples how +% they should be set up, see "help inpolygon" + + +%% Initialize return variables + +filtered_nemo_data = {}; + +%% Check parameters +parameter_error = false; + +if (ischar(p_nemo_data)) % read profiles using read_profiles.m + if (~isdir(p_nemo_data)) % parameter is not a directory + parameter_error = true; + else % read profiles + p_nemo_data = nemo.load.data(p_nemo_data); + end +else if (~iscell(p_nemo_data)) % raise error + parameter_error = true; + end +end + + +if (parameter_error) % display error message + disp([ mfilename ': Wrong parameter! Please provide either a ' ... + 'directory path or a struct created by read_profiles.m!']); + return; +else % do some cleanup + clear parameter_error; +end + + +%% Delete all profiles that are not in polygon + +%nemo_field_names = fieldnames(p_nemo_data); % get all float names + +for index_current_float = 1:length(p_nemo_data) % process every single float + current_profiles = p_nemo_data{index_current_float}; + profile_count = length(current_profiles); + filtered_profiles = {}; + + % loop through current_profiles + index_current_profile = 1; + while (index_current_profile <= profile_count) + % get position + xq = current_profiles{index_current_profile}.SECOND_ORDER_INFORMATION.best_guess_position_lon; + yq = current_profiles{index_current_profile}.SECOND_ORDER_INFORMATION.best_guess_position_lat; + + if (inpolygon(xq, yq, p_xv, p_yv)) + % profile is not in given rectangle, so delete it + filtered_profiles{end + 1} = current_profiles{index_current_profile}; + end + + % increase profile index + index_current_profile = index_current_profile + 1; + end + + % update return variable + if (~isempty(filtered_profiles)) % there are profiles to update + filtered_nemo_data{end + 1} = filtered_profiles; + end + + clear current_profiles profile_field_names profile_count ... + index_current_profile; + +end + +end diff --git a/lib/vendor/nemo2profilelib/+nemo/get_profiles_in_timerange.m b/lib/vendor/nemo2profilelib/+nemo/get_profiles_in_timerange.m new file mode 100644 index 0000000..b38703b --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/get_profiles_in_timerange.m @@ -0,0 +1,87 @@ +function [ filtered_nemo_data ] = get_profiles_in_timerange( p_nemo_data, ... + p_tlower, p_tupper ) +%GET_PROFILES_IN_TIMERANGE Returns all profiles in the given time range. +% This function returns all profiles that have been found in the given +% time range. The check is being performed with the built in MATLAB function +% isbetween. Please check documentation to be sure p_tlower and p_tupper is being +% set correctly. The structure of p_nemo_data is being held, all profiles +% that are not in the timrange are being removed. If p_nemo_data is a +% string, it reads the profiles using nemo.load_data(). +% +% Returns: p_nemo_data with filtered profiles, false on fail. +% +% Requirements: +% - read_profiles.m: Containing function that reads nemo profiles from a +% file into a multidimensional struct. +% +% Parameters: +% p_nemo_data: Either has to be a multidimensional struct (created by +% nemo.load.data()) or a string, where the nemo float data +% is stored. +% p_tlower, p_tupper: Lower and upper time range boundary. To get examples how +% they should be set up, see "help inpolygon" + + +%% Initialize return variables + +filtered_nemo_data = {}; + +%% Check parameters +parameter_error = false; + +if (ischar(p_nemo_data)) % read profiles using read_profiles.m + if (~isdir(p_nemo_data)) % parameter is not a directory + parameter_error = true; + else % read profiles + p_nemo_data = nemo.load.data(p_nemo_data); + end +else if (~iscell(p_nemo_data)) % raise error + parameter_error = true; + end +end + + +if (parameter_error) % display error message + disp([ mfilename ': Wrong parameter! Please provide either a ' ... + 'directory path or a struct created by read_profiles.m!']); + return; +else % do some cleanup + clear parameter_error; +end + + +%% Delete all profiles that are not in polygon + +%nemo_field_names = fieldnames(p_nemo_data); % get all float names + +for index_current_float = 1:length(p_nemo_data) % process every single float + current_profiles = p_nemo_data{index_current_float}; + profile_count = length(current_profiles); + filtered_profiles = {}; + + % loop through current_profiles + index_current_profile = 1; + while (index_current_profile <= profile_count) + % get position + profile_time = datetime(current_profiles{index_current_profile}.SECOND_ORDER_INFORMATION.best_guess_datevec); + + if (isbetween(profile_time, p_tlower, p_tupper)) + % profile is not in given rectangle, so delete it + filtered_profiles{end + 1} = current_profiles{index_current_profile}; + end + + % increase profile index + index_current_profile = index_current_profile + 1; + end + + % update return variable + if (~isempty(filtered_profiles)) % there are profiles to update + filtered_nemo_data{end + 1} = filtered_profiles; + end + + clear current_profiles profile_field_names profile_count ... + index_current_profile; + +end + +end diff --git a/lib/vendor/nemo2profilelib/+nemo/save_data.m b/lib/vendor/nemo2profilelib/+nemo/save_data.m new file mode 100644 index 0000000..4841142 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/save_data.m @@ -0,0 +1,101 @@ +function [ boolean ] = save_data( p_nemo_data, p_config ) +%SAVE_DATA Writes the input nemo data using the given config. +% This function saves given nemo data using nemo.pofile.save(). All +% required options have to be stored in p_config.OUTPUT_FILES. +% +% Returns: Boolean variable, being false if something went wrong during +% write. +% +% Parameters: +% p_nemo_data: Contains structure with all float and profile information. +% p_config: Contains all config options that are required, such as +% output path as well as filename format (currently not +% implemented) of profile files. +% + +%% Initialize return variables +boolean = false; + +%% Parameter check +parameter_error = false; + +if (~iscell(p_nemo_data) || ~isstruct(p_config)) + parameter_error = true; +end + +if (parameter_error) % display error message + disp([ mfilename ': Wrong parameter! Please check your input parameters carefully!']); + return; +else % do some cleanup + clear parameter_error; +end + +%% Save all profiles + +output_folder = p_config.OUTPUT_FILES.output_path; +header_filename = p_config.OUTPUT_FILES.profile_header_file; + +if (~exist(header_filename, 'file')) + warning('Specified header file does not exist. Please check your configuration!'); + return; +end + +float_count = length(p_nemo_data); + +if (~exist(output_folder, 'dir')) + if (~mkdir(output_folder)) + warning('Could not create output folder for nemo profiles!'); + return; + end +end + +for o_float = 1:float_count % every float will be processed + profile_count = length(p_nemo_data{o_float}); + profile_subfoldername = sprintf('%04d', p_nemo_data{o_float}{1}.FLOAT_IDENTIFICATION.float_serial_number); + profile_folder = [output_folder profile_subfoldername p_config.GENERAL.folder_separator]; + + % check if folder exists + if (~exist(profile_folder, 'dir')) + if (~mkdir(profile_folder)) + warning(['Could not create output subfolder for nemo float number ' profile_subfoldername '!']); + return; + end + end + + for o_profile = 1:profile_count + % construct desired profile filename + profile_number = sprintf('%04d', p_nemo_data{o_float}{o_profile}.PROFILE_TECHNICAL_DATA.xmit_profile_number); + if isnan(p_nemo_data{o_float}{o_profile}.PROFILE_TECHNICAL_DATA.xmit_profile_number) + disp([mfilename ': Float ' profile_subfoldername ... + ' contains a profile without xmit_profile_number!']); + continue + end + output_filename = [profile_folder profile_subfoldername '_' ... + profile_number '.profile']; + + overwrite_file = 1; % if true, existing files will be overwritten + if (p_config.OUTPUT_FILES.update_changed_files_only) ... + && exist(output_filename, 'file') + % load existing profile + old_profile = nemo.profile.load(output_filename); + if nemo.profile.compare(old_profile, ... + p_nemo_data{o_float}{o_profile}) + % profiles are the same, so do not overwrite current one + overwrite_file = 0; + end + clear old_profile + end + + if ~nemo.profile.save(header_filename, ... + p_nemo_data{o_float}{o_profile}, output_filename, overwrite_file) + warning(['Writing profile ' profile_number ' of float with serial ' profile_subfoldername ' failed!' ]); + end + end +end + +boolean = true; + + + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/save_startup_messages.m b/lib/vendor/nemo2profilelib/+nemo/save_startup_messages.m new file mode 100644 index 0000000..de5ad4a --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/save_startup_messages.m @@ -0,0 +1,29 @@ +function [ boolean ] = save_startup_messages( p_config, p_startup_messages ) +%SAVE_STARTUP_MESSAGES Summary of this function goes here +% Detailed explanation goes here + +%% Initialize return variable +boolean = false; + +%% Parameter check +if ~isstruct(p_config) || ~iscell(p_startup_messages) + disp([ mfilename ': Wrong parameter! Please check your input parameters carefully!']); + return; +end + +%% Save all messages to the specified folder +if ~isfield(p_config.OUTPUT_FILES, 'output_path_startup_messages') + disp([ mfilename ': Parameter output_path_startup_messages not specified!']); +end + +output_folder = p_config.OUTPUT_FILES.output_path_startup_messages; + +% remove empty cells if there are some +p_startup_messages = p_startup_messages(~cellfun(@(x) isempty(x), p_startup_messages)); + +boolean = all(cellfun(@(x) nemo.float.save_startup_message(x, output_folder), p_startup_messages)); + + + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/sort_data_by_float_serial.m b/lib/vendor/nemo2profilelib/+nemo/sort_data_by_float_serial.m new file mode 100644 index 0000000..5f98045 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/sort_data_by_float_serial.m @@ -0,0 +1,36 @@ +function [ sorted_nemo_data ] = sort_data_by_float_serial( p_nemo_data ) +%SORT_DATA_BY_FLOAT_SERIAL Summary of this function goes here +% Detailed explanation goes here + +%% Initialize return variable +sorted_nemo_data = {}; + +%% Parameter check +if ~iscell(p_nemo_data) + disp([mfilename ': Please check your input parameter!']); + return; +end + +%% Sort by float serial + +float_serials = cellfun(@(x) x{1}.FLOAT_IDENTIFICATION.float_serial_number, p_nemo_data); + +[~, indices] = sort(float_serials); +% create helping variable to check if the float is already sorted +sorted = [1:length(float_serials)]; +if indices == sorted + % indeed it is, so save work and just return the input one :) + sorted_nemo_data = p_nemo_data; + return +end + +% sorting required +sorted_nemo_data = {}; + +% indices have already been taken so just run through them +for i = 1:length(p_nemo_data) + sorted_nemo_data{i} = p_nemo_data{indices(i)}; +end + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/sync_metadata_to_nemodata.m b/lib/vendor/nemo2profilelib/+nemo/sync_metadata_to_nemodata.m new file mode 100644 index 0000000..2c37411 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/sync_metadata_to_nemodata.m @@ -0,0 +1,16 @@ +function [ metadata ] = sync_metadata_to_nemodata( p_nemo_data, p_metadata ) +%SYNC_METADATA_NEMODATA Summary of this function goes here +% Detailed explanation goes here + + +%% Initialize return variables +metadata = {}; + +%% Get all IMEIS of the floats of nemo data + +imeis = cellfun(@(x) x{1}.FLOAT_IDENTIFICATION.transmission_id_number_dec, p_nemo_data); +metadata = nemo.find_float_in_metadata(p_metadata, imeis); + + +end + diff --git a/lib/vendor/nemo2profilelib/+nemo/update_nemo_data_mat.m b/lib/vendor/nemo2profilelib/+nemo/update_nemo_data_mat.m new file mode 100644 index 0000000..6c01f04 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+nemo/update_nemo_data_mat.m @@ -0,0 +1,44 @@ +function [ boolean ] = update_nemo_data_mat( p_nemo_data_filename, p_updated_floats ) +%UPDATE_NEMO_DATA_MAT Summary of this function goes here +% Detailed explanation goes here + +%% Initialize return variable +boolean = false; + +%% Parameter check +if ~ischar(p_nemo_data_filename) || ~iscell(p_updated_floats) + disp([mfilename 'Please check input parameter!']); + return +end + +%% Load mat file +if exist(p_nemo_data_filename, 'file') == 2 + n = load(p_nemo_data_filename); + nemo_data = n.nemo_data; + clear n +else + nemo_data = {}; +end + +%% Replace updated floats in nemo data +for i = 1:length(p_updated_floats) + [~, index] = nemo.float.find(nemo_data, p_updated_floats{1}{1}.FLOAT_IDENTIFICATION.float_serial_number); + if index == 0 % float is not present + nemo_data{end + 1} = p_updated_floats{1}; + else + nemo_data{index} = p_updated_floats{1}; + p_updated_floats(1) = []; + end +end + +%% Sort floats by serial number + +nemo_data = nemo.sort_data_by_float_serial(nemo_data); + +%% Save to file +save(p_nemo_data_filename, 'nemo_data'); + +boolean = true; + +end + diff --git a/lib/vendor/nemo2profilelib/+utils/suplabel.m b/lib/vendor/nemo2profilelib/+utils/suplabel.m new file mode 100644 index 0000000..7845259 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+utils/suplabel.m @@ -0,0 +1,101 @@ +function [ax,h]=suplabel(text,whichLabel,supAxes) +% PLaces text as a title, xlabel, or ylabel on a group of subplots. +% Returns a handle to the label and a handle to the axis. +% [ax,h]=suplabel(text,whichLabel,supAxes) +% returns handles to both the axis and the label. +% ax=suplabel(text,whichLabel,supAxes) +% returns a handle to the axis only. +% suplabel(text) with one input argument assumes whichLabel='x' +% +% whichLabel is any of 'x', 'y', 'yy', or 't', specifying whether the +% text is to be the xlable, ylabel, right side y-label, +% or title respectively. +% +% supAxes is an optional argument specifying the Position of the +% "super" axes surrounding the subplots. +% supAxes defaults to [.08 .08 .84 .84] +% specify supAxes if labels get chopped or overlay subplots +% +% EXAMPLE: +% subplot(2,2,1);ylabel('ylabel1');title('title1') +% subplot(2,2,2);ylabel('ylabel2');title('title2') +% subplot(2,2,3);ylabel('ylabel3');xlabel('xlabel3') +% subplot(2,2,4);ylabel('ylabel4');xlabel('xlabel4') +% [ax1,h1]=suplabel('super X label'); +% [ax2,h2]=suplabel('super Y label','y'); +% [ax3,h2]=suplabel('super Y label (right)','yy'); +% [ax4,h3]=suplabel('super Title' ,'t'); +% set(h3,'FontSize',30) +% +% SEE ALSO: text, title, xlabel, ylabel, zlabel, subplot, +% suptitle (Matlab Central) + +% Author: Ben Barrowes <barrowes@alum.mit.edu> + +%modified 3/16/2010 by IJW to make axis behavior re "zoom" on exit same as +%at beginning. Requires adding tag to the invisible axes + + +currax=findobj(gcf,'type','axes','-not','tag','suplabel'); + +if nargin < 3 + supAxes=[.08 .08 .84 .84]; + ah=findall(gcf,'type','axes'); + if ~isempty(ah) + supAxes=[inf,inf,0,0]; + leftMin=inf; bottomMin=inf; leftMax=0; bottomMax=0; + axBuf=.04; + set(ah,'units','normalized') + ah=findall(gcf,'type','axes'); + for ii=1:length(ah) + if strcmp(get(ah(ii),'Visible'),'on') + thisPos=get(ah(ii),'Position'); + leftMin=min(leftMin,thisPos(1)); + bottomMin=min(bottomMin,thisPos(2)); + leftMax=max(leftMax,thisPos(1)+thisPos(3)); + bottomMax=max(bottomMax,thisPos(2)+thisPos(4)); + end + end + supAxes=[leftMin-axBuf,bottomMin-axBuf,leftMax-leftMin+axBuf*2,bottomMax-bottomMin+axBuf*2]; + end +end +if nargin < 2, whichLabel = 'x'; end +if nargin < 1, help(mfilename); return; end + +if ~isstr(text) | ~isstr(whichLabel) + error('text and whichLabel must be strings') +end +whichLabel=lower(whichLabel); + +ax=axes('Units','Normal','Position',supAxes,'Visible','off','tag','suplabel'); +if strcmp('t',whichLabel) + set(get(ax,'Title'),'Visible','on') + title(text); +elseif strcmp('x',whichLabel) + set(get(ax,'XLabel'),'Visible','on') + xlabel(text); +elseif strcmp('y',whichLabel) + set(get(ax,'YLabel'),'Visible','on') + ylabel(text); +elseif strcmp('yy',whichLabel) + set(get(ax,'YLabel'),'Visible','on') + ylabel(text); + set(ax,'YAxisLocation','right') +end + +%for k=1:length(currax), axes(currax(k));end % restore all other axes + +if (nargout < 2) + return +end +if strcmp('t',whichLabel) + h=get(ax,'Title'); + set(h,'VerticalAlignment','middle') +elseif strcmp('x',whichLabel) + h=get(ax,'XLabel'); +elseif strcmp('y',whichLabel) | strcmp('yy',whichLabel) + h=get(ax,'YLabel'); +end + +%%%ah=findall(gcf,'type','axes'); +%%%'sssssssss',kb \ No newline at end of file diff --git a/lib/vendor/nemo2profilelib/+utils/xml_read.m b/lib/vendor/nemo2profilelib/+utils/xml_read.m new file mode 100644 index 0000000..845efd0 --- /dev/null +++ b/lib/vendor/nemo2profilelib/+utils/xml_read.m @@ -0,0 +1,550 @@ +function [tree, RootName, DOMnode] = xml_read(xmlfile, Pref) +%XML_READ reads xml files and converts them into Matlab's struct tree. +% +% DESCRIPTION +% tree = xml_read(xmlfile) reads 'xmlfile' into data structure 'tree' +% +% tree = xml_read(xmlfile, Pref) reads 'xmlfile' into data structure 'tree' +% according to your preferences +% +% [tree, RootName, DOMnode] = xml_read(xmlfile) get additional information +% about XML file +% +% INPUT: +% xmlfile URL or filename of xml file to read +% Pref Preferences: +% Pref.ItemName - default 'item' - name of a special tag used to itemize +% cell arrays +% Pref.ReadAttr - default true - allow reading attributes +% Pref.ReadSpec - default true - allow reading special nodes +% Pref.Str2Num - default 'smart' - convert strings that look like numbers +% to numbers. Options: "always", "never", and "smart" +% Pref.KeepNS - default true - keep or strip namespace info +% Pref.NoCells - default true - force output to have no cell arrays +% Pref.Debug - default false - show mode specific error messages +% Pref.NumLevels- default infinity - how many recursive levels are +% allowed. Can be used to speed up the function by prunning the tree. +% Pref.RootOnly - default true - output variable 'tree' corresponds to +% xml file root element, otherwise it correspond to the whole file. +% Pref.CellItem - default 'true' - leave 'item' nodes in cell notation. +% OUTPUT: +% tree tree of structs and/or cell arrays corresponding to xml file +% RootName XML tag name used for root (top level) node. +% Optionally it can be a string cell array storing: Name of +% root node, document "Processing Instructions" data and +% document "comment" string +% DOMnode output of xmlread +% +% DETAILS: +% Function xml_read first calls MATLAB's xmlread function and than +% converts its output ('Document Object Model' tree of Java objects) +% to tree of MATLAB struct's. The output is in format of nested structs +% and cells. In the output data structure field names are based on +% XML tags, except in cases when tags produce illegal variable names. +% +% Several special xml node types result in special tags for fields of +% 'tree' nodes: +% - node.CONTENT - stores data section of the node if other fields are +% present. Usually data section is stored directly in 'node'. +% - node.ATTRIBUTE.name - stores node's attribute called 'name'. +% - node.COMMENT - stores node's comment section (string). For global +% comments see "RootName" output variable. +% - node.CDATA_SECTION - stores node's CDATA section (string). +% - node.PROCESSING_INSTRUCTIONS - stores "processing instruction" child +% node. For global "processing instructions" see "RootName" output variable. +% - other special node types like: document fragment nodes, document type +% nodes, entity nodes, notation nodes and processing instruction nodes +% will be treated like regular nodes +% +% EXAMPLES: +% MyTree=[]; +% MyTree.MyNumber = 13; +% MyTree.MyString = 'Hello World'; +% xml_write('test.xml', MyTree); +% [tree treeName] = xml_read ('test.xml'); +% disp(treeName) +% gen_object_display() +% % See also xml_examples.m +% +% See also: +% xml_write, xmlread, xmlwrite +% +% Written by Jarek Tuszynski, SAIC, jaroslaw.w.tuszynski_at_saic.com +% References: +% - Function inspired by Example 3 found in xmlread function. +% - Output data structures inspired by xml_toolbox structures. + +%% default preferences +DPref.TableName = {'tr','td'}; % name of a special tags used to itemize 2D cell arrays +DPref.ItemName = 'item'; % name of a special tag used to itemize 1D cell arrays +DPref.CellItem = false; % leave 'item' nodes in cell notation +DPref.ReadAttr = true; % allow reading attributes +DPref.ReadSpec = true; % allow reading special nodes: comments, CData, etc. +DPref.KeepNS = true; % Keep or strip namespace info +DPref.Str2Num = 'smart';% convert strings that look like numbers to numbers +DPref.NoCells = true; % force output to have no cell arrays +DPref.NumLevels = 1e10; % number of recurence levels +DPref.PreserveSpace = false; % Preserve or delete spaces at the beggining and the end of stings? +RootOnly = true; % return root node with no top level special nodes +Debug = false; % show specific errors (true) or general (false)? +tree = []; +RootName = []; + +%% Check Matlab Version +v = ver('MATLAB'); +version = str2double(regexp(v.Version, '\d.\d','match','once')); +if (version<7.1) + error('Your MATLAB version is too old. You need version 7.1 or newer.'); +end + +%% read user preferences +if (nargin>1) + if (isfield(Pref, 'TableName')), DPref.TableName = Pref.TableName; end + if (isfield(Pref, 'ItemName' )), DPref.ItemName = Pref.ItemName; end + if (isfield(Pref, 'CellItem' )), DPref.CellItem = Pref.CellItem; end + if (isfield(Pref, 'Str2Num' )), DPref.Str2Num = Pref.Str2Num ; end + if (isfield(Pref, 'NoCells' )), DPref.NoCells = Pref.NoCells ; end + if (isfield(Pref, 'NumLevels')), DPref.NumLevels = Pref.NumLevels; end + if (isfield(Pref, 'ReadAttr' )), DPref.ReadAttr = Pref.ReadAttr; end + if (isfield(Pref, 'ReadSpec' )), DPref.ReadSpec = Pref.ReadSpec; end + if (isfield(Pref, 'KeepNS' )), DPref.KeepNS = Pref.KeepNS; end + if (isfield(Pref, 'RootOnly' )), RootOnly = Pref.RootOnly; end + if (isfield(Pref, 'Debug' )), Debug = Pref.Debug ; end + if (isfield(Pref, 'PreserveSpace')), DPref.PreserveSpace = Pref.PreserveSpace; end +end +if ischar(DPref.Str2Num), % convert from character description to numbers + DPref.Str2Num = find(strcmpi(DPref.Str2Num, {'never', 'smart', 'always'}))-1; + if isempty(DPref.Str2Num), DPref.Str2Num=1; end % 1-smart by default +end + +%% read xml file using Matlab function +if isa(xmlfile, 'org.apache.xerces.dom.DeferredDocumentImpl'); + % if xmlfile is a DOMnode than skip the call to xmlread + try + try + DOMnode = xmlfile; + catch ME + error('Invalid DOM node: \n%s.', getReport(ME)); + end + catch %#ok<CTCH> catch for mablab versions prior to 7.5 + error('Invalid DOM node. \n'); + end +else % we assume xmlfile is a filename + if (Debug) % in debuging mode crashes are allowed + DOMnode = xmlread(xmlfile); + else % in normal mode crashes are not allowed + try + try + DOMnode = xmlread(xmlfile); + catch ME + error('Failed to read XML file %s: \n%s',xmlfile, getReport(ME)); + end + catch %#ok<CTCH> catch for mablab versions prior to 7.5 + error('Failed to read XML file %s\n',xmlfile); + end + end +end +Node = DOMnode.getFirstChild; + +%% Find the Root node. Also store data from Global Comment and Processing +% Instruction nodes, if any. +GlobalTextNodes = cell(1,3); +GlobalProcInst = []; +GlobalComment = []; +GlobalDocType = []; +while (~isempty(Node)) + if (Node.getNodeType==Node.ELEMENT_NODE) + RootNode=Node; + elseif (Node.getNodeType==Node.PROCESSING_INSTRUCTION_NODE) + data = strtrim(char(Node.getData)); + target = strtrim(char(Node.getTarget)); + GlobalProcInst = [target, ' ', data]; + GlobalTextNodes{2} = GlobalProcInst; + elseif (Node.getNodeType==Node.COMMENT_NODE) + GlobalComment = strtrim(char(Node.getData)); + GlobalTextNodes{3} = GlobalComment; + % elseif (Node.getNodeType==Node.DOCUMENT_TYPE_NODE) + % GlobalTextNodes{4} = GlobalDocType; + end + Node = Node.getNextSibling; +end + +%% parse xml file through calls to recursive DOMnode2struct function +if (Debug) % in debuging mode crashes are allowed + [tree RootName] = DOMnode2struct(RootNode, DPref, 1); +else % in normal mode crashes are not allowed + try + try + [tree RootName] = DOMnode2struct(RootNode, DPref, 1); + catch ME + error('Unable to parse XML file %s: \n %s.',xmlfile, getReport(ME)); + end + catch %#ok<CTCH> catch for mablab versions prior to 7.5 + error('Unable to parse XML file %s.',xmlfile); + end +end + +%% If there were any Global Text nodes than return them +if (~RootOnly) + if (~isempty(GlobalProcInst) && DPref.ReadSpec) + t.PROCESSING_INSTRUCTION = GlobalProcInst; + end + if (~isempty(GlobalComment) && DPref.ReadSpec) + t.COMMENT = GlobalComment; + end + if (~isempty(GlobalDocType) && DPref.ReadSpec) + t.DOCUMENT_TYPE = GlobalDocType; + end + t.(RootName) = tree; + tree=t; +end +if (~isempty(GlobalTextNodes)) + GlobalTextNodes{1} = RootName; + RootName = GlobalTextNodes; +end + + +%% ======================================================================= +% === DOMnode2struct Function =========================================== +% ======================================================================= +function [s TagName LeafNode] = DOMnode2struct(node, Pref, level) + +%% === Step 1: Get node name and check if it is a leaf node ============== +[TagName LeafNode] = NodeName(node, Pref.KeepNS); +s = []; % initialize output structure + +%% === Step 2: Process Leaf Nodes (nodes with no children) =============== +if (LeafNode) + if (LeafNode>1 && ~Pref.ReadSpec), LeafNode=-1; end % tags only so ignore special nodes + if (LeafNode>0) % supported leaf node types + try + try % use try-catch: errors here are often due to VERY large fields (like images) that overflow java memory + s = char(node.getData); + if (isempty(s)), s = ' '; end % make it a string + % for some reason current xmlread 'creates' a lot of empty text + % fields with first chatacter=10 - those will be deleted. + if (~Pref.PreserveSpace || s(1)==10) + if (isspace(s(1)) || isspace(s(end))), s = strtrim(s); end % trim speces is any + end + if (LeafNode==1), s=str2var(s, Pref.Str2Num, 0); end % convert to number(s) if needed + catch ME % catch for mablab versions 7.5 and higher + warning('xml_io_tools:read:LeafRead', ... + 'This leaf node could not be read and was ignored. '); + getReport(ME) + end + catch %#ok<CTCH> catch for mablab versions prior to 7.5 + warning('xml_io_tools:read:LeafRead', ... + 'This leaf node could not be read and was ignored. '); + end + end + if (LeafNode==3) % ProcessingInstructions need special treatment + target = strtrim(char(node.getTarget)); + s = [target, ' ', s]; + end + return % We are done the rest of the function deals with nodes with children +end +if (level>Pref.NumLevels+1), return; end % if Pref.NumLevels is reached than we are done + +%% === Step 3: Process nodes with children =============================== +if (node.hasChildNodes) % children present + Child = node.getChildNodes; % create array of children nodes + nChild = Child.getLength; % number of children + + % --- pass 1: how many children with each name ----------------------- + f = []; + for iChild = 1:nChild % read in each child + [cname cLeaf] = NodeName(Child.item(iChild-1), Pref.KeepNS); + if (cLeaf<0), continue; end % unsupported leaf node types + if (~isfield(f,cname)), + f.(cname)=0; % initialize first time I see this name + end + f.(cname) = f.(cname)+1; % add to the counter + end % end for iChild + % text_nodes become CONTENT & for some reason current xmlread 'creates' a + % lot of empty text fields so f.CONTENT value should not be trusted + if (isfield(f,'CONTENT') && f.CONTENT>2), f.CONTENT=2; end + + % --- pass 2: store all the children as struct of cell arrays ---------- + for iChild = 1:nChild % read in each child + [c cname cLeaf] = DOMnode2struct(Child.item(iChild-1), Pref, level+1); + if (cLeaf && isempty(c)) % if empty leaf node than skip + continue; % usually empty text node or one of unhandled node types + elseif (nChild==1 && cLeaf==1) + s=c; % shortcut for a common case + else % if normal node + if (level>Pref.NumLevels), continue; end + n = f.(cname); % how many of them in the array so far? + if (~isfield(s,cname)) % encountered this name for the first time + if (n==1) % if there will be only one of them ... + s.(cname) = c; % than save it in format it came in + else % if there will be many of them ... + s.(cname) = cell(1,n); + s.(cname){1} = c; % than save as cell array + end + f.(cname) = 1; % initialize the counter + else % already have seen this name + s.(cname){n+1} = c; % add to the array + f.(cname) = n+1; % add to the array counter + end + end + end % for iChild +end % end if (node.hasChildNodes) + +%% === Step 4: Post-process struct's created for nodes with children ===== +if (isstruct(s)) + fields = fieldnames(s); + nField = length(fields); + + % Detect structure that looks like Html table and store it in cell Matrix + if (nField==1 && strcmpi(fields{1},Pref.TableName{1})) + tr = s.(Pref.TableName{1}); + fields2 = fieldnames(tr{1}); + if (length(fields2)==1 && strcmpi(fields2{1},Pref.TableName{2})) + % This seems to be a special structure such that for + % Pref.TableName = {'tr','td'} 's' corresponds to + % <tr> <td>M11</td> <td>M12</td> </tr> + % <tr> <td>M12</td> <td>M22</td> </tr> + % Recognize it as encoding for 2D struct + nr = length(tr); + for r = 1:nr + row = tr{r}.(Pref.TableName{2}); + Table(r,1:length(row)) = row; %#ok<AGROW> + end + s = Table; + end + end + + % --- Post-processing: convert 'struct of cell-arrays' to 'array of structs' + % Example: let say s has 3 fields s.a, s.b & s.c and each field is an + % cell-array with more than one cell-element and all 3 have the same length. + % Then change it to array of structs, each with single cell. + % This way element s.a{1} will be now accessed through s(1).a + vec = zeros(size(fields)); + for i=1:nField, vec(i) = f.(fields{i}); end + if (numel(vec)>1 && vec(1)>1 && var(vec)==0) % convert from struct of + s = cell2struct(struct2cell(s), fields, 1); % arrays to array of struct + end % if anyone knows better way to do above conversion please let me know. + +end + +%% === Step 5: Process nodes with attributes ============================= +if (node.hasAttributes && Pref.ReadAttr) + if (~isstruct(s)), % make into struct if is not already + ss.CONTENT=s; + s=ss; + end + Attr = node.getAttributes; % list of all attributes + for iAttr = 1:Attr.getLength % for each attribute + name = char(Attr.item(iAttr-1).getName); % attribute name + name = str2varName(name, Pref.KeepNS); % fix name if needed + value = char(Attr.item(iAttr-1).getValue); % attribute value + value = str2var(value, Pref.Str2Num, 1); % convert to number if possible + s.ATTRIBUTE.(name) = value; % save again + end % end iAttr loop +end % done with attributes +if (~isstruct(s)), return; end %The rest of the code deals with struct's + +%% === Post-processing: fields of "s" +% convert 'cell-array of structs' to 'arrays of structs' +fields = fieldnames(s); % get field names +nField = length(fields); +for iItem=1:length(s) % for each struct in the array - usually one + for iField=1:length(fields) + field = fields{iField}; % get field name + % if this is an 'item' field and user want to leave those as cells + % than skip this one + if (strcmpi(field, Pref.ItemName) && Pref.CellItem), continue; end + x = s(iItem).(field); + if (iscell(x) && all(cellfun(@isstruct,x(:))) && numel(x)>1) % it's cell-array of structs + % numel(x)>1 check is to keep 1 cell-arrays created when Pref.CellItem=1 + try % this operation fails sometimes + % example: change s(1).a{1}.b='jack'; s(1).a{2}.b='john'; to + % more convinient s(1).a(1).b='jack'; s(1).a(2).b='john'; + s(iItem).(field) = [x{:}]'; %#ok<AGROW> % converted to arrays of structs + catch %#ok<CTCH> + % above operation will fail if s(1).a{1} and s(1).a{2} have + % different fields. If desired, function forceCell2Struct can force + % them to the same field structure by adding empty fields. + if (Pref.NoCells) + s(iItem).(field) = forceCell2Struct(x); %#ok<AGROW> + end + end % end catch + end + end +end + +%% === Step 4: Post-process struct's created for nodes with children ===== + +% --- Post-processing: remove special 'item' tags --------------------- +% many xml writes (including xml_write) use a special keyword to mark +% arrays of nodes (see xml_write for examples). The code below converts +% s.item to s.CONTENT +ItemContent = false; +if (isfield(s,Pref.ItemName)) + s.CONTENT = s.(Pref.ItemName); + s = rmfield(s,Pref.ItemName); + ItemContent = Pref.CellItem; % if CellItem than keep s.CONTENT as cells +end + +% --- Post-processing: clean up CONTENT tags --------------------- +% if s.CONTENT is a cell-array with empty elements at the end than trim +% the length of this cell-array. Also if s.CONTENT is the only field than +% remove .CONTENT part and store it as s. +if (isfield(s,'CONTENT')) + if (iscell(s.CONTENT) && isvector(s.CONTENT)) + x = s.CONTENT; + for i=numel(x):-1:1, if ~isempty(x{i}), break; end; end + if (i==1 && ~ItemContent) + s.CONTENT = x{1}; % delete cell structure + else + s.CONTENT = x(1:i); % delete empty cells + end + end + if (nField==1) + if (ItemContent) + ss = s.CONTENT; % only child: remove a level but ensure output is a cell-array + s=[]; s{1}=ss; + else + s = s.CONTENT; % only child: remove a level + end + end +end + + + +%% ======================================================================= +% === forceCell2Struct Function ========================================= +% ======================================================================= +function s = forceCell2Struct(x) +% Convert cell-array of structs, where not all of structs have the same +% fields, to a single array of structs + +%% Convert 1D cell array of structs to 2D cell array, where each row +% represents item in original array and each column corresponds to a unique +% field name. Array "AllFields" store fieldnames for each column +AllFields = fieldnames(x{1}); % get field names of the first struct +CellMat = cell(length(x), length(AllFields)); +for iItem=1:length(x) + fields = fieldnames(x{iItem}); % get field names of the next struct + for iField=1:length(fields) % inspect all fieldnames and find those + field = fields{iField}; % get field name + col = find(strcmp(field,AllFields),1); + if isempty(col) % no column for such fieldname yet + AllFields = [AllFields; field]; %#ok<AGROW> + col = length(AllFields); % create a new column for it + end + CellMat{iItem,col} = x{iItem}.(field); % store rearanged data + end +end +%% Convert 2D cell array to array of structs +s = cell2struct(CellMat, AllFields, 2); + +%% ======================================================================= +% === str2var Function ================================================== +% ======================================================================= +function val=str2var(str, option, attribute) +% Can this string 'str' be converted to a number? if so than do it. +val = str; +len = numel(str); +if (len==0 || option==0), return; end % Str2Num="never" of empty string -> do not do enything +if (len>10000 && option==1), return; end % Str2Num="smart" and string is very long -> probably base64 encoded binary +digits = '(Inf)|(NaN)|(pi)|[\t\n\d\+\-\*\.ei EI\[\]\;\,]'; +s = regexprep(str, digits, ''); % remove all the digits and other allowed characters +if (~all(~isempty(s))) % if nothing left than this is probably a number + if (~isempty(strfind(str, ' '))), option=2; end %if str has white-spaces assume by default that it is not a date string + if (~isempty(strfind(str, '['))), option=2; end % same with brackets + str(strfind(str, '\n')) = ';';% parse data tables into 2D arrays, if any + if (option==1) % the 'smart' option + try % try to convert to a date, like 2007-12-05 + datenum(str); % if successful than leave it as string + catch %#ok<CTCH> % if this is not a date than ... + option=2; % ... try converting to a number + end + end + if (option==2) + if (attribute) + num = str2double(str); % try converting to a single number using sscanf function + if isnan(num), return; end % So, it wasn't really a number after all + else + num = str2num(str); %#ok<ST2NM> % try converting to a single number or array using eval function + end + if(isnumeric(num) && numel(num)>0), val=num; end % if convertion to a single was succesful than save + end +elseif ((str(1)=='[' && str(end)==']') || (str(1)=='{' && str(end)=='}')) % this looks like a (cell) array encoded as a string + try + val = eval(str); + catch %#ok<CTCH> + val = str; + end +elseif (~attribute) % see if it is a boolean array with no [] brackets + str1 = lower(str); + str1 = strrep(str1, 'false', '0'); + str1 = strrep(str1, 'true' , '1'); + s = regexprep(str1, '[01 \;\,]', ''); % remove all 0/1, spaces, commas and semicolons + if (~all(~isempty(s))) % if nothing left than this is probably a boolean array + num = str2num(str1); %#ok<ST2NM> + if(isnumeric(num) && numel(num)>0), val = (num>0); end % if convertion was succesful than save as logical + end +end + + +%% ======================================================================= +% === str2varName Function ============================================== +% ======================================================================= +function str = str2varName(str, KeepNS) +% convert a sting to a valid matlab variable name +if(KeepNS) + str = regexprep(str,':','_COLON_', 'once', 'ignorecase'); +else + k = strfind(str,':'); + if (~isempty(k)) + str = str(k+1:end); + end +end +str = regexprep(str,'-','_DASH_' ,'once', 'ignorecase'); +if (~isvarname(str)) && (~iskeyword(str)) + str = genvarname(str); +end + +%% ======================================================================= +% === NodeName Function ================================================= +% ======================================================================= +function [Name LeafNode] = NodeName(node, KeepNS) +% get node name and make sure it is a valid variable name in Matlab. +% also get node type: +% LeafNode=0 - normal element node, +% LeafNode=1 - text node +% LeafNode=2 - supported non-text leaf node, +% LeafNode=3 - supported processing instructions leaf node, +% LeafNode=-1 - unsupported non-text leaf node +switch (node.getNodeType) + case node.ELEMENT_NODE + Name = char(node.getNodeName);% capture name of the node + Name = str2varName(Name, KeepNS); % if Name is not a good variable name - fix it + LeafNode = 0; + case node.TEXT_NODE + Name = 'CONTENT'; + LeafNode = 1; + case node.COMMENT_NODE + Name = 'COMMENT'; + LeafNode = 2; + case node.CDATA_SECTION_NODE + Name = 'CDATA_SECTION'; + LeafNode = 2; + case node.DOCUMENT_TYPE_NODE + Name = 'DOCUMENT_TYPE'; + LeafNode = 2; + case node.PROCESSING_INSTRUCTION_NODE + Name = 'PROCESSING_INSTRUCTION'; + LeafNode = 3; + otherwise + NodeType = {'ELEMENT','ATTRIBUTE','TEXT','CDATA_SECTION', ... + 'ENTITY_REFERENCE', 'ENTITY', 'PROCESSING_INSTRUCTION', 'COMMENT',... + 'DOCUMENT', 'DOCUMENT_TYPE', 'DOCUMENT_FRAGMENT', 'NOTATION'}; + Name = char(node.getNodeName);% capture name of the node + warning('xml_io_tools:read:unkNode', ... + 'Unknown node type encountered: %s_NODE (%s)', NodeType{node.getNodeType}, Name); + LeafNode = -1; +end + + diff --git a/lib/vendor/nemo2profilelib/+utils/xml_write.m b/lib/vendor/nemo2profilelib/+utils/xml_write.m new file mode 100644 index 0000000..10a18de --- /dev/null +++ b/lib/vendor/nemo2profilelib/+utils/xml_write.m @@ -0,0 +1,447 @@ +function DOMnode = xml_write(filename, tree, RootName, Pref) +%XML_WRITE Writes Matlab data structures to XML file +% +% DESCRIPTION +% xml_write( filename, tree) Converts Matlab data structure 'tree' containing +% cells, structs, numbers and strings to Document Object Model (DOM) node +% tree, then saves it to XML file 'filename' using Matlab's xmlwrite +% function. Optionally one can also use alternative version of xmlwrite +% function which directly calls JAVA functions for XML writing without +% MATLAB middleware. This function is provided as a patch to existing +% bugs in xmlwrite (in R2006b). +% +% xml_write(filename, tree, RootName, Pref) allows you to specify +% additional preferences about file format +% +% DOMnode = xml_write([], tree) same as above except that DOM node is +% not saved to the file but returned. +% +% INPUT +% filename file name +% tree Matlab structure tree to store in xml file. +% RootName String with XML tag name used for root (top level) node +% Optionally it can be a string cell array storing: Name of +% root node, document "Processing Instructions" data and +% document "comment" string +% Pref Other preferences: +% Pref.ItemName - default 'item' - name of a special tag used to +% itemize cell or struct arrays +% Pref.XmlEngine - let you choose the XML engine. Currently default is +% 'Xerces', which is using directly the apache xerces java file. +% Other option is 'Matlab' which uses MATLAB's xmlwrite and its +% XMLUtils java file. Both options create identical results except in +% case of CDATA sections where xmlwrite fails. +% Pref.CellItem - default 'true' - allow cell arrays to use 'item' +% notation. See below. +% Pref.RootOnly - default true - output variable 'tree' corresponds to +% xml file root element, otherwise it correspond to the whole file. +% Pref.StructItem - default 'true' - allow arrays of structs to use +% 'item' notation. For example "Pref.StructItem = true" gives: +% <a> +% <b> +% <item> ... <\item> +% <item> ... <\item> +% <\b> +% <\a> +% while "Pref.StructItem = false" gives: +% <a> +% <b> ... <\b> +% <b> ... <\b> +% <\a> +% +% +% Several special xml node types can be created if special tags are used +% for field names of 'tree' nodes: +% - node.CONTENT - stores data section of the node if other fields +% (usually ATTRIBUTE are present. Usually data section is stored +% directly in 'node'. +% - node.ATTRIBUTE.name - stores node's attribute called 'name'. +% - node.COMMENT - create comment child node from the string. For global +% comments see "RootName" input variable. +% - node.PROCESSING_INSTRUCTIONS - create "processing instruction" child +% node from the string. For global "processing instructions" see +% "RootName" input variable. +% - node.CDATA_SECTION - stores node's CDATA section (string). Only works +% if Pref.XmlEngine='Xerces'. For more info, see comments of F_xmlwrite. +% - other special node types like: document fragment nodes, document type +% nodes, entity nodes and notation nodes are not being handled by +% 'xml_write' at the moment. +% +% OUTPUT +% DOMnode Document Object Model (DOM) node tree in the format +% required as input to xmlwrite. (optional) +% +% EXAMPLES: +% MyTree=[]; +% MyTree.MyNumber = 13; +% MyTree.MyString = 'Hello World'; +% xml_write('test.xml', MyTree); +% type('test.xml') +% %See also xml_tutorial.m +% +% See also +% xml_read, xmlread, xmlwrite +% +% Written by Jarek Tuszynski, SAIC, jaroslaw.w.tuszynski_at_saic.com + +%% Check Matlab Version +v = ver('MATLAB'); +v = str2double(regexp(v.Version, '\d.\d','match','once')); +if (v<7) + error('Your MATLAB version is too old. You need version 7.0 or newer.'); +end + +%% default preferences +DPref.TableName = {'tr','td'}; % name of a special tags used to itemize 2D cell arrays +DPref.ItemName = 'item'; % name of a special tag used to itemize 1D cell arrays +DPref.StructItem = true; % allow arrays of structs to use 'item' notation +DPref.CellItem = true; % allow cell arrays to use 'item' notation +DPref.StructTable= 'Html'; +DPref.CellTable = 'Html'; +DPref.XmlEngine = 'Matlab'; % use matlab provided XMLUtils +%DPref.XmlEngine = 'Xerces'; % use Xerces xml generator directly +DPref.PreserveSpace = false; % Preserve or delete spaces at the beggining and the end of stings? +RootOnly = true; % Input is root node only +GlobalProcInst = []; +GlobalComment = []; +GlobalDocType = []; + +%% read user preferences +if (nargin>3) + if (isfield(Pref, 'TableName' )), DPref.TableName = Pref.TableName; end + if (isfield(Pref, 'ItemName' )), DPref.ItemName = Pref.ItemName; end + if (isfield(Pref, 'StructItem')), DPref.StructItem = Pref.StructItem; end + if (isfield(Pref, 'CellItem' )), DPref.CellItem = Pref.CellItem; end + if (isfield(Pref, 'CellTable')), DPref.CellTable = Pref.CellTable; end + if (isfield(Pref, 'StructTable')), DPref.StructTable= Pref.StructTable; end + if (isfield(Pref, 'XmlEngine' )), DPref.XmlEngine = Pref.XmlEngine; end + if (isfield(Pref, 'RootOnly' )), RootOnly = Pref.RootOnly; end + if (isfield(Pref, 'PreserveSpace')), DPref.PreserveSpace = Pref.PreserveSpace; end +end +if (nargin<3 || isempty(RootName)), RootName=inputname(2); end +if (isempty(RootName)), RootName='ROOT'; end +if (iscell(RootName)) % RootName also stores global text node data + rName = RootName; + RootName = char(rName{1}); + if (length(rName)>1), GlobalProcInst = char(rName{2}); end + if (length(rName)>2), GlobalComment = char(rName{3}); end + if (length(rName)>3), GlobalDocType = char(rName{4}); end +end +if(~RootOnly && isstruct(tree)) % if struct than deal with each field separatly + fields = fieldnames(tree); + for i=1:length(fields) + field = fields{i}; + x = tree(1).(field); + if (strcmp(field, 'COMMENT')) + GlobalComment = x; + elseif (strcmp(field, 'PROCESSING_INSTRUCTION')) + GlobalProcInst = x; + elseif (strcmp(field, 'DOCUMENT_TYPE')) + GlobalDocType = x; + else + RootName = field; + t = x; + end + end + tree = t; +end + +%% Initialize jave object that will store xml data structure +RootName = varName2str(RootName); +if (~isempty(GlobalDocType)) + % n = strfind(GlobalDocType, ' '); + % if (~isempty(n)) + % dtype = com.mathworks.xml.XMLUtils.createDocumentType(GlobalDocType); + % end + % DOMnode = com.mathworks.xml.XMLUtils.createDocument(RootName, dtype); + warning('xml_io_tools:write:docType', ... + 'DOCUMENT_TYPE node was encountered which is not supported yet. Ignoring.'); +end +DOMnode = com.mathworks.xml.XMLUtils.createDocument(RootName); + + +%% Use recursive function to convert matlab data structure to XML +root = DOMnode.getDocumentElement; +struct2DOMnode(DOMnode, root, tree, DPref.ItemName, DPref); + +%% Remove the only child of the root node +root = DOMnode.getDocumentElement; +Child = root.getChildNodes; % create array of children nodes +nChild = Child.getLength; % number of children +if (nChild==1) + node = root.removeChild(root.getFirstChild); + while(node.hasChildNodes) + root.appendChild(node.removeChild(node.getFirstChild)); + end + while(node.hasAttributes) % copy all attributes + root.setAttributeNode(node.removeAttributeNode(node.getAttributes.item(0))); + end +end + +%% Save exotic Global nodes +if (~isempty(GlobalComment)) + DOMnode.insertBefore(DOMnode.createComment(GlobalComment), DOMnode.getFirstChild()); +end +if (~isempty(GlobalProcInst)) + n = strfind(GlobalProcInst, ' '); + if (~isempty(n)) + proc = DOMnode.createProcessingInstruction(GlobalProcInst(1:(n(1)-1)),... + GlobalProcInst((n(1)+1):end)); + DOMnode.insertBefore(proc, DOMnode.getFirstChild()); + end +end +% Not supported yet as the code below does not work +% if (~isempty(GlobalDocType)) +% n = strfind(GlobalDocType, ' '); +% if (~isempty(n)) +% dtype = DOMnode.createDocumentType(GlobalDocType); +% DOMnode.insertBefore(dtype, DOMnode.getFirstChild()); +% end +% end + +%% save java DOM tree to XML file +if (~isempty(filename)) + if (strcmpi(DPref.XmlEngine, 'Xerces')) + xmlwrite_xerces(filename, DOMnode); + else + xmlwrite(filename, DOMnode); + end +end + + +%% ======================================================================= +% === struct2DOMnode Function =========================================== +% ======================================================================= +function [] = struct2DOMnode(xml, parent, s, TagName, Pref) +% struct2DOMnode is a recursive function that converts matlab's structs to +% DOM nodes. +% INPUTS: +% xml - jave object that will store xml data structure +% parent - parent DOM Element +% s - Matlab data structure to save +% TagName - name to be used in xml tags describing 's' +% Pref - preferenced +% OUTPUT: +% parent - modified 'parent' + +% perform some conversions +if (ischar(s) && min(size(s))>1) % if 2D array of characters + s=cellstr(s); % than convert to cell array +end +% if (strcmp(TagName, 'CONTENT')) +% while (iscell(s) && length(s)==1), s = s{1}; end % unwrap cell arrays of length 1 +% end +TagName = varName2str(TagName); + +%% == node is a 2D cell array == +% convert to some other format prior to further processing +nDim = nnz(size(s)>1); % is it a scalar, vector, 2D array, 3D cube, etc? +if (iscell(s) && nDim==2 && strcmpi(Pref.CellTable, 'Matlab')) + s = var2str(s, Pref.PreserveSpace); +end +if (nDim==2 && (iscell (s) && strcmpi(Pref.CellTable, 'Vector')) || ... + (isstruct(s) && strcmpi(Pref.StructTable, 'Vector'))) + s = s(:); +end +if (nDim>2), s = s(:); end % can not handle this case well +nItem = numel(s); +nDim = nnz(size(s)>1); % is it a scalar, vector, 2D array, 3D cube, etc? + +%% == node is a cell == +if (iscell(s)) % if this is a cell or cell array + if ((nDim==2 && strcmpi(Pref.CellTable,'Html')) || (nDim< 2 && Pref.CellItem)) + % if 2D array of cells than can use HTML-like notation or if 1D array + % than can use item notation + if (strcmp(TagName, 'CONTENT')) % CONTENT nodes already have <TagName> ... </TagName> + array2DOMnode(xml, parent, s, Pref.ItemName, Pref ); % recursive call + else + node = xml.createElement(TagName); % <TagName> ... </TagName> + array2DOMnode(xml, node, s, Pref.ItemName, Pref ); % recursive call + parent.appendChild(node); + end + else % use <TagName>...<\TagName> <TagName>...<\TagName> notation + array2DOMnode(xml, parent, s, TagName, Pref ); % recursive call + end +%% == node is a struct == +elseif (isstruct(s)) % if struct than deal with each field separatly + if ((nDim==2 && strcmpi(Pref.StructTable,'Html')) || (nItem>1 && Pref.StructItem)) + % if 2D array of structs than can use HTML-like notation or + % if 1D array of structs than can use 'items' notation + node = xml.createElement(TagName); + array2DOMnode(xml, node, s, Pref.ItemName, Pref ); % recursive call + parent.appendChild(node); + elseif (nItem>1) % use <TagName>...<\TagName> <TagName>...<\TagName> notation + array2DOMnode(xml, parent, s, TagName, Pref ); % recursive call + else % otherwise save each struct separatelly + fields = fieldnames(s); + node = xml.createElement(TagName); + for i=1:length(fields) % add field by field to the node + field = fields{i}; + x = s.(field); + switch field + case {'COMMENT', 'CDATA_SECTION', 'PROCESSING_INSTRUCTION'} + if iscellstr(x) % cell array of strings -> add them one by one + array2DOMnode(xml, node, x(:), field, Pref ); % recursive call will modify 'node' + elseif ischar(x) % single string -> add it + struct2DOMnode(xml, node, x, field, Pref ); % recursive call will modify 'node' + else % not a string - Ignore + warning('xml_io_tools:write:badSpecialNode', ... + ['Struct field named ',field,' encountered which was not a string. Ignoring.']); + end + case 'ATTRIBUTE' % set attributes of the node + if (isempty(x)), continue; end + if (isstruct(x)) + attName = fieldnames(x); % get names of all the attributes + for k=1:length(attName) % attach them to the node + att = xml.createAttribute(varName2str(attName(k))); + att.setValue(var2str(x.(attName{k}),Pref.PreserveSpace)); + node.setAttributeNode(att); + end + else + warning('xml_io_tools:write:badAttribute', ... + 'Struct field named ATTRIBUTE encountered which was not a struct. Ignoring.'); + end + otherwise % set children of the node + struct2DOMnode(xml, node, x, field, Pref ); % recursive call will modify 'node' + end + end % end for i=1:nFields + parent.appendChild(node); + end +%% == node is a leaf node == +else % if not a struct and not a cell than it is a leaf node + switch TagName % different processing depending on desired type of the node + case 'COMMENT' % create comment node + com = xml.createComment(s); + parent.appendChild(com); + case 'CDATA_SECTION' % create CDATA Section + cdt = xml.createCDATASection(s); + parent.appendChild(cdt); + case 'PROCESSING_INSTRUCTION' % set attributes of the node + OK = false; + if (ischar(s)) + n = strfind(s, ' '); + if (~isempty(n)) + proc = xml.createProcessingInstruction(s(1:(n(1)-1)),s((n(1)+1):end)); + parent.insertBefore(proc, parent.getFirstChild()); + OK = true; + end + end + if (~OK) + warning('xml_io_tools:write:badProcInst', ... + ['Struct field named PROCESSING_INSTRUCTION need to be',... + ' a string, for example: xml-stylesheet type="text/css" ', ... + 'href="myStyleSheet.css". Ignoring.']); + end + case 'CONTENT' % this is text part of already existing node + txt = xml.createTextNode(var2str(s, Pref.PreserveSpace)); % convert to text + parent.appendChild(txt); + otherwise % I guess it is a regular text leaf node + txt = xml.createTextNode(var2str(s, Pref.PreserveSpace)); + node = xml.createElement(TagName); + node.appendChild(txt); + parent.appendChild(node); + end +end % of struct2DOMnode function + +%% ======================================================================= +% === array2DOMnode Function ============================================ +% ======================================================================= +function [] = array2DOMnode(xml, parent, s, TagName, Pref) +% Deal with 1D and 2D arrays of cell or struct. Will modify 'parent'. +nDim = nnz(size(s)>1); % is it a scalar, vector, 2D array, 3D cube, etc? +switch nDim + case 2 % 2D array + for r=1:size(s,1) + subnode = xml.createElement(Pref.TableName{1}); + for c=1:size(s,2) + v = s(r,c); + if iscell(v), v = v{1}; end + struct2DOMnode(xml, subnode, v, Pref.TableName{2}, Pref ); % recursive call + end + parent.appendChild(subnode); + end + case 1 %1D array + for iItem=1:numel(s) + v = s(iItem); + if iscell(v), v = v{1}; end + struct2DOMnode(xml, parent, v, TagName, Pref ); % recursive call + end + case 0 % scalar -> this case should never be called + if ~isempty(s) + if iscell(s), s = s{1}; end + struct2DOMnode(xml, parent, s, TagName, Pref ); + end +end + +%% ======================================================================= +% === var2str Function ================================================== +% ======================================================================= +function str = var2str(object, PreserveSpace) +% convert matlab variables to a string +switch (1) + case isempty(object) + str = ''; + case (isnumeric(object) || islogical(object)) + if ndims(object)>2, object=object(:); end % can't handle arrays with dimention > 2 + str=mat2str(object); % convert matrix to a string + % mark logical scalars with [] (logical arrays already have them) so the xml_read + % recognizes them as MATLAB objects instead of strings. Same with sparse + % matrices + if ((islogical(object) && isscalar(object)) || issparse(object)), + str = ['[' str ']']; + end + if (isinteger(object)), + str = ['[', class(object), '(', str ')]']; + end + case iscell(object) + if ndims(object)>2, object=object(:); end % can't handle cell arrays with dimention > 2 + [nr nc] = size(object); + obj2 = object; + for i=1:length(object(:)) + str = var2str(object{i}, PreserveSpace); + if (ischar(object{i})), object{i} = ['''' object{i} '''']; else object{i}=str; end + obj2{i} = [object{i} ',']; + end + for r = 1:nr, obj2{r,nc} = [object{r,nc} ';']; end + obj2 = obj2.'; + str = ['{' obj2{:} '}']; + case isstruct(object) + str=''; + warning('xml_io_tools:write:var2str', ... + 'Struct was encountered where string was expected. Ignoring.'); + case isa(object, 'function_handle') + str = ['[@' char(object) ']']; + case ischar(object) + str = object; + otherwise + str = char(object); +end + +%% string clean-up +str=str(:); str=str.'; % make sure this is a row vector of char's +if (~isempty(str)) + str(str<32|str==127)=' '; % convert no-printable characters to spaces + if (~PreserveSpace) + str = strtrim(str); % remove spaces from begining and the end + str = regexprep(str,'\s+',' '); % remove multiple spaces + end +end + +%% ======================================================================= +% === var2Namestr Function ============================================== +% ======================================================================= +function str = varName2str(str) +% convert matlab variable names to a sting +str = char(str); +p = strfind(str,'0x'); +if (~isempty(p)) + for i=1:length(p) + before = str( p(i)+(0:3) ); % string to replace + after = char(hex2dec(before(3:4))); % string to replace with + str = regexprep(str,before,after, 'once', 'ignorecase'); + p=p-3; % since 4 characters were replaced with one - compensate + end +end +str = regexprep(str,'_COLON_',':', 'once', 'ignorecase'); +str = regexprep(str,'_DASH_' ,'-', 'once', 'ignorecase'); + diff --git a/lib/vendor/nemo2profilelib/exclude_elements.m b/lib/vendor/nemo2profilelib/exclude_elements.m new file mode 100644 index 0000000..d3f56bc --- /dev/null +++ b/lib/vendor/nemo2profilelib/exclude_elements.m @@ -0,0 +1,38 @@ +function [ resulting_list ] = exclude_elements( p_source, p_exclude_items, p_source_type ) +%EXCLUDE_ELEMENTS Excludes items from given source. +% Runs through the given source and excludes all given elements from it. +% Currently only source type 'dir' is implemented. +% +% Parameters: +% p_source Input source, class depends on p_source_type. +% p_exclude_items Items to exclude from p_source. +% p_source_type Type of given source, currently only 'dir' is implemented. + +%% Initialize return variables +resulting_list = false; + +%% Parameter check +if nargin ~= 3 + warning([mfilename ': Parameter not set correctly! Returning false.']); + return +end + +%% Switch source type + +switch p_source_type + case 'dir' + tmp_list = []; + for o_index = 1:length(p_source) % check if element is a directory + if ~any(strcmp(p_source(o_index).name, p_exclude_items)) + tmp_list = [tmp_list p_source(o_index)]; + end % ignore every other elements + end + resulting_list = tmp_list; % copy the created list to the correct variable + clear tmp_list o_index; % clean up + otherwise + warning([mfilename ': Unknown source type! Returning false.']); + return +end + +end + diff --git a/lib/vendor/nemo2profilelib/exclude_files.m b/lib/vendor/nemo2profilelib/exclude_files.m new file mode 100644 index 0000000..30992de --- /dev/null +++ b/lib/vendor/nemo2profilelib/exclude_files.m @@ -0,0 +1,30 @@ +function [ folder_list ] = exclude_files( p_source ) +%EXCLUDE_FILES Excludes all files from a list of folders given by p_source. +% Runs through p_source and excludes all files that are found. +% +% Parameters: +% p_source List of directories. + +%% Initialize return variables +folder_list = false; + +%% Parameter check +if ~isstruct(p_source) + warning([mfilename ': Parameter not set correctly! Returning false.']); + return +end + +%% Exclude all files + +tmp_list = []; +for o_index = 1:size(p_source, 1) % check if element is a directory + %if isdir(p_source(o_index).name) + if p_source(o_index).isdir + tmp_list = [tmp_list p_source(o_index)]; + end % ignore every other elements +end +folder_list = tmp_list; % copy the created list to the correct variable +clear tmp_list o_index; % clean up + +end + -- GitLab