Contents
Angular units
Angular values come in different shapes and under different names. Angles can be measured in radians, degrees, hours. Military have their own unit, called mil (which comes in at least 3 different varieties, depending on a country) and French surveyors at some point used (or, maybe still use) grad. Even when expressed in degrees, the fractional part can be expressed in familiar decimals, minutes or minutes and seconds.
This script attempts to deal with some of this complexity.
coinsert 'geo' cocurrent 'geo' NB. Angular units formatting and conversion. require 'regex' «convert» «format» «parse» «misc»
Conversion
Units conversion
NB.*θfζ v convert angular units: obtain θ units from ζ units NB. Units: NB. d - degrees NB. m - minutes(angular) =nm NB. r - radians NB. g - grads NB. h - hours dfr=:180p_1&* mfr=:(180 * 60 * 1p_1)&* gfr=:200p_1&* hfr=:12p_1&* rfd=:dfr^:_1 rfm=:mfr^:_1 rfg=:gfr^:_1 rfh=:hfr^:_1
f in the name stands for from. This way dfr reads degrees from radians. Since data is visualized as "flowing" from right to left, this seems to be better mnemonic than r2d.
h here is angular hour, such as one used to describe right ascension. There are 24 hours in 360°.
m means minute of arc =1/60°, not 1/60 of an hour. It is also equal to 1 nautical mile on Earth surface.
RFD=:%DFR=:180p_1 RFM=:%MFR=:(180 * 60 * 1p_1) RFG=:%GFR=:200p_1 RFH=:%HFR=:12p_1
Originally the script included these conversion constants, but there seems to be little use for them.
Base 60 Conversion
Sometimes fraction of a degree (or an hour) is expressed as minutes and fraction of a minute is expressed as seconds.
tomin=:f60=:((_: , #&60)@[ #: 60&^@[ * ]) frommin=:e60=:(60&#. % 60&^@<:@#)
f60 (format-60) converts decimal fraction representation into given number of base 60 parts. x = number of such parts (0 = none, 1 = minutes and decimal fractions of minutes, 2 = minutes, seconds and decimal fraction of seconds, etc.) This works so far only for positive. Negatives need to be investigated
e60 (eval-60) performs backward conversion -- from list of base 60 units into decimal fraction.
NB.*cms v cover verb "convert minutes and seconds" NB. optional left argument specifies how many base-60 parts should NB. be on output cms=:e60 : (f60 e60)
For example:
cms 1 20 NB. 1 hour 20 minutes 1.33333 2 cms 1 20.11 NB. 1 hour 20.11 minutes in hours, minutes, seconds 1 20 6.6
NB.*toneg v force y into (-x/2..x/2] range toneg=:(| - [ * | > -:@[) NB.*round v round y to nearest multiple of x round=:([ (] - |) -:@[ + ]) NB.*roundup v round y up to a multiple of x roundup=:] + (| -) NB.*rounddown v round y down to a multiple of x rounddown=:] - |
Formatting
All formatting verbs have names starting with f.
Format individual values
DMS=:'-',u: 16bb0 16bb4 16b2dd NB. unicode positions, >j601
DMS is graphic symbols for sign (-), degree (°), minute (´) and second (˝).
Alternatives for DMS:
DMS=:'- ''"' NB. ascii
DMS=:'-',16bb0 16b92 16b94{a. NB. pre j601, single byte encodingHMS=:'?hms'
Labels for hours, minutes, seconds.
All the following formatting verbs take angular value y given in radians and format it as a string. The verbs perform conversion, round and add appropriate marking symbols depending on the assumed meaning of their argument.
f2=:(({.@[ #~ 0 > ]) , ;@(}.@[ (,~ ":)&.> 2&tomin@|@]))
This is a generic verb that formats possibly fractional y into 2 level base-60 fraction using x for sign symbol and names for whole, first and second base-60 components
fh2=: HMS&f2@hfr fd2=: DMS&f2@dfr
Format radians as degrees-minutes-seconds or hours-minutes-seconds with proper marking.
fpl=:('fd2';'fd2';']')"_ apply&> ]Some remnant. Looks like a formatting for a triplet representing a point in spherical coordinates.
NB.*faz v format azimuth
faz=:(1{DMS) ,~ _5 {. '00' , 0j1 ": 360 | dfr
Format as azimuth. Show in range 0..360° rounded to 0.1°
NB.*fdist v format distance fdist_nm=:'nm' ,~ '0.1' >@(8!:0) mfr fdist_km=:'km' ,~ '0.1' >@(8!:0) 6372.797&* fdist=:fdist_nm
Angular distances on the globe can be displayed in linear units. fdist name is default distance formatter and it selects one of the formatters. Format is rounded to 0.1 of a unit. Appends unit name (nm, km or mi).
NB.*fdm v format to decimal minutes
fdm=:3 : 0
'd m'=.1 tomin | dfr y
('-'#~0>y),(":d),(1{DMS),(_4{.'0',0j1":m),(2{DMS)
:
'd m'=.1 tomin | (0.1 % 60) round dfr y
(x{~0>y),~(":d),(1{DMS),(_4{.'0',0j1":m),(2{DMS)
)
Another generic verb that, probably would not be useful on itself. It outputs the number rounded to 0.1´. Optional x is a 2-character list of names for positive and negative values.
NB.*falt v format altitude falt=:fdm
Format observed/computed altitude of a body.
NB.*fmin v format angle as angular minutes fmin=: '´' ,~ '0.1' >@(8!:0) mfr
Same as fdist_nm, but different graphic sign. Useful to format corrections/offsets.
NB.*flat v format latitude (positive=N, negative=S) flat=:'NS'&fdm
Positive is North, negative is South. The latitude "name" (sign) is located after the numeric values. In some texts it is located in front of the numbers, but placing name in the back seems to be more common.
NB.*flon v format longitude (positive=E, negative=W) flon=:'EW'&fdm
Same, but East and West. This verb may benefit from applying 2p1&toneg before it, but so far it is only applied to a longitude which is already in canonical form.
NB.*fdec v format declination (positive=N, negative=S) fdec=:_1 |. flat
It seems that, unlike latitude, declination has its name displayed before the numeric values more often than after.
Format compound values
NB.*fmt v generic format applies formatting verbs from x to elements of y NB. and joins result together using ' ' as a delimiter fmt=:(<' ') ;@}.@,@,. apply&.>
NB.*fpos v format 2-element position array (lat,lon)
fpos=:('flat';'flon')&fmt
NB.*fvec v format 2-element vector array (azimuth, distance)
fvec=:('faz';'fdist')&fmt
NB.*fobs v format 2-element array of sextant observation (altitude, azimuth)
fobs=:('falt';'faz')&fmt
This is almost same as fvec, except that Hs/Hc are usually recorded with altitude first.
NB.*fcat v format catalog entry (Dec,RA)
fcat=:('fdec';'fh2')&fmt
Format non-angular values
NB.*fdur v format time duration in hours; hours above 24 are displayed
NB. as days, fractions of an hour are displayed as minutes and seconds.
fdur=:3 : 0
s=.'-' {.~y<0
d=.(|y) <.@% 24
y=.1r3600 round 24|y NB. round to nearest second
t=.(-@(0={:) }. ]) 2 tomin y
s,((":d),'d ')&,^:(d>0) (":@{.t) , ,(':' , _2{.'00',":)"0 <.}.t
)
This verb is for formatting time intervals expressed in hours. Stretches longer than 24 hours are converted to days. Example:
fdur 700%6 NB. 700 miles at 6 knots 4d 20:40 fdur 70%6 NB. 70 miles at 6 knots 11:40
NB.*mmin v move decimal point and everything after it after last symbol
mmin=:(i:~ ({. , _1 |. }.) ])
Parsing
NB.*angfstr v parse string containing 1 or more angles in degrees, minutes, etc.
NB. Returns numeric vector
angfstr=:3 : 0
p=.'([-+NnSsWwEe]?)([0-9.]+)(\s*[°d]\s*([0-9.]+)(\s*[′´''m]\s*(([0-9.]+)\s*[″˝"s]?)?)?)?([NnSsWwEe]?)' rxmatches y
r=.i.0
for_m. p do.
d=.frommin {.@".&> (2 4 7{m) rxfrom y
h=.{.toupper >y rxfrom~ 1{m
s=.1 _1 1 _1 1 _1 1{~ 'NSEW+- ' i. h
h=.{.toupper >y rxfrom~ 8{m
r=.r,d*s*1 _1 1 _1 1 _1 1{~ 'NSEW+- ' i. h
end.
{.^:(1=#) rfd r
)
hoursfstr=:([: frommin [: ".;._1 ':'&,)
Stub for time/duration parsing. Not included in final script.
According to http://en.wikipedia.org/wiki/Geographic_coordinate_conversion all of the following is valid and acceptable way to write coordinates
GP_TEST=:<;._2 ] 0 : 0 40:26:46N,79:56:55W 40:26:46.302N 79:56:55.903W 40°26'47"N 79°58'36"W 40d 26' 47" N 79d 58' 36" W 40.446195N 79.948862W 40.446195, -79.948862 40° 26.7717, -79° 56.93172 N4°35.0´ W12°30.6´ 4°35.0´N 12°30.6´W )
NB.*posfstr v parse string into (lat, lon) position
NB. Understands variety of input forms
posfstr=: 3 : 0
atstart=.(] rxmatch~ '^\s*(' , ')\s*' ,~ [)
SIGN=.'[-+NnSsWwEe]'
TAILSIGN=.'[NnSsWwEe]'
NUMBER=.'[0-9.]+'
DEG=.'[do°º\*:]'
MIN=.'[m′''´’‘:]'
SEC=.'[s″"˝”“]|'''''
SEP=.'[;,/]|$'
found=._1 0 -.@-: {.
piece=. >@rxfrom~ 1&{
r=.i.0 NB. numberic results
o=.i.0 NB. inferred order
while. '' -.@-: y do.
NB. components
sgn=.''
d=.0
m=.0
s=.0
p=.SIGN atstart y
sgn=.y piece p
y=.({:{.p)}.y
p=.NUMBER atstart y
assert. found p
d=._ ". y piece p
y=.y}.~{:{.p
if. -.found p=.DEG atstart y do. goto_trail. end.
y=.y}.~{:{.p
if. -.found p=.NUMBER atstart y do. goto_trail. end.
m=._ ". y piece p
y1=.y NB. save in case need to rollback
y=.y}.~{:{.p
if. found p=.MIN atstart y do.
y=.y}.~{:{.p
else.
if. found p=.DEG atstart y do.
NB. rollback
m=.0
y=.y1
end.
goto_trail.
end.
if. -.found p=.NUMBER atstart y do. goto_trail. end.
s=._ ". y piece p
y1=.y NB. save in case need to rollback
y=.y}.~{:{.p
if. found p=.SEC atstart y do.
y=.y}.~{:{.p
else.
if. found p=.DEG atstart y do.
NB. rollback
m=.0
y=.y1
end.
goto_trail.
end.
label_trail.
NB. check for sign if sign is currently empty
if. ''-:sgn do.
p=.TAILSIGN atstart y
if. found p do.
sgn=.y piece p
y=.y}.~{:{.p
end.
end.
NB. skip separators/trainiling spaces if any
if. found p=.SEP atstart y do. y=.y}.~{:{.p end.
sgn=.toupper {.sgn
r=.r,(frommin d,m,s)*1 _1 1 _1 1 _1 1{~ 'NSEW+- ' i. sgn
o=.o,_1 _1 1 1 0 {~ 'NSEW' i. sgn
end.
rfd r /: o
)
This sequential parsing routine replaces older version which was simpler and faster, but did not handle some of the example cases.
The intention of this verb is to be able to process copy/paste geo coordinates taken from a random source into canonical form. At least, in some of the cases.
Other
makepos=:rfd
converts numeric latitude and longitude (in degrees) to radians.
NB.*makevec v convert numeric directional vector into radians
NB. y=(direction in degrees, speed/distance in knots/nm)
makevec=:('rfd';'rfm')&(apply&>)
This verb better belongs to `drnav` script, but all necessary routines are here.
Contributed by AndrewNikitin
