summaryrefslogtreecommitdiff
path: root/inst/str2angle.m
blob: 7b51d0b1b886c237cc037378b2593dafd1a4f558 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
## Copyright (C) 2020 Philip Nienhuis
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <https://www.gnu.org/licenses/>.

## -*- texinfo -*-
## @deftypefn {} {@var{deg} =} str2angle (@var{txt})
## @deftypefnx {} {@var{deg} =} str2angle (@var{txt}, @var{verbose})
## Convert string type angular coordinates into numerical decimal degrees.
##
## @var{txt} can be a txt string or cellstr array containing one or more
## strings each representing an angular coordinate (latitude and/or
## longitude).  str2angle is quite picky as regards input formats, see below.
##
## If optional input argument @var{verbose} is set to 1 or true,
## str2angle will warn if the input cntains strings that couldn't
## be converted.  The default value is false (no warnings).
##
## Output argument @var{deg} contains an 1xN array of numerical degree
## value(s.  Unrecognizable input strings are either ignored or, if looking
## almost recognizable, set to NaN in the output.
##
## The angular strings should look like:
## @itemize
## @item
## an integer number comprising one to three leading digits (degrees), maybe
## preceded by a plus or minus character;
##
## @item
## one of a 'degree' character or even a 'D', 'E', 'W', 'N' or 'S'
## (capitalization ignored) optionally followed by a space.  In case of a
## 'W', 'w', 'S' or 's' character (western or southern hemisphere, resp.)
## the corresponding output value will be negated;
##
## @item
## a positive integer number comprising exactly two digits (minutes)
## immediately followed by either an apostroph (') or 'M' or 'm' character,
## optionally followed by a space;
##
## @item
## a positive integer or real number (seconds), immediately followed by
## either a '"' character (double quote), or an 's' or 'S' character, or
## two consecutive single quote characters ('');
##
## @item
## optionally, a character 'E', 'N', 'S' or 'W' indicating the hemisphere.
## @end itemize
##
## So-called packed DMS and degrees/minutes/seconds separated by hyphens or
## minus signs are not accepted.
##
## So, angular degree strings may look like e.g.: @*
## @verbatim
## 191E21'3.1",      12e 22'33.24",    13E 23' 33.344",
## 14w24' 33.4444",  15S25'33.544",    -16W26'33.644444'',
## 17s27'33.74444",  18N28'33.844",    +19d29m33.944444s,
## 20D20M33.0444Se,  21°51'4.1",       22°52'44.25",
## 23° 53'33.34",    24°54' 33.44N",    25° 55' 33.544",
## 26°56'33.644''S,   27°57' 33.744'',  28°58'33844"w.
## @end verbatim
##
## Note: the regular expression used to parse the input strings can be
## fooled.  In particularly bad cases it may loose track completely and
## give up, angle2str returns an empty scalar Inf value then to distinguish
## from partly convertible inputs.
##
## @seealso{angl2str}
## @end deftypefn

## Author: Philip Nienhuis <prnienhuis at users.sf.net>
## Created: 2020-05-18

function deg = str2angle (txt, verbose = 0)

  fmt = [ '([-+]?[0123456789]{1,3})([^+-]\s?)([+-]?[0123456789]{2})' ...
          '[''mM]\s?([+-]?[0123456789\.].*?)((?:["sS]|'''')[eEnNsSwW]?)' ];

  if (iscellstr (txt))
    txt = strjoin (txt, "   ");
  elseif (! ischar (txt))
    error ("str2angle: char string or cellstr expected, but got %s", ...
          class (txt));
  endif
  try
    rs = reshape (cell2mat (regexp (txt, fmt, "tokens")), 5, []);
    deg = [zeros(1, size (rs, 2)); str2double(rs([1 3 4], :))];
    ierr = find (deg(3, :) >= 60.0 | deg(3, :) < 0.0 | strcmp (rs(2, :), '-'));
    ierr = [ ierr (find (abs (deg(1, :)) > 360.0)) ];
    ierr = unique ([ierr (find (deg(4, :) >= 60.0 | deg(4, :) < 0.0))]);
    if (! isempty (ierr))
      if (verbose)
        warning ("angstr:inconv", "str2angle: inconvertible values set to NaN");
      endif
      deg(1, ierr) = deg(3, ierr) = deg(4, ierr) = 0;
    endif
    deg(1, :) = (abs (deg(2, :)) .+ (deg(3, :) .+ deg(4, :) / 60) / 60) .* sign (deg(2, :));
    deg(2:end, :) = [];
    deg(1, ierr) = NaN;
    ## Set coordinates in western or southern hemisphere to negative.
    ## 1. Check hemisphere indicators directly following degrees
    ineg = find (! cellfun (@isempty, regexpi (rs(2, :), '[sw]')));
    ## For trailing hemisphere indicaors, first remove seconds indicators
    rs(5, :) = regexprep (rs(5, :), '[''"Ss]', '');
    ## 2. Check for S or W hemisphere
    ineg = [ ineg (find (! cellfun (@isempty, regexpi (rs(5, :), '[sw]')))) ];
    deg(1, ineg) = -deg(1, ineg);
  catch
    if (verbose)
      warning ("angstr:inconv", "str2angle: inconvertible input");
    endif
    deg = Inf;
  end_try_catch

endfunction


%!test
%!shared tst, res
%! tst = '191E21''3.1"\n12e 22''33.24"\n13E 23'' 33.344"\n14w24'' 33.4444"\n';
%! tst = [tst '15S25''33.54444"\n16W26''33.644444''''\n17s27''33.7444444"\n'];
%! tst = [tst '18N28''33.84444444"\n19d29m33.944444444s\n20D20M33.04444444Se\n'];
%! tst = [tst '21°51''4.1"\n22°52''44.25"\n23° 53''33.34"\n24°54'' 33.44"N\n'];
%! tst = [tst '25° 55'' 33.544"\n26°56''33.644''''S\n27°57'' 33.744''''\n'];
%! tst = [tst '28°58''33.844"w'];
%! tst = strrep (tst, '\n', char(10));
%! res = [191.351, 12.376, 13.393, -14.409, -15.426, -16.443, -17.459, 18.476, ...
%!        19.493, 20.343, 21.851, 22.879, 23.893, 24.909, 25.926, 26.943, ...
%!        27.959, -28.976];
%! assert (str2angle (tst), res, 1e-3);

%!test
%! tstc = strsplit (tst, "\n");
%! assert (str2angle (tstc), res, 1e-3);

%!test
%! tstc = strjoin (strsplit (tst, "\n"), "   ");
%! assert (str2angle (tstc), res, 1e-3);

%!test
%! assert (str2angle ('24E77''33"  25W43''57.7"'), [NaN, -25.7333], 1e-3);

%!test
%! assert (str2angle ('; aggag'), Inf);

%!warning <inconvertible> str2angle ('24E77''33"', 1);
%!warning <inconvertible> str2angle (' -4D-32''-44.57"', 1);
%!error <char string or cellstr expected> str2angle (25);