From eafcf6fe89312e649e0b8cfc72d92f63fe281dd2 Mon Sep 17 00:00:00 2001 From: Lewin Probst <info@emirror.de> Date: Sat, 2 Nov 2019 11:53:29 +0100 Subject: [PATCH] Improved trajectory output window. + Added checkbox to enable mercator projection + Added checkbox to show/hide position dates + Extended position dates by rafos day + Added colors for trajectories / Improved soundvelocity caluclation + Added clock error calculation --- .../checkboxEnableMercatorProjection.m | 13 ++ .../checkboxShowPositionDates.m | 23 +++ .../+track/+trajectoryOutput/open.m | 5 +- .../+track/+trajectoryOutput/plot.m | 45 ++++- .../+track/+trajectoryOutput/plotTrajectory.m | 2 +- .../tableGeneratedTracksSelect.m | 9 +- lib/+artoa/+controller/+track/run.m | 52 ++++- .../+controller/setPlotHandleVisibility.m | 49 +++++ lib/+artoa/+convert/+mercator/imerc.m | 19 ++ lib/+artoa/+convert/+mercator/merc.m | 15 ++ lib/+artoa/+convert/axes2mercator.m | 63 ++++++ lib/+artoa/+data/calculateEllipk4.m | 32 +++ lib/+artoa/+data/interpolateRafosData.m | 23 ++- .../+float/calculateCombinationSegment.m | 184 ++++++++++++++++++ lib/+artoa/+float/calculateTrajectory.m | 98 +++++----- lib/+artoa/+gui/+track/trajectoryOutput.m | 15 +- lib/+artoa/+vendor/xnavai.m | 180 +++++++++++++++++ 17 files changed, 751 insertions(+), 76 deletions(-) create mode 100644 lib/+artoa/+controller/+track/+trajectoryOutput/checkboxEnableMercatorProjection.m create mode 100644 lib/+artoa/+controller/+track/+trajectoryOutput/checkboxShowPositionDates.m create mode 100644 lib/+artoa/+controller/setPlotHandleVisibility.m create mode 100644 lib/+artoa/+convert/+mercator/imerc.m create mode 100644 lib/+artoa/+convert/+mercator/merc.m create mode 100644 lib/+artoa/+convert/axes2mercator.m create mode 100644 lib/+artoa/+data/calculateEllipk4.m create mode 100644 lib/+artoa/+float/calculateCombinationSegment.m create mode 100644 lib/+artoa/+vendor/xnavai.m diff --git a/lib/+artoa/+controller/+track/+trajectoryOutput/checkboxEnableMercatorProjection.m b/lib/+artoa/+controller/+track/+trajectoryOutput/checkboxEnableMercatorProjection.m new file mode 100644 index 0000000..e842888 --- /dev/null +++ b/lib/+artoa/+controller/+track/+trajectoryOutput/checkboxEnableMercatorProjection.m @@ -0,0 +1,13 @@ +function [] = checkboxEnableMercatorProjection(~, event) +%CHECKBOXUPDATETRACKPARAMETERWINDOW Summary of this function goes here +% Detailed explanation goes here + +global artoaWorkspace; + +artoaWorkspace.trajectoryOutput.enableMercatorProjection = logical(event.Source.Value); + +%% Replot +artoa.controller.track.trajectoryOutput.plot(); + +end + diff --git a/lib/+artoa/+controller/+track/+trajectoryOutput/checkboxShowPositionDates.m b/lib/+artoa/+controller/+track/+trajectoryOutput/checkboxShowPositionDates.m new file mode 100644 index 0000000..b71e3e8 --- /dev/null +++ b/lib/+artoa/+controller/+track/+trajectoryOutput/checkboxShowPositionDates.m @@ -0,0 +1,23 @@ +function [] = checkboxShowPositionDates(~, event) +%CHECKBOXUPDATETRACKPARAMETERWINDOW Summary of this function goes here +% Detailed explanation goes here + +global artoaWorkspace artoaGui; + +artoaWorkspace.trajectoryOutput.showPositionDates = logical(event.Source.Value); + +%% Get trajectory handles +trajectoryHandles = artoaGui.trajectoryOutput.trajectoryHandles; +trajectoryWorkspace = artoaWorkspace.trajectoryOutput.trajectories; + +%% Hide or show text +for i = 1:length(trajectoryHandles) + artoa.controller.setPlotHandleVisibility( ... + trajectoryHandles{i}.textPositions, ... + (artoaWorkspace.trajectoryOutput.showPositionDates && ~trajectoryWorkspace{i}.hidden) ... + ); +end + + +end + diff --git a/lib/+artoa/+controller/+track/+trajectoryOutput/open.m b/lib/+artoa/+controller/+track/+trajectoryOutput/open.m index 53dcc34..ed34452 100644 --- a/lib/+artoa/+controller/+track/+trajectoryOutput/open.m +++ b/lib/+artoa/+controller/+track/+trajectoryOutput/open.m @@ -17,12 +17,13 @@ callbacks = struct(); callbacks.CloseRequestFcn = @artoa.controller.track.trajectoryOutput.close; callbacks.buttonDeleteSingle = @artoa.controller.track.trajectoryOutput.buttonDeleteSelectedTrajectory; callbacks.buttonDeleteAll = ''; -callbacks.checkboxMercator = ''; +callbacks.checkboxMercator = @artoa.controller.track.trajectoryOutput.checkboxEnableMercatorProjection; callbacks.checkboxTrackVisible = ''; callbacks.buttonTrackInfos = ''; callbacks.buttonVelocities = ''; callbacks.tableGeneratedTracksSelect = @artoa.controller.track.trajectoryOutput.tableGeneratedTracksSelect; callbacks.checkboxUpdateTrackParameterWindow = @artoa.controller.track.trajectoryOutput.checkboxUpdateTrackParameterWindow; +callbacks.checkboxShowPositionDates = @artoa.controller.track.trajectoryOutput.checkboxShowPositionDates; %% Open the gui artoa.gui.track.trajectoryOutput( ... @@ -41,6 +42,8 @@ artoa.controller.track.trajectoryOutput.updateTableGeneratedTracks(); %% Update gui artoaGui.trajectoryOutput.checkboxUpdateTrackParameterWindow.Value = ... artoaWorkspace.trajectoryOutput.updateTrackParameterWindow; +artoaGui.trajectoryOutput.checkboxShowPositionDates.Value = ... + artoaWorkspace.trajectoryOutput.showPositionDates; %% Plot everything available artoa.controller.track.trajectoryOutput.plot(); diff --git a/lib/+artoa/+controller/+track/+trajectoryOutput/plot.m b/lib/+artoa/+controller/+track/+trajectoryOutput/plot.m index e29cb8d..ed95101 100644 --- a/lib/+artoa/+controller/+track/+trajectoryOutput/plot.m +++ b/lib/+artoa/+controller/+track/+trajectoryOutput/plot.m @@ -37,10 +37,10 @@ end for i = 1:length(trajectories) % initialize handle container artoaGui.trajectoryOutput.trajectoryHandles{i} = struct(); - if trajectories{i}.hidden - continue; - end - trajectoryColor = artoaWorkspace.soundsourceColors(mod(trajectories{i}.id, size(artoaWorkspace.soundsourceColors, 1)), :); + trajectoryColor = artoaWorkspace.soundsourceColors( ... + mod(trajectories{i}.id, size(artoaWorkspace.soundsourceColors, 1)) + 1, ... + : ... + ); % plot the trajectory itself [ ... artoaGui.trajectoryOutput.trajectoryHandles{i}.scatterPositions, ... @@ -61,7 +61,44 @@ for i = 1:length(trajectories) trajectories{i}, ... trajectoryColor ... ); + + + artoa.controller.setPlotHandleVisibility( ... + { ... + artoaGui.trajectoryOutput.trajectoryHandles{i}.scatterPositions, ... + artoaGui.trajectoryOutput.trajectoryHandles{i}.linePositions, ... + artoaGui.trajectoryOutput.trajectoryHandles{i}.textPositions, ... + artoaGui.trajectoryOutput.trajectoryHandles{i}.scatterReferencePoints, ... + artoaGui.trajectoryOutput.trajectoryHandles{i}.textReferencePoints ... + }, ... + ~trajectories{i}.hidden ... + ); + if ~trajectories{i}.hidden + artoa.controller.setPlotHandleVisibility( ... + { ... + artoaGui.trajectoryOutput.trajectoryHandles{i}.textPositions ... + }, ... + artoaWorkspace.trajectoryOutput.showPositionDates ... + ); + end +end + +%% Change trajectory plot to mercator if required +trajectoryAxes = artoaGui.trajectoryOutput.axesTrajectoryOutput; + +if ~artoaWorkspace.trajectoryOutput.enableMercatorProjection + trajectoryAxes.UserData = []; + trajectoryAxes.XLimMode = 'auto'; + trajectoryAxes.YLimMode = 'auto'; + return; end +trajectoryAxes = artoaGui.trajectoryOutput.axesTrajectoryOutput; +artoa.convert.axes2mercator( ... + trajectoryAxes, ... + false ... +); + + end diff --git a/lib/+artoa/+controller/+track/+trajectoryOutput/plotTrajectory.m b/lib/+artoa/+controller/+track/+trajectoryOutput/plotTrajectory.m index dab4895..2fa166c 100644 --- a/lib/+artoa/+controller/+track/+trajectoryOutput/plotTrajectory.m +++ b/lib/+artoa/+controller/+track/+trajectoryOutput/plotTrajectory.m @@ -32,7 +32,7 @@ for i = 1:15:length(t) textHandles{i} = text( ... pTrajectory.longitude(i), ... pTrajectory.latitude(i), ... - {' '; [num2str(t(i, 3)) '-' pad(num2str(t(i, 2)), 2, 'left', '0') '-' pad(num2str(t(i, 1)), 2, 'left', '0')]}, ... + {' '; [num2str(t(i, 3)) '-' pad(num2str(t(i, 2)), 2, 'left', '0') '-' pad(num2str(t(i, 1)), 2, 'left', '0') ', ' num2str(pTrajectory.date(i))]}, ... 'FontSize', 6, ... 'Color', pColor ... ); diff --git a/lib/+artoa/+controller/+track/+trajectoryOutput/tableGeneratedTracksSelect.m b/lib/+artoa/+controller/+track/+trajectoryOutput/tableGeneratedTracksSelect.m index 959afb2..02ee753 100644 --- a/lib/+artoa/+controller/+track/+trajectoryOutput/tableGeneratedTracksSelect.m +++ b/lib/+artoa/+controller/+track/+trajectoryOutput/tableGeneratedTracksSelect.m @@ -2,7 +2,7 @@ function [] = tableGeneratedTracksSelect(~, event) %TABLEGENERATEDTRACKSSELECT Summary of this function goes here % Detailed explanation goes here -global artoaWorkspace; +global artoaWorkspace artoaGui; %% Get data try @@ -24,8 +24,13 @@ if selectedColumn == 1 ~artoaWorkspace.trajectoryOutput.trajectories{selectedRow}.hidden; % Update table artoa.controller.track.trajectoryOutput.updateTableGeneratedTracks(); + % Hide trajectory + artoa.controller.setPlotHandleVisibility( ... + artoaGui.trajectoryOutput.trajectoryHandles{selectedRow}, ... + ~artoaWorkspace.trajectoryOutput.trajectories{selectedRow}.hidden ... + ); % Plot again - artoa.controller.track.trajectoryOutput.plot(); + %artoa.controller.track.trajectoryOutput.plot(); end %% Replace track parameter diff --git a/lib/+artoa/+controller/+track/run.m b/lib/+artoa/+controller/+track/run.m index 228595e..495ad66 100644 --- a/lib/+artoa/+controller/+track/run.m +++ b/lib/+artoa/+controller/+track/run.m @@ -19,13 +19,53 @@ if ~artoa.data.hasMember(artoaWorkspace, {'trajectoryOutput', 'trajectories'}) artoaWorkspace.trajectoryOutput.trajectories = {}; end +%% Prepare sound velocity +soundVelocity = NaN( ... + size(artoaWorkspace.trackParameter.soundsourceCombinations, 1), ... + 3 ... +); + +%% Get sound velocity +for i = 1:size(soundVelocity, 1) + if strcmpi( ... + artoaWorkspace.trackParameter.soundspeedMethodString, ... + 'manual' ... + ) ... + && strcmpi( ... + artoaWorkspace.trackParameter.trackingMethodString, ... + 'hyperbolic' ... + ) + soundVelocity(i, :) = [ ... + artoaWorkspace.trackParameter.soundsourceCombinations.soundspeed1(i), ... + artoaWorkspace.trackParameter.soundsourceCombinations.soundspeed2(i), ... + artoaWorkspace.trackParameter.soundsourceCombinations.soundspeed3(i) ... + ]; + elseif strcmpi( ... + artoaWorkspace.trackParameter.soundspeedMethodString, ... + 'manual' ... + ) + soundVelocity(i, :) = ... + artoaWorkspace.trackParameter.soundsourceCombinations.soundspeed1(i); + else + fnames = fieldnames(artoaWorkspace.filteredSoundsources); + soundVelocity(i, :) = artoa.data.calculateSoundVelocity( ... + artoaWorkspace.temperature(artoaWorkspace.statusTemperature == 1), ... + artoaWorkspace.pressure(artoaWorkspace.statusPressure == 1), ... + artoaWorkspace.trackParameter.soundspeedMethodString, ... + artoaWorkspace.filteredSoundsources.(fnames{1}) ... % WHICH SOUNDSOURCE TO USE IF "soundsource" IS SELECTED?? + ); + % TODO: IMPLEMENT OTHER SOUNDVELOCITY OPTIONS + end +end + %% Calculate and append track -[trajectory, trajectoryDates] = artoa.float.calculateTrajectory( ... +[trajectory, trajectoryDates, trajectoryClockError] = artoa.float.calculateTrajectory( ... artoaWorkspace.float, ... artoaWorkspace.toaData, ... artoaWorkspace.filteredSoundsources, ... artoaWorkspace.trackParameter, ... + soundVelocity, ... artoaDataInput.ini.leapseconds ... ); @@ -46,7 +86,12 @@ trajectoryObject.trackParameter = artoaWorkspace.trackParameter; trajectoryObject.latitude = trajectory(:, 1); trajectoryObject.longitude = trajectory(:, 2); trajectoryObject.date = trajectoryDates; -trajectoryObject.id = artoaWorkspace.trajectoryOutput.trajectories{end}.id + 1; +trajectoryObject.clockError = trajectoryClockError; +if isempty(artoaWorkspace.trajectoryOutput.trajectories) + trajectoryObject.id = 1; +else + trajectoryObject.id = artoaWorkspace.trajectoryOutput.trajectories{end}.id + 1; +end trajectoryObject.hidden = false; % store to workspace @@ -58,8 +103,5 @@ artoa.controller.track.trajectoryOutput.open(); %% Plot artoa.controller.track.trajectoryOutput.plot(); -%% Plot trajectory -%artoa.controller.track.trajectoryOutput.plotTrajectory(trajectory); - end diff --git a/lib/+artoa/+controller/setPlotHandleVisibility.m b/lib/+artoa/+controller/setPlotHandleVisibility.m new file mode 100644 index 0000000..f20f38f --- /dev/null +++ b/lib/+artoa/+controller/setPlotHandleVisibility.m @@ -0,0 +1,49 @@ +function [] = setPlotHandleVisibility(pHandles, pVisible) +%HIDEORSHOWHANDLES Summary of this function goes here +% Detailed explanation goes here + +%% Parameter check +if ~islogical(pVisible) + warning('Parameter pVisible needs to be logical. Skipping...'); + return; +end + +%% Check data structure +if iscell(pHandles) + processCell(pHandles); + return; +end + +if isstruct(pHandles) + processStruct(pHandles); + return; +end + +%% Check if handle is valid +if isempty(pHandles) || ~isvalid(pHandles) + return; +end + +%% Set visibility +if pVisible + pHandles.Visible = 'on'; +else + pHandles.Visible = 'off'; +end + + function processCell(pCell) + for i = 1:length(pCell) + artoa.controller.setPlotHandleVisibility(pCell{i}, pVisible); + end + end + + function processStruct(pHandles) + fnames = fieldnames(pHandles); + for oFnames = 1:length(fnames) + artoa.controller.setPlotHandleVisibility(pHandles.(fnames{oFnames}), pVisible); + end + end + + +end + diff --git a/lib/+artoa/+convert/+mercator/imerc.m b/lib/+artoa/+convert/+mercator/imerc.m new file mode 100644 index 0000000..44a5547 --- /dev/null +++ b/lib/+artoa/+convert/+mercator/imerc.m @@ -0,0 +1,19 @@ +function yo = imerc(y); + +%IMERC inverse mercator projection + +%----------- +%yo = 2 * atan(exp(y)) - pi/2; +%yo = yo/pi*180; +%----------- + +c1 = 0.19231594611859; +c2 = 0.3773144885e-3; + +yo = 2 * atan(exp(y)) - pi/2; +yo = yo/pi*180; + +yo = yo + sin(yo/90*pi)*c1 + sin(yo/45*pi)*c2; + + + diff --git a/lib/+artoa/+convert/+mercator/merc.m b/lib/+artoa/+convert/+mercator/merc.m new file mode 100644 index 0000000..bbb6848 --- /dev/null +++ b/lib/+artoa/+convert/+mercator/merc.m @@ -0,0 +1,15 @@ +function [yo] = merc(y) +% MERC transfer coordinates according Mercator projection. +% Yo = MERC(Y) returns the mercator-transformed of Y. + +a = 6378137.000; +b = 6356752.314; +e = sqrt(a^2-b^2)/a; + +rad = pi/180; + +s = e * sin(y*rad); + +yo = log(tan(pi./4 + (y*rad)/2) .* (((1-s)./(1+s)) .^ (e/2) )); + +yo = real(yo); diff --git a/lib/+artoa/+convert/axes2mercator.m b/lib/+artoa/+convert/axes2mercator.m new file mode 100644 index 0000000..27bcebc --- /dev/null +++ b/lib/+artoa/+convert/axes2mercator.m @@ -0,0 +1,63 @@ +function [] = axes2mercator(pAxesHandle, pInverse) +%AXES2MERCATOR Converts the given axes and all its children to mercator projection. +% Detailed explanation goes here + +%% Get required variables +children = pAxesHandle.Children; + +if nargin == 1 + pInverse = false; +end + +%% Check if already converted +if strcmp(pAxesHandle.UserData, 'mercator') + warning([mfilename ': Given axes has already been converted to mercator! Skipping...']); + return; +end + +%% Set units +pAxesHandle.Units = 'norm'; + +%% Convert every children +for i = 1:length(children) + currentChild = children(i); + % get position, depending on type + switch (currentChild.Type) + case 'image' + disp('Image discovered, skipping...'); + continue; + case 'text' + if pInverse + currentChild.Position(2) = artoa.convert.mercator.imerc( ... + currentChild.Position(2) ... + ); + else + currentChild.Position(2) = artoa.convert.mercator.merc( ... + currentChild.Position(2) ... + ); + end + otherwise + if pInverse + currentChild.YData = artoa.convert.mercator.imerc( ... + currentChild.YData ... + ); + else + currentChild.YData = artoa.convert.mercator.merc( ... + currentChild.YData ... + ); + end + end +end + +%% Set corrected limits +if pInverse + pAxesHandle.YLim = artoa.convert.mercator.imerc(pAxesHandle.YLim); +else + pAxesHandle.YLim = artoa.convert.mercator.merc([pAxesHandle.YLim(1) - 1, pAxesHandle.YLim(2) + 1]); +end + +%% Save state to axes +pAxesHandle.UserData = 'mercator'; + +end + diff --git a/lib/+artoa/+data/calculateEllipk4.m b/lib/+artoa/+data/calculateEllipk4.m new file mode 100644 index 0000000..3be6e72 --- /dev/null +++ b/lib/+artoa/+data/calculateEllipk4.m @@ -0,0 +1,32 @@ +function z = calculateEllipk4(st,sc) +% +% NO ANGLE! +% +%function [abstand,ang] = ellipk4(st,sc) +% +% routine computes great arc distance z in kilometers on a spheroid +% with flattening =1/295=.00339 using the andoyer-lambert approximation. +% z is the distance from sc to st and ang is the bearing from sc to +% st in true radians rel. to north. +% the coordinates for sc and st(lat,long) must be in radians. this and +% other details make this routine different from an earlier version +% ellips used at yale. +% 6377. is the equatorial radius of the earth minus the depth of the +% sound channel(=1200meters). +% 6378 is the equatorial radius of the earth, approximately. +% +% changed to matlab by c.schmid march 94 +% + +snst=sin(st(:,1)); +snsc=sin(sc(:,1)); +cfz = snst * snsc + cos(st(:,1)) * cos(sc(:,1)) * cos(st(:,2)-sc(:,2)); +z = 1 - cfz * cfz; +sfz=sqrt(z); +z=atan2(sfz,cfz); +a=snst-snsc; +b=snst+snsc; +delz = (z+3*sfz) / (1.-cfz) *a *a; +delz = .84752e-3 * (delz + (z-3.*sfz) / (1.+cfz) *b *b); +z=6378.0*(z-delz); +%the end diff --git a/lib/+artoa/+data/interpolateRafosData.m b/lib/+artoa/+data/interpolateRafosData.m index 04e4dd0..d95eee8 100644 --- a/lib/+artoa/+data/interpolateRafosData.m +++ b/lib/+artoa/+data/interpolateRafosData.m @@ -60,6 +60,10 @@ if (strcmpi(method, 'none')) % if the interpolation method was none just return return end +if (strcmpi(method, 'cubic')) + method = 'pchip'; +end + [x, indx] = sort(x); % sort the time axis, which seems to consist of at least 2 sebments y = y(indx); @@ -97,6 +101,9 @@ yint = interp1(x, y, xint, lower(char(method))); % interpolate the data xover = ismember(xint, int_index_array); % HHF, 17JUL03: Ahhhh! This is the problem! if isempty(isgap) + xint = toColumn(xint); + yint = toColumn(yint); + xover = toColumn(xover); return end @@ -107,8 +114,18 @@ for ix = 1:length(isgap) end % and now cut them out: jx = find(xint ~= (-1)); -xint = xint(jx); -yint = yint(jx); -xover = xover(jx); +xint = toColumn(xint(jx)); +yint = toColumn(yint(jx)); +xover = toColumn(xover(jx)); + + + function [columnVector] = toColumn(vector) + if ~iscolumn(vector) + columnVector = vector'; + else + columnVector = vector; + end + end + end diff --git a/lib/+artoa/+float/calculateCombinationSegment.m b/lib/+artoa/+float/calculateCombinationSegment.m new file mode 100644 index 0000000..b7206f8 --- /dev/null +++ b/lib/+artoa/+float/calculateCombinationSegment.m @@ -0,0 +1,184 @@ +function [segmentPositions, segmentDates, clockError] = calculateCombinationSegment(pCorrectedData, pCombinationDetails, pFloatReferenceTime, pSoundVelocity, pTrackingMethod) +%CALCULATECOMBINATIONSEGMENT Combination segment calculation. +% Using the input data, the function calculates the segment positions, +% its corresponding dates as well as the clock error (in case of hyperbolic calculation) +% +% Parameters: +% pCorrectedData (struct): +% Data structured by soundsource. The toa is already corrected +% (e.g. doppler correction etc.) +% pCombinationDetails (table): +% The combination table that is produced by the track parameter +% window. +% pFloatReferenceTime (double): +% Vector containing the float reference time, in the +% format [hour, minute] +% pSoundVelocity (double): +% Sound velocity next to the sound sources that are used. +% If hyperbolic is being used, it needs to be a vector containing +% at least as many entries as the count of sound sources being +% used. +% For all other methods, only one value needs to be set. +% Unit: [m/s] +% pTrackingMethod (string/char): +% Contains the tracking algorithm that should be used. +% Available methods: +% least square +% exclusive least square +% circular +% hyperbolic + +%% Initialize return variables +segmentPositions = []; +segmentDates = []; +clockError = []; + +%% Get sound sources +soundsourceNames = unique(strsplit(pCombinationDetails.soundsources{1}, ' ')); + +%% Get tracking method +trackingMethod = lower(pTrackingMethod); + +%% Get start and end point +segmentStart = pCombinationDetails{1, 1}; +segmentEnd = pCombinationDetails{1, 2}; + +%% Get reference position +referencePosition = cellfun(@str2double, strsplit(pCombinationDetails.referencePosition{1})); + +%% Filter toa data +intersectedToaDates = pCorrectedData.(soundsourceNames{1}).toaDate; +soundsourcePositions = [pCorrectedData.(soundsourceNames{1}).position]; +for i = 2:length(soundsourceNames) + [intersectedToaDates] = intersect(intersectedToaDates, pCorrectedData.(soundsourceNames{i}).toaDate); + soundsourcePositions = [soundsourcePositions; pCorrectedData.(soundsourceNames{i}).position]; + +end + +%% Remove all dates that are out of bounds +intersectedToaDates = intersectedToaDates( ... + intersectedToaDates >= segmentStart ... + & intersectedToaDates <= segmentEnd ... +); + +distanceToSoundsources = {}; +%segmentPositions = [pStartposition]; +segmentPositions = referencePosition; +for oDates = 1:length(intersectedToaDates) + currentDateValue = intersectedToaDates(oDates); + distanceToSoundsources{oDates} = []; + for i = 1:length(soundsourceNames) + currentName = soundsourceNames{i}; + selection = pCorrectedData.(currentName).toaDate == currentDateValue; + filteredValue = pCorrectedData.(currentName).toa(selection); + %filteredData.(currentName).toa(oDates) = filteredValue(1); + %filteredValue = pCorrectedData.(currentName).toaDate(selection); + %filteredData.(currentName).toaDate(oDates) = filteredValue(1); + +% relativeToa = ( ... +% filteredValue(1) ... +% + (pFloatReferenceTime(1) * 60 + pFloatReferenceTime(2)) * 60 ... +% - (pCorrectedData.(currentName).soundsourceReferenceTime(1) * 3600 + pCorrectedData.(currentName).soundsourceReferenceTime(2) * 60 - pCorrectedData.(currentName).leapseconds) ... +% ); + + windowStartTime = ... + pFloatReferenceTime(1) * 3600 ... + + pFloatReferenceTime(2) * 60 ... + - pCorrectedData.(currentName).soundsourceReferenceTime(1) * 3600 ... + - pCorrectedData.(currentName).soundsourceReferenceTime(2) * 60; + leapseconds = pCorrectedData.(currentName).leapseconds; + floatWindowStart = pCorrectedData.(currentName).floatWindowStart * 60; + + relativeToa = filteredValue(1) ... + + windowStartTime ... + + leapseconds ... + - floatWindowStart; + + % calculate distance to the source + distanceToSoundsources{oDates} = [ ... + distanceToSoundsources{oDates}; ... + relativeToa * (pSoundVelocity(i)/1000) + ]; +% distanceToSoundsources{oDates} = [ ... +% distanceToSoundsources{oDates}; ... +% (... +% filteredValue(1) ... %filteredData.(currentName).toa(oDates) ... +% + (pFloatReferenceTime(1) * 60 + pFloatReferenceTime(2)) * 60 ... +% - (pCorrectedData.(currentName).soundsourceReferenceTime(1) * 3600 + pCorrectedData.(currentName).soundsourceReferenceTime(2) * 60 - pCorrectedData.(currentName).leapseconds) ... +% ) * (pSoundVelocity/1000) +% ]; + + end +end + +if strcmp(trackingMethod, 'hyperbolic') && (length(soundsourceNames) < 3) + trackingMethod = 'circular'; +end + +switch trackingMethod + case {'least square', 'exclusive least square'} + for oDates = 1:length(intersectedToaDates) + if strcmp(pTrackingMethod, 'exclusive least square') ... + && (sum(~isnan(distanceToSoundsources{oDates})) ~= length(soundsourcePositions)) + segmentPositions = [segmentPositions; [NaN, NaN]]; + clockError = [clockError; NaN(1, length(soundsourceNames))]; + continue; + end + segmentPositions = [segmentPositions; ... + rad2deg(artoa.vendor.ls_converge(segmentPositions(end, :), length(soundsourceNames), soundsourcePositions, distanceToSoundsources{oDates}, pSoundVelocity(1)/1000)) ... + ]; + clockError = [clockError; NaN(1, length(soundsourceNames))]; + end + case 'circular' + for oDates = 1:length(intersectedToaDates) + distances = distanceToSoundsources{oDates}; + segmentPositions = [segmentPositions; ... + rad2deg(artoa.vendor.xnavai(deg2rad(soundsourcePositions), distances, deg2rad(referencePosition), 2)) ... + ]; + clockError = [clockError; NaN(1, length(soundsourceNames))]; + end + case 'hyperbolic' + for oDates = 1:length(intersectedToaDates) + distances = distanceToSoundsources{oDates}; + if any(isnan(distances)) + segmentPositions = [segmentPositions; [NaN NaN]]; + clockError = [clockError; NaN(1, length(soundsourceNames))]; + continue; + end + segmentPositions = [segmentPositions; ... + rad2deg(artoa.vendor.xnavai(deg2rad(soundsourcePositions), distances, deg2rad(referencePosition), 3)) ... + ]; + currentClockError = NaN(1, 3); + for oClockError = 1:3 + currentClockError(oClockError) = ... + ( ... + artoa.data.calculateEllipk4(segmentPositions(end, :), ... + deg2rad(soundsourcePositions(oClockError,:))) - distances(oClockError) ... + ) / (pSoundVelocity(oClockError)/1000); + end + clockError = [clockError; currentClockError]; + end + +% if length(unique(sosonr)) < 3; % check if 3 sources have been specified in track panel +% fprintf(1,'Hyperbolic tracking not possible: only 2 sound sources specified, using circular tracking\r'); +% pnt=xnavai(sosopos/RAD,dist,refpos/RAD,2); % use circular by default +% else +% if (isnan(dist(1)) | isnan(dist(2)) | isnan(dist(3))); % check if data for all 3 sources is availble +% tpnt=[tpnt;[NaN,NaN]]; +% else +% pnt=xnavai(sosopos/RAD,dist,refpos/RAD,3); +% clkerror(1) = (artoa.data.calculateEllipk4(pnt,sosopos(1,:)/RAD)-dist(1))/svsoso(1); +% clkerror(2) = (artoa.data.calculateEllipk4(pnt,sosopos(2,:)/RAD)-dist(2))/svsoso(2); +% clkerror(3) = (artoa.data.calculateEllipk4(pnt,sosopos(3,:)/RAD)-dist(3))/svsoso(3); +% end +% end + +end + +%% Store toa dates +segmentDates = intersectedToaDates; + +%% Remove reference position from trajectory +segmentPositions = segmentPositions(2:end, :); + +end diff --git a/lib/+artoa/+float/calculateTrajectory.m b/lib/+artoa/+float/calculateTrajectory.m index c92b94e..ed78297 100644 --- a/lib/+artoa/+float/calculateTrajectory.m +++ b/lib/+artoa/+float/calculateTrajectory.m @@ -1,4 +1,4 @@ -function [trajectory, trajectoryDates] = calculateTrajectory(pFloatDetails, pToaData, pSoundsources, pTrackingParameter, pLeapsecondsMatrix) +function [trajectory, trajectoryDates, trajectoryClockError] = calculateTrajectory(pFloatDetails, pToaData, pSoundsources, pTrackingParameter, pSoundVelocity, pLeapsecondsMatrix) %CALCULATETRAJECTORY Summary of this function goes here % Detailed explanation goes here @@ -87,7 +87,6 @@ for i = 1:length(involvedSoundsources) end % subtract offset (corrective value) of sound source - tmpFnames = fieldnames(pSoundsources); soundsourceOffsetCorrectiveValue = ... pTrackingParameter.soundsourceOffsets.(currentSoundsource.sourcename); currentToa = currentToa - soundsourceOffsetCorrectiveValue(1); @@ -104,26 +103,34 @@ for i = 1:length(involvedSoundsources) pTrackingParameter.gapSize, ... lower(pTrackingParameter.interpolationMethodString) ... ); - preparedData.(currentSoundsource.sourcename).toaIndex = preparedData.(currentSoundsource.sourcename).toaIndex'; + %preparedData.(currentSoundsource.sourcename).toaIndex = preparedData.(currentSoundsource.sourcename).toaIndex'; % combine interpolated and original data and sort them - preparedData.(currentSoundsource.sourcename).toaDate = [preparedData.(currentSoundsource.sourcename).toaDate; currentToaDate]; - preparedData.(currentSoundsource.sourcename).toa = [preparedData.(currentSoundsource.sourcename).toa; currentToa]; + preparedData.(currentSoundsource.sourcename).toaDate = ... + [preparedData.(currentSoundsource.sourcename).toaDate; currentToaDate]; + preparedData.(currentSoundsource.sourcename).toa = ... + [preparedData.(currentSoundsource.sourcename).toa; currentToa]; % Find the nearest neighbor rafos date for oRafosDate = 1:length(preparedData.(currentSoundsource.sourcename).toaDate) - neighbor = artoa.data.findNearestNeighbor(uniqueToaDates, preparedData.(currentSoundsource.sourcename).toaDate(oRafosDate)); - preparedData.(currentSoundsource.sourcename).toaDate(oRafosDate) = neighbor; - clear neighbor; + preparedData.(currentSoundsource.sourcename).toaDate(oRafosDate) = ... + artoa.data.findNearestNeighbor( ... + uniqueToaDates, ... + preparedData.(currentSoundsource.sourcename).toaDate(oRafosDate) ... + ); end % sort toa by date - [preparedData.(currentSoundsource.sourcename).toaDate, sortIndices] = sort(preparedData.(currentSoundsource.sourcename).toaDate); - preparedData.(currentSoundsource.sourcename).toa = preparedData.(currentSoundsource.sourcename).toa(sortIndices); + [preparedData.(currentSoundsource.sourcename).toaDate, sortIndices] = ... + sort(preparedData.(currentSoundsource.sourcename).toaDate); + preparedData.(currentSoundsource.sourcename).toa = ... + preparedData.(currentSoundsource.sourcename).toa(sortIndices); % shift toa data - [preparedData.(currentSoundsource.sourcename).toaDate, preparedData.(currentSoundsource.sourcename).toa] = ... - artoa.data.simulshift( ... + [ ... + preparedData.(currentSoundsource.sourcename).toaDate, ... + preparedData.(currentSoundsource.sourcename).toa ... + ] = artoa.data.simulshift( ... preparedData.(currentSoundsource.sourcename).toaDate, ... preparedData.(currentSoundsource.sourcename).toa, ... pFloatDetails.schedule, ... @@ -136,12 +143,9 @@ for i = 1:length(involvedSoundsources) preparedData.(currentSoundsource.sourcename).position = currentSoundsource.position; % save float window start preparedData.(currentSoundsource.sourcename).floatWindowStart = pFloatDetails.windowstart; - - % save soundsource reference time preparedData.(currentSoundsource.sourcename).soundsourceReferenceTime = currentSoundsource.reftime; - % get leapseconds for soundsource preparedData.(currentSoundsource.sourcename).leapseconds = artoa.data.calculateLeapseconds( ... artoa.convert.dmy2rd(pFloatDetails.launchtime(3), pFloatDetails.launchtime(2), pFloatDetails.launchtime(1)), ... @@ -159,59 +163,45 @@ for i = 1:length(involvedSoundsources) % apply doppler correction if selected [preparedData.(currentSoundsource.sourcename).toaDate, preparedData.(currentSoundsource.sourcename).toa] = ... artoa.data.doppcorr( ... - preparedData.(currentSoundsource.sourcename).toaDate, preparedData.(currentSoundsource.sourcename).toa, pFloatDetails.schedule ... + preparedData.(currentSoundsource.sourcename).toaDate, ... + preparedData.(currentSoundsource.sourcename).toa, ... + pFloatDetails.schedule ... ); - - %hms = artoa.convert.rd2hms(preparedData.(currentSoundsource.sourcename).toaDate); - %preparedData.(currentSoundsource.sourcename).toa = preparedData.(currentSoundsource.sourcename).toa - 60*hms(2) - hms(3); - %preparedData.(currentSoundsource.sourcename).toa = preparedData.(currentSoundsource.sourcename).toa + pFloatDetails.windowstart * 60; - %preparedData.(currentSoundsource.sourcename).toa = preparedData.(currentSoundsource.sourcename).toa - 3600; end %% Get static data floatReferenceTime = pFloatDetails.phasereftime; -soundVelocity = soundsourceCombinations(:, 5); %startPosition = [pSatData.lat_sat(1) pSatData.lon_sat(1)]; -trackingMethod = lower(pTrackingParameter.trackingMethodString); +%trackingMethod = lower(pTrackingParameter.trackingMethodString); trajectory = []; +trajectoryDates = []; +trajectoryClockError = []; -switch (trackingMethod) - case 'least square' - [trajectory, trajectoryDates] = artoa.float.calculateTrajectoryLeastSquares( ... +%% Calculate for every combination +for oCombination = 1:size(soundsourceCombinations, 1) + [segmentPositions, segmentDates, segmentClockError] = artoa.float.calculateCombinationSegment( ... preparedData, ... - soundsourceCombinations, ...startPosition, ... + soundsourceCombinations(oCombination, :), ...%trajectory(end, :), ... floatReferenceTime, ... - soundVelocity ... - ); - case 'exclusive least square' - trajectory = artoa.float.calculateTrajectoryExclusiveLeastSquares( ... - ); -end - -return - -%% Calculate positions for every combination segment -trajectory = startPosition; - -for oCombination = 1:size(soundsourceCombinations, 1) + pSoundVelocity(oCombination, :), ... + pTrackingParameter.trackingMethodString ... + ); + if islogical(segmentPositions) && segmentPositions == false + trajectory = false; + trajectoryDates = false; + trajectoryClockError = false; + return + end trajectory = [ ... trajectory; ... - artoa.data.calculateCombinationSegmentLeastSquares( ... - preparedData, ... - soundsourceCombinations(oCombination, :), ... - trajectory(end, :), ... - floatReferenceTime, ... - soundVelocity(oCombination) ... - ) ... + segmentPositions ... ]; + trajectoryDates = [trajectoryDates; segmentDates]; + if size(segmentClockError, 2) == 2 && size(trajectoryClockError, 2) == 3 + segmentClockError(:, 3) = NaN(size(segmentClockError, 1), 1); + end + trajectoryClockError = [trajectoryClockError; segmentClockError]; end -% x_new=ls_converge(refpos,length(sosopos),sosopos,dist,svsoso((jx))); -% if (sum(~isnan(dist)) ~= length(sosopos)) x_new=nan*size(x_new);end -% pnt=x_new(1,1:2); -% %disp([num2str(ix),' ',num2str(mx),num2str(pnt(1)*RAD),' ',num2str(pnt(2)*RAD)]); -% clkerror = NaN; - - return \ No newline at end of file diff --git a/lib/+artoa/+gui/+track/trajectoryOutput.m b/lib/+artoa/+gui/+track/trajectoryOutput.m index b74ac84..2571e52 100644 --- a/lib/+artoa/+gui/+track/trajectoryOutput.m +++ b/lib/+artoa/+gui/+track/trajectoryOutput.m @@ -17,7 +17,7 @@ availableCallbacks = { ... 'buttonTrackInfos', ... 'buttonVelocities', ... 'tableGeneratedTracksSelect', ... - 'buttonGenerateNewTrajectoryId' ... + 'checkboxShowPositionDates' ... }; @@ -47,6 +47,8 @@ set( ... %% Setup axes artoaGui.figures.trajectoryOutput.CurrentAxes = axes(); artoaGui.figures.trajectoryOutput.CurrentAxes.Position = [.1 .1 .55 .85]; +artoaGui.figures.trajectoryOutput.CurrentAxes.Color = [.2 .2 .2]; +artoaGui.figures.trajectoryOutput.CurrentAxes.GridColor = [.5 .5 .5]; titleVal = [ ... 'Project: ' artoaWorkspace.float.projectname ... ' - Float ID: ' num2str(artoaWorkspace.float.floatname) ... @@ -77,7 +79,7 @@ artoaGui.trajectoryOutput.frameTrackList = uipanel( ... columns = { ... '', ... 'No.', ... - 'Soundspeed Method' ... + 'Tracking Method' ... }; artoaGui.trajectoryOutput.tableTrackList = uitable( ... artoaGui.trajectoryOutput.frameTrackList, ... @@ -163,14 +165,15 @@ artoaGui.trajectoryOutput.checkboxMercator = uicontrol( ... 'CallBack', pCallbacks.checkboxMercator ... ); -artoaGui.trajectoryOutput.buttonGenerateNewTrajectoryId = uicontrol( ... + +artoaGui.trajectoryOutput.checkboxShowPositionDates = uicontrol( ... 'Parent', artoaGui.trajectoryOutput.frameDisplayOptions, ... - 'String', 'Generate new trajectory id', ... - 'Style', 'PushButton', ... + 'String', 'Show date positions', ... + 'Style', 'CheckBox', ... 'FontSize', 8, ... 'Units', 'normalized', ... 'Position', [.1 .6 .8 .175], ... - 'CallBack', pCallbacks.buttonGenerateNewTrajectoryId ... + 'CallBack', pCallbacks.checkboxShowPositionDates ... ); artoaGui.trajectoryOutput.buttonTrackInfos = uicontrol( ... diff --git a/lib/+artoa/+vendor/xnavai.m b/lib/+artoa/+vendor/xnavai.m new file mode 100644 index 0000000..efdd062 --- /dev/null +++ b/lib/+artoa/+vendor/xnavai.m @@ -0,0 +1,180 @@ +% XNAVAI locate a point on a spheroid using circular or hyperbolic navigation +% +% SYNOPSIS: +% function [pnt]=xnavai(sosopos,dist,refpoint,mindex) +% +% DESCRIPTION: +% XNAVAI locates a point from a spheroid using circular or hyperbolic +% navigation. +% +% ARGUMETNS: +% INPUT: sosopos matrix of the size 2 by 2 which contains the +% positions (latitude, longitude) of the sound +% sources used for this point in radiants. +% The matrix should look like: +% [soso_lat1, soso_long1; +% soso_lat2, soso_long2] +% dist vector which contains the distance of each +% sound source in km used for this point. +% refpoint vector with length 2, which contains the +% position of the refpoint in radiants. +% The vector should look like: +% [ref_lat, ref_lon] +% mindex =2 --> circular navigation +% =3 --> hyperbolic navigation +% +% OUTPUT: pnt vector with length 2, which contains the +% calculated floatposition in radiants.# +% The vector will look like: +% [pnt_lat, pnt_lon] +% +% Maximum number of iteration steps set to 20 + +function [pnt]=xnavai(sosopos,dist,refpoint,mindex) + +a(1:3)=999; +dt1=999; +dt2=999; +dt3=999; +dt4=999; +dt5=999; +dt6=999; + +TOL=0.1; +INCR=0.000156788; +ERAD=6377; + +imax=20; +icnt=0; +stop=0; +pnt=[NaN, NaN]; + +if mindex==2 % the circular case + if ~(isnan(dist(1)) || isnan(dist(2))) % if neither dist(1) nor dist(2) = NaN + d1=dist(1); + d2=dist(2); + sosoposition(1,:) = sosopos(1,:); + sosoposition(2,:) = sosopos(2,:); + elseif ~(isnan(dist(1)) || isnan(dist(3))) % if neither dist(1) nor dist(3) = NaN + d1=dist(1); + d2=dist(3); + sosoposition(1,:) = sosopos(1,:); + sosoposition(2,:) = sosopos(3,:); + elseif ~(isnan(dist(2)) || isnan(dist(3))) % if neither dist(1) nor dist(3) = NaN + d1=dist(2); + d2=dist(3); + sosoposition(1,:) = sosopos(2,:); + sosoposition(2,:) = sosopos(3,:); + end +elseif mindex==3 % the hyperbolic case + d1=dist(2)-dist(1); + d2=dist(3)-dist(1); + sosoposition(1,:) = sosopos(1,:); + sosoposition(2,:) = sosopos(2,:); + sosoposition(3,:) = sosopos(3,:); +end + +istop=0; + +while ((icnt < imax) && ~stop) + + istop=istop+1; + + if istop > 20 + pnt=[NaN, NaN]; + stop=1; + end + + ps=refpoint; + + a(1)=artoa.data.calculateEllipk4(ps,sosoposition(1,:)); + a(2)=artoa.data.calculateEllipk4(ps,sosoposition(2,:)); + if mindex==3 + a(3)=artoa.data.calculateEllipk4(ps,sosoposition(3,:)); + end + + + if ((a(1)<0) || (a(2)<0) || (a(3)<0)) + stop=1; + else + + if mindex==2 + cr1=d1-a(1); + cr2=d2-a(2); + elseif mindex==3 + cr1=d1-a(2)+a(1); + cr2=d2-a(3)+a(1); + end + % check if the error is small enough + err=abs(cr1)+abs(cr2); + + if (err <= TOL) + pnt=refpoint; + stop=1; + else + % if not, go on incrementing position + ps(1)=refpoint(1); + ps(2)=refpoint(2)+INCR; + dt1=artoa.data.calculateEllipk4(ps,sosoposition(1,:)); + dt2=artoa.data.calculateEllipk4(ps,sosoposition(2,:)); + if mindex==3 + dt3=artoa.data.calculateEllipk4(ps,sosoposition(3,:)); + end + + + if ((dt1<0) || (dt2<0) || (dt3<0)) + stop=1; + else + + ps(1)=refpoint(1)+INCR; + ps(2)=refpoint(2); + dt4=artoa.data.calculateEllipk4(ps,sosoposition(1,:)); + dt5=artoa.data.calculateEllipk4(ps,sosoposition(2,:)); + if mindex==3 + dt6=artoa.data.calculateEllipk4(ps,sosoposition(3,:)); + end + + if ((dt4<0) || (dt5<0) || (dt6<0)) + stop=1; + else + if mindex==2 + b1=dt1-a(1); + b2=dt2-a(2); + b3=dt4-a(1); + b4=dt5-a(2); + elseif mindex==3 + b1=(dt2-dt1)-(a(2)-a(1)); + b2=(dt3-dt1)-(a(3)-a(1)); + b3=(dt5-dt4)-(a(2)-a(1)); + b4=(dt6-dt4)-(a(3)-a(1)); + end + + dnom=(b1*b4-b2*b3)*ERAD; + + c(2)=(cr1*b4-b3*cr2)/dnom; + c(1)=(cr2*b1-b2*cr1)/dnom; + + im=max(abs(c))==abs(c); + cc=c(im); + + if mindex==2 + im=min(abs(a(1:2)))==abs(a(1:2)); + aa=a(im); + elseif mindex==3 + im=min(abs(a(1:3)))==abs(a(1:3)); + aa=a(im); + end + + cp=2*cc*ERAD/aa; + + if cp < 1 + cp=1; + end + + refpoint=refpoint+c/cp; + + end + end + end + end +end -- GitLab