author | A.M. Thurnherr <athurnherr@yahoo.com> |
Sat, 24 Jul 2021 09:38:16 -0400 | |
changeset 46 | 70e566505a12 |
parent 42 | 22f5d5d35236 |
permissions | -rw-r--r-- |
0 | 1 |
#====================================================================== |
36 | 2 |
# L I B V E C . P L |
0 | 3 |
# doc: Sat Mar 20 12:50:32 1999 |
42 | 4 |
# dlm: Mon Mar 1 08:46:35 2021 |
0 | 5 |
# (c) 1999 A.M. Thurnherr |
42 | 6 |
# uE-Info: 45 70 NIL 0 0 70 2 2 4 NIL ofnI |
0 | 7 |
#====================================================================== |
8 |
||
9 |
# HISTORY: |
|
10 |
# Mar 20, 1999: - created for ANTS_2.1 (no more c-code) |
|
11 |
# May 27, 1999: - added polar/cartesian conversions |
|
12 |
# Sep 18, 1999: - argument typechecking |
|
13 |
# Dec 10, 1999: - vel_u(), vel_v(), vel_dir(), vel_mag() |
|
14 |
# Mar 07, 2000: - proj(), deg(), rad() |
|
15 |
# Apr 18, 2002: - area() |
|
16 |
# Jan 6, 2003: - changed dist() output to meters |
|
17 |
# Jan 16, 2003: - renamed vel_vel() to vel_speed() |
|
18 |
# Sep 3, 2003: - dir_bias() |
|
19 |
# May 13, 2004: - BUG: had fogotten to adapt area() to new dist() |
|
20 |
# May 21, 2004: - forced zero distance on &dist() if lat/lon does |
|
21 |
# not change (avoid roundoff error) |
|
22 |
# Jun 22, 2004: - added GMTdeg(), dir() |
|
23 |
# Nov 11, 2004: - BUG: roundoff test in dist() was done before |
|
24 |
# conversion to numbers |
|
25 |
# Jul 1, 2006: - Version 3.3 [HISTORY] |
|
26 |
# Jul 24, 2006: - modified to use equal() |
|
27 |
# Nov 16, 2006: - added degmin() |
|
28 |
# Dec 19, 2007: - addapted vel_speed(), vel_dir() to new &antsFunUsage() |
|
29 |
# - same routines now return nan on nan input |
|
30 |
# Jan 15, 2007: - BUG: vel_dir() was broken |
|
31 |
# Jun 14, 2009: - added p_vel() |
|
32 |
# Nov 5, 2009: - added angle(); vel_bias() => angle_diff() |
|
33 |
# Apr 22, 2010: - added angle_ts() |
|
2 | 34 |
# Jun 5, 2012: - added &closestPointOnStraightLine() |
3 | 35 |
# Jun 11, 2012: - addeed $t output to &closestPointOnStraightLine() |
5 | 36 |
# Nov 27, 2013: - added &angle_pos(), mag_heading() |
6 | 37 |
# Mar 3, 2014: - made deg(), rad() handle nans |
38 |
# Mar 4, 2014: - made some angle funs handle nans |
|
29
f41d125405a6
version after ECOGIG EN586 cruise
A.M. Thurnherr <athurnherr@yahoo.com>
parents:
11
diff
changeset
|
39 |
# Jul 29, 2016: - BUG: mag_heading was inconsistent with ADCP headings |
f41d125405a6
version after ECOGIG EN586 cruise
A.M. Thurnherr <athurnherr@yahoo.com>
parents:
11
diff
changeset
|
40 |
# (previous version only used for 2014 CLIVAR P06 |
f41d125405a6
version after ECOGIG EN586 cruise
A.M. Thurnherr <athurnherr@yahoo.com>
parents:
11
diff
changeset
|
41 |
# processing with IMP data with confused coord |
f41d125405a6
version after ECOGIG EN586 cruise
A.M. Thurnherr <athurnherr@yahoo.com>
parents:
11
diff
changeset
|
42 |
# system) |
30 | 43 |
# Aug 7, 2016: - made vel_u and vel_v deal with nans |
32 | 44 |
# Nov 15, 2017: - re-enabled usage-message (of sorts) for vel_u only |
42 | 45 |
# Mar 1, 2021: - adapted rotation_ts and angle_ts to deal with nans |
0 | 46 |
|
47 |
require "$ANTS/libPOSIX.pl"; # acos() |
|
48 |
||
49 |
#---------------------------------------------------------------------- |
|
50 |
# &rad() calc radians |
|
51 |
# °() calc degrees |
|
52 |
#---------------------------------------------------------------------- |
|
53 |
||
54 |
$PI = 3.14159265358979; |
|
55 |
||
56 |
sub rad(@) |
|
57 |
{ |
|
6 | 58 |
my($d) = &antsFunUsage(1,".","<deg>",@_); |
59 |
return numberp($d) ? $d/180 * $PI : nan; |
|
0 | 60 |
} |
61 |
||
62 |
sub deg(@) |
|
63 |
{ |
|
6 | 64 |
my($r) = &antsFunUsage(1,".","<rad>",@_); |
65 |
return numberp($r) ? $r/$PI * 180 : nan; |
|
0 | 66 |
} |
67 |
||
68 |
||
69 |
#---------------------------------------------------------------------- |
|
70 |
# &proj(from_x,from_y,onto_unit_x,onto_unit_y) |
|
71 |
# project vector onto another |
|
72 |
#---------------------------------------------------------------------- |
|
73 |
||
74 |
# to transform CM velocity components u/v to along/across mean l/c: |
|
75 |
# - mean dir d = &vel_dir(<u>,<v>); with <.> indicating ensemble avg |
|
76 |
# - l = proj(u,v,sin(rad(d)),cos(rad(d))); NEW: l = p_vel(d[,u,v]) |
|
77 |
# - c = -proj(u,v,-cos(rad(d)),sin(rad(d))); |
|
78 |
||
79 |
sub proj(@) |
|
80 |
{ |
|
81 |
my($fx,$fy,$oux,$ouy) = |
|
82 |
&antsFunUsage(4,"ffff","<from_x> <from_y> " . |
|
83 |
"<onto_unit_x> <onto_unit_y>",@_); |
|
84 |
return $fx*$oux + $fy*$ouy; |
|
85 |
} |
|
86 |
||
87 |
{ my(@fc); |
|
88 |
sub p_vel(@) |
|
89 |
{ |
|
90 |
my($u,$v,$d) = &antsFunUsage(3,'..f','[u, v,] dir',\@fc,'u','v',undef,@_); |
|
91 |
return nan unless numbersp($d,$u,$v); |
|
92 |
return proj($u,$v,sin(rad($d)),cos(rad($d))); |
|
93 |
} |
|
94 |
} |
|
95 |
||
96 |
||
97 |
#---------------------------------------------------------------------- |
|
98 |
# &polar_r(x,y),&vel_vel(u,v) calc polar radius, velocity |
|
99 |
# &polar_phi(x,y),&vel_dir(u,v) calc polar degrees cclockwise from |
|
100 |
# horiz (phi) OR clockwise from N (dir) |
|
101 |
# &cartesian_x(r,phi),&vel_u(m,dir) calc x and u from polar coords |
|
102 |
# &cartesian_y(r,phi),&vel_v(m,dir) calc y and v from polar coords |
|
103 |
#---------------------------------------------------------------------- |
|
104 |
||
105 |
sub polar_r(@) |
|
106 |
{ |
|
107 |
my($x,$y) = &antsFunUsage(2,"ff","<x> <y>",@_); |
|
108 |
return sqrt($x*$x+$y*$y); |
|
109 |
} |
|
110 |
||
111 |
{ my(@fc); |
|
112 |
sub vel_speed(@) |
|
113 |
{ |
|
114 |
my($u,$v) = &antsFunUsage(2,'..','[u, v]',\@fc,'u','v',@_); # . allows for nans |
|
115 |
return nan unless numbersp($u,$v); |
|
116 |
return sqrt($u*$u+$v*$v); |
|
117 |
} |
|
118 |
} |
|
119 |
||
120 |
sub polar_phi(@) |
|
121 |
{ |
|
122 |
my($x,$y) = &antsFunUsage(2,"ff","<x> <y>",@_); |
|
123 |
return 180 / $PI * atan2($y,$x); |
|
124 |
} |
|
125 |
||
126 |
||
127 |
{ my(@fc); |
|
128 |
sub vel_dir(@) |
|
129 |
{ |
|
130 |
my($u,$v) = &antsFunUsage(2,'..','[u, v]',\@fc,'u','v',@_); # . allows for nans |
|
131 |
return nan unless numbersp($u,$v); |
|
132 |
my($dir) = 180 / $PI * atan2($u,$v); |
|
133 |
return ($dir >= 0) ? $dir : $dir+360; |
|
134 |
} |
|
135 |
} |
|
136 |
||
137 |
sub cartesian_x(@) |
|
138 |
{ |
|
30 | 139 |
my($r,$phi) = &antsFunUsage(2,"..","<r> <phi>",@_); |
140 |
return nan unless numbersp($r,$phi); |
|
0 | 141 |
return $r * cos($PI*$phi/180); |
142 |
} |
|
143 |
||
32 | 144 |
sub vel_u(@) { |
145 |
if (@_) { return &cartesian_x($_[0],90-$_[1]); } |
|
146 |
else { return &cartesian_x(); } |
|
147 |
} |
|
0 | 148 |
|
149 |
sub cartesian_y(@) |
|
150 |
{ |
|
30 | 151 |
my($r,$phi) = &antsFunUsage(2,"..","<r> <phi>",@_); |
152 |
return nan unless numbersp($r,$phi); |
|
0 | 153 |
return $r * sin($PI*$phi/180); |
154 |
} |
|
155 |
||
156 |
sub vel_v(@) { return &cartesian_y($_[0],90-$_[1]); } |
|
157 |
||
158 |
#---------------------------------------------------------------------- |
|
11
56799f01321a
bug fix in libvec.pl; version given to Kanae Komaki, July 16, 2014
A.M. Thurnherr <athurnherr@yahoo.com>
parents:
6
diff
changeset
|
159 |
# magnetic heading from magnetometers |
56799f01321a
bug fix in libvec.pl; version given to Kanae Komaki, July 16, 2014
A.M. Thurnherr <athurnherr@yahoo.com>
parents:
6
diff
changeset
|
160 |
# - atan2 handles all the singularities |
29
f41d125405a6
version after ECOGIG EN586 cruise
A.M. Thurnherr <athurnherr@yahoo.com>
parents:
11
diff
changeset
|
161 |
# - the current version (7/29/2016) was verified with ECOGIG EN586 |
f41d125405a6
version after ECOGIG EN586 cruise
A.M. Thurnherr <athurnherr@yahoo.com>
parents:
11
diff
changeset
|
162 |
# data, where the IMP Mk.3 rev 2 (i.e. with coordinate mapping |
f41d125405a6
version after ECOGIG EN586 cruise
A.M. Thurnherr <athurnherr@yahoo.com>
parents:
11
diff
changeset
|
163 |
# in the sensor board) on profiles 1-15 was accidentally installed |
f41d125405a6
version after ECOGIG EN586 cruise
A.M. Thurnherr <athurnherr@yahoo.com>
parents:
11
diff
changeset
|
164 |
# in a consistent orientation with the DL (very similar pitch/roll |
f41d125405a6
version after ECOGIG EN586 cruise
A.M. Thurnherr <athurnherr@yahoo.com>
parents:
11
diff
changeset
|
165 |
# measured by both instruments) => heading is directly comparable |
5 | 166 |
#---------------------------------------------------------------------- |
167 |
||
168 |
sub mag_heading($$) |
|
169 |
{ |
|
29
f41d125405a6
version after ECOGIG EN586 cruise
A.M. Thurnherr <athurnherr@yahoo.com>
parents:
11
diff
changeset
|
170 |
# return angle_pos(deg(-atan2($_[0],$_[1]))); # used for 2014 CLIVAR P06 IMP processing with confused coord systems |
f41d125405a6
version after ECOGIG EN586 cruise
A.M. Thurnherr <athurnherr@yahoo.com>
parents:
11
diff
changeset
|
171 |
return angle_pos(deg(atan2($_[1],$_[0]))); |
5 | 172 |
} |
173 |
||
174 |
#---------------------------------------------------------------------- |
|
0 | 175 |
# &angle(val) |
176 |
# return angle in range [-180,180] |
|
5 | 177 |
# &angle_pos(val) |
178 |
# return angle in range [0,360] |
|
0 | 179 |
# &angle_diff(ref_dir,dir) |
5 | 180 |
# return rotation between two angles in range [-180,180] |
0 | 181 |
# &rotation_ts(dir) |
182 |
# return time series of rotation |
|
183 |
# &angle_ts(dir) |
|
184 |
# return time series of angle without "wrap-around jumps" |
|
185 |
#---------------------------------------------------------------------- |
|
186 |
||
187 |
sub angle(@) |
|
188 |
{ |
|
6 | 189 |
my($val) = &antsFunUsage(1,".","<val>",@_); |
190 |
return nan unless numberp($val); |
|
0 | 191 |
$val += 360 while ($val < -180); |
192 |
$val -= 360 while ($val > 180); |
|
193 |
return $val; |
|
194 |
} |
|
195 |
||
5 | 196 |
sub angle_pos(@) |
197 |
{ |
|
198 |
my($val) = angle(@_); |
|
6 | 199 |
return nan unless numberp($val); |
5 | 200 |
return ($val < 0) ? 360+$val : $val; |
201 |
} |
|
202 |
||
0 | 203 |
sub angle_diff(@) |
204 |
{ |
|
6 | 205 |
my($m,$s) = &antsFunUsage(2,"..","<minuend> <subtrahend>",@_); |
206 |
return nan unless numbersp($m,$s); |
|
0 | 207 |
return angle($m-$s); |
208 |
} |
|
209 |
||
210 |
{ my($last_in); |
|
211 |
||
212 |
sub rotation_ts(@) |
|
213 |
{ |
|
42 | 214 |
my($a) = &antsFunUsage(1,".","<angle>",@_); |
215 |
return nan unless numberp($a); |
|
0 | 216 |
|
217 |
my($rot) = defined($last_in) ? angle_diff($a,$last_in) : nan; |
|
218 |
$last_in = $a; |
|
219 |
return $rot; |
|
220 |
} |
|
221 |
} |
|
222 |
||
223 |
{ my($last_in,$last_out); |
|
224 |
||
225 |
sub angle_ts(@) |
|
226 |
{ |
|
42 | 227 |
my($a) = &antsFunUsage(1,".","<angle>",@_); |
228 |
return nan unless numberp($a); |
|
0 | 229 |
|
230 |
$last_out = $last_in = $a |
|
231 |
unless (defined($last_in)); |
|
232 |
||
233 |
$last_out += angle_diff($a,$last_in); |
|
234 |
$last_in = $a; |
|
235 |
return $last_out; |
|
236 |
} |
|
237 |
} |
|
238 |
||
239 |
#---------------------------------------------------------------------- |
|
240 |
# &ddeg(deg),&GMTdeg(deg) convert degree formats |
|
241 |
#---------------------------------------------------------------------- |
|
242 |
||
243 |
sub ddeg(@) |
|
244 |
{ |
|
245 |
my($deg) = &antsFunUsage(1,"","<degrees in GMT format>",@_); |
|
246 |
my($d,$m,$s) = split(':',$deg); |
|
247 |
return ($d>=0) ? $d+$m/60+$s/3600 |
|
248 |
: $d-$m/60-$s/3600; |
|
249 |
} |
|
250 |
||
251 |
# NB: without roundoff code, results are as follows: |
|
252 |
# abc -Lvec 'GMTdeg(ddeg("10:11"))' -> 10:11:8.52651e-13 |
|
253 |
# abc -Lvec 'GMTdeg(ddeg("10:10"))' -> 10:9:60 |
|
254 |
sub GMTdeg(@) |
|
255 |
{ |
|
256 |
my($deg) = &antsFunUsage(1,"f","<degrees>",@_); |
|
257 |
my($sgn); if ($deg < 0) { $sgn = '-'; $deg *= -1; } |
|
258 |
my($min) = 60*($deg-int($deg)); |
|
259 |
my($sec) = 60*($min-int($min)); |
|
260 |
$sec=0,$min++ if equal($sec,60); |
|
261 |
$sec=0 if equal($sec,0); |
|
262 |
return sprintf("$sgn%d:%d:%g",int($deg),int($min),$sec); |
|
263 |
} |
|
264 |
||
265 |
sub degmin(@) |
|
266 |
{ |
|
267 |
my($deg) = &antsFunUsage(1,"f","<degrees>",@_); |
|
268 |
my($sgn); if ($deg < 0) { $sgn = '-'; $deg *= -1; } |
|
269 |
my($min) = 60*($deg-int($deg)); |
|
270 |
$min=0 if equal($min,0); |
|
271 |
return sprintf("$sgn%d:%04.1f",int($deg),$min); |
|
272 |
} |
|
273 |
||
274 |
#---------------------------------------------------------------------- |
|
275 |
# &dist(lat1,lon1,lat2,lon2) distance on globe (in m) |
|
276 |
# &dist12(...) ditto but with deg/min/sec separate |
|
277 |
# &dir(lat1,lon1,lat2,lon2) direction btw two points |
|
278 |
# &area(gmt_region) approximate area |
|
279 |
#---------------------------------------------------------------------- |
|
280 |
||
281 |
sub dist(@) |
|
282 |
{ |
|
283 |
my($lat1,$lon1,$lat2,$lon2) = |
|
284 |
&antsFunUsage(4,"","lat1 lon1 lat2 lon2",@_); |
|
285 |
||
286 |
$lat1 = &ddeg($lat1); |
|
287 |
$lat2 = &ddeg($lat2); |
|
288 |
$lon1 = &ddeg($lon1); |
|
289 |
$lon2 = &ddeg($lon2); |
|
290 |
||
291 |
return 0 if ($lat1 == $lat2 && $lon1 == $lon2); # avoid roundoff |
|
292 |
||
293 |
$radius = 6378139; # const |
|
294 |
$pi = 3.14159265358979; |
|
295 |
$d2r = $pi/180.0; |
|
296 |
||
297 |
$ct1 = cos($d2r*$lat1); |
|
298 |
$st1 = sin($d2r*$lat1); |
|
299 |
$cp1 = cos($d2r*$lon1); |
|
300 |
$sp1 = sin($d2r*$lon1); |
|
301 |
$ct2 = cos($d2r*$lat2); |
|
302 |
$st2 = sin($d2r*$lat2); |
|
303 |
$cp2 = cos($d2r*$lon2); |
|
304 |
$sp2 = sin($d2r*$lon2); |
|
305 |
||
306 |
$cosine = $ct1*$cp1*$ct2*$cp2 + $ct1*$sp1*$ct2*$sp2 + $st1*$st2; |
|
307 |
if ($cosine > 1.0) { $cosine = 1.0; } |
|
308 |
if ($cosine < -1.0) { $cosine = -1.0; } |
|
309 |
||
310 |
return $radius * acos($cosine); |
|
311 |
} |
|
312 |
||
313 |
sub dist12(@) |
|
314 |
{ |
|
315 |
my($la1d,$la1m,$la1s,$lo1d,$lo1m,$lo1s, |
|
316 |
$la2d,$la2m,$la2s,$lo2d,$lo2m,$lo2s) = |
|
317 |
&antsFunUsage(12,"ffffffffffff","lat1 m s lon1 m s lat2 m s lon2 m s",@_); |
|
318 |
return dist( |
|
319 |
($la1d>=0)?$la1d+$la1m/60+$la1s/3600 : $la1d-$la1m/60-$la1s/3600, |
|
320 |
($lo1d>=0)?$lo1d+$lo1m/60+$lo1s/3600 : $lo1d-$lo1m/60-$lo1s/3600, |
|
321 |
($la2d>=0)?$la2d+$la2m/60+$la2s/3600 : $la2d-$la2m/60-$la2s/3600, |
|
322 |
($lo2d>=0)?$lo2d+$lo2m/60+$lo2s/3600 : $lo2d-$lo2m/60-$lo2s/3600 |
|
323 |
); |
|
324 |
} |
|
325 |
||
326 |
sub dir(@) |
|
327 |
{ |
|
328 |
my($lat1,$lon1,$lat2,$lon2) = |
|
329 |
&antsFunUsage(4,"","lat1 lon1 lat2 lon2",@_); |
|
330 |
my($dx) = dist(($lat1+$lat2)/2,$lon1,($lat1+$lat2)/2,$lon2); |
|
331 |
$dx *= -1 if ($lon2 < $lon1); |
|
332 |
my($dy) = dist($lat1,($lon1+$lon2)/2,$lat2,($lon1+$lon2)/2); |
|
333 |
$dy *= -1 if ($lat2 < $lat1); |
|
334 |
return ($dx == 0 && $dy == 0) ? nan : vel_dir($dx,$dy); |
|
335 |
} |
|
336 |
||
337 |
sub area(@) |
|
338 |
{ |
|
339 |
my($R) = &antsFunUsage(1,"",'<"W/E/S/N">',@_); |
|
340 |
my($W,$E,$S,$N) = split('/',$R); |
|
341 |
||
342 |
return (&dist($S,$W,$S,$E) + &dist($N,$W,$N,$E)) / 2 * |
|
343 |
(&dist($S,$W,$N,$W) + &dist($S,$E,$N,$E)) / 2; |
|
344 |
} |
|
345 |
||
2 | 346 |
#---------------------------------------------------------------------- |
3 | 347 |
# ($lat,$lon,$t) = &closestPointOnStraightLine(lat,lon,lat1,lonA,lat2,lon2) |
2 | 348 |
# - determine point on line segment from <lat1,lonA> to <lat2,lon2> that |
349 |
# is closest to target point <lat,lon> |
|
3 | 350 |
# - $t [0-1] output indicates where along the line segment the closest |
351 |
# point lies |
|
2 | 352 |
# - http://stackoverflow.com/questions/3120357/get-closest-point-to-a-line |
353 |
# - NOT DONE IN PLANAR GEOMETRY => USE ONLY IN SMALL DOMAINS |
|
354 |
#---------------------------------------------------------------------- |
|
355 |
||
356 |
sub closestPointOnStraightLine(@) |
|
357 |
{ |
|
358 |
my($latP,$lonP,$latA,$lonA,$latB,$lonB) = |
|
359 |
&antsFunUsage(6,'ffffff','pnt_lat, pnt_lon, lne_latA, lne_lonA, lne_latB, lne_lonB',@_); |
|
360 |
||
361 |
my($ABlon) = $lonB - $lonA; my($ABlat) = $latB - $latA; |
|
362 |
my($APlon) = $lonP - $lonA; my($APlat) = $latP - $latA; |
|
363 |
my($t) = ($APlon*$ABlon + $APlat*$ABlat) / ($ABlon**2 + $ABlat**2); |
|
364 |
return (undef,undef) unless ($t>=0 && $t<=1); |
|
3 | 365 |
return ($latA + $t*$ABlat,$lonA + $t*$ABlon, $t); |
2 | 366 |
} |
367 |
||
0 | 368 |
1; |