## 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.

«angular.ijs»=
```coinsert 'geo'

cocurrent 'geo'
NB. Angular units formatting and conversion.
require 'regex'
«convert»
«format»
«parse»
«misc»
```

## Conversion

### Units conversion

«convert»=
```NB.*θfζ v convert angular units: obtain θ units from ζ units
NB. Units:
NB. d - degrees
NB. m - minutes(angular) =nm
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.

«convert»=
```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.

«convert»=
```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```

«convert»=
```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

«format»=
```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 encoding```

«format»=
```HMS=:'?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.

«format»=
```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

«format»=
```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.

«format»=
```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°

«format»=
```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).

«format»=
```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.

«format»=
```NB.*falt v format altitude
falt=:fdm
```

Format observed/computed altitude of a body.

«format»=
```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.

«format»=
```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.

«format»=
```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.

«format»=
```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

«format»=
```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&.>
```

«format»=
```NB.*fpos v format 2-element position array (lat,lon)
fpos=:('flat';'flon')&fmt
```

«format»=
```NB.*fvec v format 2-element vector array (azimuth, distance)
fvec=:('faz';'fdist')&fmt
```

«format»=
```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.

«format»=
```NB.*fcat v format catalog entry (Dec,RA)
fcat=:('fdec';'fh2')&fmt
```

### Format non-angular values

«format»=
```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```

«format»=
```NB.*mmin v move decimal point and everything after it after last symbol
mmin=:(i:~ ({. , _1 |. }.) ])
```

## Parsing

«parse»=
```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
)```

«parse»=
```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

«misc»=
```makepos=:rfd
```

converts numeric latitude and longitude (in degrees) to radians.

«misc»=
```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

AndrewNikitin/Angular (last edited 2011-06-07 12:22:33 by AndrewNikitin)