Files
paparazzi/sw/lib/ocaml/latlong.ml
T
Felix Ruess 1008f946d8 [lib/ocaml] update leap_seconds
most recent leap second was inserted on June 30, 2012 at 23:59:60 UTC
So since the gps epoch 1980 we have 16 leap seconds.

closes #549
2013-10-05 14:57:15 +02:00

714 lines
25 KiB
OCaml

(*
* Geographic conversion utilities
*
* Copyright (C) 2004-2006 ENAC, Pascal Brisset, Antoine Drouin
*
* This file is part of paparazzi.
*
* paparazzi 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 2, or (at your option)
* any later version.
*
* paparazzi 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 paparazzi; see the file COPYING. If not, write to
* the Free Software Foundation, 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
*)
let (//) = Filename.concat
module C = struct
include Complex
let make x y = {re = x; im = y}
let im c = c.im
let re c = c.re
let scal a {re = x; im = y} = {re = x*.a; im = y*.a}
let i {re = x; im = y} = {re = -.y; im = x}
let sin z =
let iz = i z in
i (scal (-. 0.5) (sub (exp iz) (exp (scal (-1.) iz))))
end
let pi = 3.14159265358979323846;;
type degree = float
type radian = float
type semi = float
type dms = int * int * float
type semicircle = { lat : semi; long : semi }
type geographic = { posn_lat : radian ; posn_long : radian }
let norm_angle = fun long ->
if long >= pi then long -. 2.*.pi
else if long < -. pi then long +. 2.*.pi
else long
let make_geo = fun lat long ->
{ posn_long = norm_angle long; posn_lat = lat }
let valid_geo = fun {posn_long = lambda; posn_lat = phi} ->
phi >= (-.pi/.2.) && phi <= pi/.2. && lambda >= -.pi && lambda < pi
type angle_unit = Semi | Rad | Deg | Grd
let piradian = function
Semi -> 2. ** 31. | Rad -> pi | Deg -> 180. | Grd -> 200.
let (>>) u1 u2 x = (x *. piradian u2) /. piradian u1;;
let make_geo_deg = fun lat long ->
{ posn_long = norm_angle ((Deg>>Rad)long); posn_lat = ((Deg>>Rad)lat) }
let deg_string_of_rad = fun r -> Printf.sprintf "%.6f" ((Rad>>Deg)r)
let decimal d m s = float d +. float m /. 60. +. s /. 3600.;;
let dms = fun x ->
let d = truncate x in
let m = truncate ((x -. float d) *. 60.) in
let s = 3600. *. (x -. float d -. float m /. 60.) in
(d, m, s);;
let sprint_degree_of_radian x =
Printf.sprintf "%.6f" ((Rad>>Deg) x)
let string_degrees_of_geographic sm =
Printf.sprintf "%s\t%s"
(sprint_degree_of_radian sm.posn_lat) (sprint_degree_of_radian sm.posn_long)
let string_dms_of_geographic = fun geo ->
let hemi = if geo.posn_lat >= 0. then 'N' else 'S'
and east = if geo.posn_long >= 0. then 'E' else 'W'
and lat = abs_float geo.posn_lat
and lon = abs_float geo.posn_long in
let (lat_d, lat_m, lat_s) = dms ((Rad>>Deg) lat)
and (lon_d, lon_m, lon_s) = dms ((Rad>>Deg) lon) in
Printf.sprintf "%2d %02d' %02.1f\" %c\t%2d %02d' %02.1f\" %c"
lat_d lat_m lat_s hemi lon_d lon_m lon_s east
let of_semicircle x =
{ posn_lat = (Semi>>Rad) x.lat ; posn_long = (Semi>>Rad) x.long }
let semicircle_of x =
{ lat = (Rad>>Semi) x.posn_lat ; long = (Rad>>Semi) x.posn_long }
type ellipsoid = { dx : float; dy : float; dz : float; a : float; df : float; e : float }
let ntf = { dx = -168.; dy = -60.; dz = 320. ; a = 6378249.2; df = 0.0034075495234250643; e = 0.08248325676}
let wgs84 = { dx = 0.; dy = 0.; dz = 0. ; a = 6378137.0; df = 0.0033528106647474805 ; e = 0.08181919106}
let ed50 = { dx = -87.0; dy = -98.0; dz = -121.0 ; a = 6378388.0; df = 0.003367003367003367 ; e = 0.08199188998}
let nad27 = { dx = 0.0; dy = 125.0; dz = 194.0 ; a = wgs84.a-. -.69.4; df = wgs84.df-. -0.37264639 /. 1e4 ; e = 0.08181919106(*** ??? ***)}
let grs80 = { dx = 0.; dy = 0.; dz = 0. ; a = 6378137.0; df = 0.00335281068118231879 ; e = 0.0818191910428151675}
type geodesic = NTF | ED50 | WGS84 | NAD27
type ntf = geographic
let ellipsoid_of = function
NTF -> ntf | ED50 -> ed50 | WGS84 -> wgs84 | NAD27 -> nad27
let latitude_isometrique phi e =
log (tan (pi/.4. +. phi /. 2.0)) -. e /. 2.0 *. log ((1.0 +. e *. sin phi) /. (1.0 -. e *. sin phi))
let inverse_latitude_isometrique lat e epsilon =
let exp_l = exp lat in
let pi_2 = pi /. 2. in
let phi0 = 2. *. atan exp_l -. pi_2 in
let rec loop phi =
let sin_phi = e *. sin phi in
let phi' = 2. *. atan (((1. +. sin_phi) /. (1. -. sin_phi))**(e/.2.) *. exp_l) -. pi_2 in
if abs_float (phi' -. phi) < epsilon then phi' else loop phi' in
loop phi0;;
(* http://professionnels.ign.fr/DISPLAY/000/526/700/5267002/transformation.pdf *)
type lambert_zone = {
ellipsoid : ellipsoid;
phi0 : radian;
c : float;
lambda0 : radian;
y0 : int;
x0 : int;
ys : int;
n : float
}
type meter = int
type fmeter = float
type lambert = { lbt_x : meter; lbt_y : meter }
type utm = { utm_x : fmeter; utm_y : fmeter ; utm_zone : int }
module Ellipse = struct
let e_square d = 2.0 *. d -. d ** 2.0;;
let e_prime_square d = 1.0 /. (1.0 -. d) ** 2.0 -. 1.0;;
end
(* From http://www.winnepenninckx.com/Geo/WiS/eqntf.html et http://www.ign.fr/DISPLAY/000/526/701/5267019/NTG_71.pdf *)
let lambertI =
let phi0 = (Deg>>Rad) (decimal 49 30 0.) in
{ ellipsoid = ntf;
lambda0 = (Deg>>Rad) (decimal 2 20 14.025);
phi0 = phi0;
x0 = 600000;
y0 = 200000;
ys = 5657617;
c = 11603796.98;
n = sin phi0 (* tangent projection *)
};;
let lambertII =
let phi0 = (Deg>>Rad) (decimal 46 48 0.) in
{ ellipsoid = ntf;
lambda0 = (Deg>>Rad) (decimal 2 20 14.025);
phi0 = phi0;
x0 = 600000;
y0 = 2200000;
ys = 6199696;
c = 11745793.39;
n = sin phi0 (* tangent projection *)
};;
let lambertIIe = { lambertII with ys = 8199696 };;
let lambertIII =
let phi0 = (Deg>>Rad) (decimal 44 6 0.) in
{ ellipsoid = ntf;
lambda0 = (Deg>>Rad) (decimal 2 20 14.025);
phi0 = phi0;
x0 = 600000;
y0 = 3200000;
ys = 6791905;
c = 11947992.52;
n = sin phi0 (* tangent projection *)
};;
let lambertIV =
let phi0 = (Deg>>Rad) (decimal 42 09 54.) in
{
ellipsoid = ntf;
lambda0 = (Deg>>Rad) (decimal 2 20 14.025);
phi0 = phi0;
x0 = 234;
y0 = 4185861;
ys = 7239162;
c = 12136281.99;
n = sin phi0; (* tangent projection *)
};;
let lambert93 = {
ellipsoid = grs80;
lambda0 = (Deg>>Rad) (decimal 3 0 0.0);
phi0 = (Deg>>Rad) (decimal 46 30 0.);
x0 = 700000;
y0 = 6600000;
ys = 12655612;
c = 11754255.426;
n = 0.725607765053268738 (* Secant projection *)
};;
let lambert_n l = l.n
let lambert_c = fun l -> l.c
let of_lambert l { lbt_x = x; lbt_y = y } =
let c = lambert_c l and n = lambert_n l in
let dx = float (x - l.x0) and dy = float (y - l.ys) in
let r = sqrt (dx**2. +. dy**2.) in
let gamma = atan2 dx (-. dy) in
let lambda = l.lambda0 +. gamma /. n
and ll = -. 1. /. n *. log (abs_float (r/.c)) in
let phi = inverse_latitude_isometrique ll l.ellipsoid.e 1e-11 in
make_geo phi lambda
let lambert_of l ({posn_long = lambda; posn_lat = phi} as pos) =
if not (valid_geo pos) then
invalid_arg "Latlong.lambert_of";
let n = lambert_n l in
let e = l.ellipsoid.e in
let ll = latitude_isometrique phi e in
let r = lambert_c l *. exp (-. ll *. n) in
let gamma = (lambda -. l.lambda0) *. n in
let x = l.x0 + truncate (r *. sin gamma)
and y = l.ys - truncate (r *. cos gamma) in
{ lbt_x = x; lbt_y = y };;
let serie5 cc e =
let ee = Array.init (Array.length cc.(0)) (fun i -> e ** (float (2*i))) in
Array.init (Array.length cc)
(fun i ->
let cci = cc.(i) in
let x = ref 0. in
for j = 0 to Array.length cci - 1 do
x := !x +. cci.(j) *. ee.(j)
done;
!x);;
let coeff_proj_mercator =
[|[|1.; -. 1./.4.; -. 3./.64.; -.5./.256.; -.175./.16384.|];
[|0.;1./.8.; -.1./.96.; -.9./.1024.; -.901./.184320.|];
[|0.;0.;13./.768.;17./.5120.;-.311./.737280.|];
[|0.;0.;0.; 61./.15360.;899./.430080.|];
[|0.;0.;0.;0.;49561./.41287680.|]|];;
let coeff_proj_mercator_inverse =
[|coeff_proj_mercator.(0);
[|0.;1./.8.; 1./.48.; 7./.2048.; 1./.61440.|];
[|0.;0.;1./.768.;3./.1280.;559./.368640.|];
[|0.;0.;0.; 17./.30720.;283./.430080.|];
[|0.;0.;0.;0.;4397./.41287680.|]|];;
let utm_of' = fun geo ->
let ellipsoid = ellipsoid_of geo in
let k0 = 0.9996
and xs = 500000. in
let e = ellipsoid.e
and n = k0 *. ellipsoid.a in
let c = serie5 coeff_proj_mercator e in
fun ({posn_long = lambda; posn_lat = phi} as pos) ->
if not (valid_geo pos) then
invalid_arg "Latlong.utm_of";
let lambda_deg = truncate (floor ((Rad>>Deg)lambda)) in
let zone = (lambda_deg + 180) / 6 + 1 in
let lambda_c = (Deg>>Rad) (float (lambda_deg - ((lambda_deg mod 6)+6)mod 6 + 3)) in
let ll = latitude_isometrique phi e
and dl = lambda -. lambda_c in
let phi' = asin (sin dl /. cosh ll) in
let ll' = latitude_isometrique phi' 0. in
let lambda' = atan (sinh ll /. cos dl) in
let z = C.make lambda' ll' in
let z' = ref (C.scal c.(0) z) in
for k = 1 to Array.length c - 1 do
z' := C.add !z' (C.scal c.(k) (C.sin (C.scal (float (2*k)) z)))
done;
z' := C.scal n !z';
{ utm_zone = zone; utm_x = xs +. C.im !z'; utm_y = C.re !z' };;
(* Benchmarks with limited bound on the for loop:
utm_of WGS84 {posn_lat = 0.8; posn_long = 0.1 };;
4: {utm_x = 711976.494998118491; utm_y = 5079519.01467700768; utm_zone = 31}
3: {utm_x = 711976.494993993081; utm_y = 5079519.01467550546; utm_zone = 31}
2: {utm_x = 711976.49488538783; utm_y = 5079519.02243153844; utm_zone = 31}
1: {utm_x = 711977.141232272843; utm_y = 5079519.25322676543; utm_zone = 31}
0: {utm_x = 711985.538644456305; utm_y = 5074176.83014749549; utm_zone = 31}
==> centimetric precision with 2 (i.e. using c(0), c(1) and c(2)
*)
(** Static evaluation for better performance (~50% for cputime) *)
let utm_of =
let u_WGS84 = utm_of' WGS84
and u_NTF = utm_of' NTF
and u_ED50 = utm_of' ED50
and u_NAD27 = utm_of' NAD27 in
fun geo -> match geo with
WGS84 -> u_WGS84
| NTF -> u_NTF
| ED50 -> u_ED50
| NAD27 -> u_NAD27
let of_utm' geo =
let ellipsoid = ellipsoid_of geo in
let k0 = 0.9996
and xs = 500000.
and ys = 0. in
let e = ellipsoid.e
and n = k0 *. ellipsoid.a in
let c = serie5 coeff_proj_mercator_inverse e in
fun { utm_zone = f; utm_x = x; utm_y = y } ->
if x < 0. || x > 1e7 || y < -1e7 || y > 1e7 || f < 0 || f > 60 then
invalid_arg "Latlong.of_utm";
let lambda_c = (Deg>>Rad) (float (6 * f - 183)) in
let z' = C.scal (1./.n/.c.(0)) (C.make (y-.ys) (x-.xs)) in
let z = ref z' in
for k = 1 to Array.length c - 1 do
z := C.sub !z (C.scal c.(k) (C.sin (C.scal (float (2*k)) z')))
done;
let ll = C.re !z and lls = C.im !z in
let lambda = lambda_c +. atan (sinh lls /. cos ll)
and phi' = asin (sin ll /. cosh lls) in
let ll = latitude_isometrique phi' 0. in
let phi = inverse_latitude_isometrique ll e 1e-11 in
make_geo phi lambda
(** Static evaluation for better performance (~50% for cputime) *)
let of_utm =
let u_WGS84 = of_utm' WGS84
and u_NTF = of_utm' NTF
and u_ED50 = of_utm' ED50
and u_NAD27 = of_utm' NAD27 in
fun geo -> match geo with
WGS84 -> u_WGS84
| NTF -> u_NTF
| ED50 -> u_ED50
| NAD27 -> u_NAD27
let (<<) geo1 geo2 ({posn_long = lambda; posn_lat = phi} as pos) =
if not (valid_geo pos) then
invalid_arg "Latlong.(<<)";
let elps1 = ellipsoid_of geo1
and elps2 = ellipsoid_of geo2 in
let d12 = sin phi
and d13 = cos phi
and d14 = sin lambda
and d15 = cos lambda in
let d16 = Ellipse.e_square elps2.df
and d17 = Ellipse.e_square elps1.df in
let d18 = elps2.a /. sqrt (1.0 -. d16 *. d12 ** 2.0) in
let d20 = d18 *. d13 *. d15 in
let d21 = d18 *. d13 *. d14 in
let d22 = d18 *. (1.0 -. d16) *. d12 in
let d23 = d20 -. elps1.dx +. elps2.dx in
let d24 = d21 -. elps1.dy +. elps2.dy in
let d25 = d22 -. elps1.dz +. elps2.dz in
let d26 = sqrt (d23 ** 2.0 +. d24 ** 2.0) in
let d27 = atan2 d25 (d26 *. (1.0 -. elps1.df)) in
let d28 = elps1.a *. (1.0 -. elps1.df) in
let d29 = Ellipse.e_prime_square elps1.df in
let d3 = atan2 (d25 +. d29 *. d28 *. (sin d27) ** 3.0) (d26 -. d17 *. elps1.a *. (cos d27) ** 3.0) in
let d4 = atan2 d24 d23 in
make_geo d3 d4
let utm_distance = fun utm1 utm2 ->
if utm1.utm_zone <> utm2.utm_zone then invalid_arg "utm_distance";
sqrt ((utm1.utm_x -. utm2.utm_x)**2. +. (utm1.utm_y -. utm2.utm_y)**2.)
let utm_add = fun u (x, y) ->
{utm_x = u.utm_x +. x; utm_y = u.utm_y +. y; utm_zone = u.utm_zone }
let utm_sub = fun u1 u2 ->
if u1.utm_zone <> u2.utm_zone then
invalid_arg (Printf.sprintf "utm_sub: %d %d" u1.utm_zone u2.utm_zone);
(u1.utm_x -. u2.utm_x, u1.utm_y -. u2.utm_y)
let of_lambertIIe = fun lbt ->
(WGS84<<NTF)(of_lambert lambertIIe lbt)
let lambertIIe_of = fun wgs84 ->
lambert_of lambertIIe ((NTF<<WGS84)wgs84)
let lbt_add = fun {lbt_x=x; lbt_y=y} (dx, dy) ->
{lbt_x = x + truncate dx; lbt_y = y + truncate dy }
let lbt_sub = fun {lbt_x=x1; lbt_y=y1} {lbt_x=x2; lbt_y=y2} ->
(float (x1-x2), float (y1-y2))
let space = Str.regexp "[ \t\'\"]+"
let fos = float_of_string
let ios = fun x -> try int_of_string x with _ -> failwith (Printf.sprintf "int_of_string: %s" x)
let rodg = fun s -> (Deg>>Rad)(fos s)
let of_string = fun s ->
match Str.split space s with
["WGS84"; lat; long] ->
make_geo (rodg lat) (rodg long)
| ["WGS84_dms"; lat_d; lat_m; lat_s; hemi; lon_d; lon_m; lon_s; east_west] ->
let sign_hemi =
match hemi with
"N" -> 1. | "S" -> -1.
| _ -> failwith (Printf.sprintf "N or S expected for hemispere in dms, found '%s'" hemi) in
let sign_east =
match east_west with
"E" -> 1. | "W" -> -1.
| _ -> failwith (Printf.sprintf "E or W expected for hemispere in dms, found '%s'" east_west) in
let lat = sign_hemi *. decimal (ios lat_d) (ios lat_m) (fos lat_s)
and lon = sign_east *. decimal (ios lon_d) (ios lon_m) (fos lon_s) in
make_geo ((Deg>>Rad) lat) ((Deg>>Rad) lon)
| ["WGS84_bearing"; lat; long; dir; dist] ->
let utm_ref = utm_of WGS84 (make_geo (rodg lat) (rodg long)) in
let dir = rodg dir and dist = fos dist in
let dx = dist *. sin dir
and dy = dist *. cos dir in
of_utm WGS84 (utm_add utm_ref (dx, dy))
| ["UTM";x;y;zone] ->
of_utm WGS84 { utm_x = fos x; utm_y = fos y; utm_zone = ios zone}
| ["LBT2e";x;y] ->
of_lambertIIe {lbt_x=ios x; lbt_y=ios y }
| _ -> invalid_arg (Printf.sprintf "Latlong.of_string: %s" s)
let string_of = fun geo ->
Printf.sprintf "WGS84 %s" (string_degrees_of_geographic geo)
let deg_of_string = fun s ->
match Str.split space s with
[lat_d; lat_m; lat_s] ->
decimal (ios lat_d) (ios lat_m) (fos lat_s)
| [lat_d; lat_m; lat_s; hemi] ->
let sign =
match hemi with
"N" | "E" -> 1. | "S" | "W" -> -1.
| _ -> failwith (Printf.sprintf "N or S expected for hemispere in dms, found '%s'" hemi) in
sign *. decimal (ios lat_d) (ios lat_m) (fos lat_s)
| [deg] -> float_of_string deg
| _ -> invalid_arg (Printf.sprintf "Latlong.of_string: %s" s)
let mercator_lat = fun l -> log (tan (pi/.4. +. 0.5*. l))
let inv_mercator_lat = fun l -> 2. *. atan (exp l) -. pi/.2.
let bearing = fun geo1 geo2 ->
let utm1 = utm_of WGS84 geo1
and utm2 = utm_of WGS84 geo2 in
let (dx, dy) = utm_sub utm2 utm1 in
((Rad>>Deg)(atan2 dx dy), sqrt(dx*.dx+.dy*.dy))
(** Offset between GPS and UTC times in seconds.
* Update when a new leap second is inserted and be careful about times in the
* past when this offset was different.
* Last leap second was inserted on June 30, 2012 at 23:59:60 UTC
* http://www.leapsecond.com/java/gpsclock.htm
*)
let leap_seconds = 16
(** Unix timestamp of the GPS epoch 1980-01-06 00:00:00 UTC *)
let gps_epoch = 315964800.
let gps_tow_of_utc = fun ?wday hour min sec ->
let wday =
match wday with
Some w -> w
| None -> (Unix.gmtime (Unix.gettimeofday ())).Unix.tm_wday in
((wday*24 + hour)*60+min)*60+sec + leap_seconds
let get_gps_tow = fun () ->
let utc = Unix.gmtime (Unix.gettimeofday ()) in
gps_tow_of_utc ~wday:utc.Unix.tm_wday utc.Unix.tm_hour utc.Unix.tm_min utc.Unix.tm_sec
let unix_time_of_tow = fun ?week tow ->
match week with
None ->
let host_tow = get_gps_tow ()
and unix_now = Unix.gettimeofday () in
unix_now +. float (tow - host_tow)
| Some w ->
gps_epoch
+. float w *. 60. *. 60. *. 24. *. 7.
+. float (tow - leap_seconds)
type coordinates_kind =
WGS84_dec
| WGS84_dms
| LBT2e
| Bearing of < pos : geographic>
let string_of_coordinates = fun kind geo ->
match kind with
WGS84_dec ->
string_degrees_of_geographic geo
| WGS84_dms ->
string_dms_of_geographic geo
| LBT2e ->
let l = lambertIIe_of geo in
Printf.sprintf "%d %d" l.lbt_x l.lbt_y
| Bearing georef ->
let (dx, dy) = utm_sub (utm_of WGS84 geo) (utm_of WGS84 georef#pos) in
let d = sqrt (dx*.dx+.dy*.dy) in
let bearing = (int_of_float ((Rad>>Deg)(atan2 dx dy)) + 360) mod 360 in
Printf.sprintf "%4d %4.0f" bearing d
let geographic_of_coordinates = fun kind s ->
match kind with
WGS84_dec ->
of_string ("WGS84 " ^ s)
| WGS84_dms ->
of_string ("WGS84_dms " ^ s)
| LBT2e ->
of_string ("LBT2e " ^ s)
| Bearing georef ->
of_string (Printf.sprintf "WGS84_bearing %s %s" (string_degrees_of_geographic georef#pos) s)
let fprint_utm = fun c utm ->
Printf.fprintf c "[%.2f %.2f %d]" utm.utm_x utm.utm_y utm.utm_zone
type ecef = float array
type ned = float array
let make_ecef = fun x -> x
let make_ned = fun x -> x
let array_of_ecef = fun x -> x
let array_of_ned = fun x -> x
let fprint_ecef = fun c x -> Printf.fprintf c "[%.2f %.2f %.2f]" x.(0) x.(1) x.(2)
let fprint_ned = fprint_ecef
let ecef_distance = fun e1 e2 ->
sqrt ((e1.(0)-.e2.(0))**2. +. (e1.(1)-.e2.(1))**2. +. (e1.(2)-.e2.(2))**2.)
let ecef_of_geo = fun geo ->
let elps = ellipsoid_of geo in
let e2 = 2.*.elps.df -. elps.df*.elps.df in
fun {posn_lat=lat; posn_long=long} h ->
let sin_lat = sin lat
and cos_lat = cos lat
and cos_long = cos long in
let chi = sqrt (1. -. e2*.sin_lat*.sin_lat) in
let x = (elps.a/.chi +.h)*.cos_lat*.cos_long
and y = (elps.a/.chi +.h)*.cos_lat*.sin long
and z = (elps.a*.(1.-.e2)/.chi +. h)*.sin_lat in
[|x; y; z|]
let geo_of_ecef = fun geo ->
let elps = ellipsoid_of geo in
let e2 = 2.*.elps.df -. elps.df*.elps.df
and ep2 = elps.df*.(2.-.elps.df)/.((1.-.elps.df)**2.)
and b = elps.a*.(1.-.elps.df) in
fun ecef ->
let x = ecef.(0) and y = ecef.(1) and z = ecef.(2) in
let z2 = z**2.
and r2 = x**2. +. y**2. in
let r = sqrt r2
and _E2 = elps.a**2. -. b**2.
and _F = 54.*.b**2.*.z2 in
let _G = r2 +. (1.-.e2)*.z2 -. e2*._E2 in
let _C = (e2*.e2*._F*.r2)/.(_G**3.) in
let _S = ( 1. +. _C +. sqrt (_C**2. +. 2.*._C))**(1./.3.) in
let _P = _F/.(3.*.(_S +. 1./._S +. 1.)**2. *. _G**2.) in
let _Q = sqrt (1.+.2.*.e2*.e2*._P) in
let r0 = -. (e2*._P*.r)/.(1.+._Q) +. sqrt ((elps.a**2./.2.)*.(1. +. 1./._Q) -. ((1.-.e2)*._P*.z2)/.(_Q*.(1.+._Q)) -. _P*.r2/.2.) in
let tmp = (r -. e2*.r0)**2. in
let _U = sqrt (tmp +. z2)
and _V = sqrt (tmp +. (1.-.e2)*.z2) in
let z0 = (b**2.*.z)/.(elps.a *. _V) in
let h = _U*.(1. -. b**2./.(elps.a *. _V))
and phi = atan ((z +. ep2*.z0)/.r )
and lambda = atan2 y x in
({posn_lat = phi; posn_long = lambda}, h)
(* http://en.wikipedia.org/wiki/Geodetic_system
Approximation using the geocentric latitude instead of the geodetic one *)
let ned_of_ecef = fun r ->
let wgs84, _h = geo_of_ecef WGS84 r in
let sin_lambda = sin wgs84.posn_long
and cos_lambda = cos wgs84.posn_long
and sin_phiP = sin wgs84.posn_lat
and cos_phiP = cos wgs84.posn_lat in
fun p ->
let x_xr = p.(0) -. r.(0)
and y_yr = p.(1) -. r.(1)
and z_zr = p.(2) -. r.(2) in
let e = -.sin_lambda*.x_xr +. cos_lambda*.y_yr
and n = -.sin_phiP*.cos_lambda*.x_xr -. sin_phiP*.sin_lambda*.y_yr +. cos_phiP*.z_zr
and u = cos_phiP*.cos_lambda*.x_xr +. cos_phiP*.sin_lambda*.y_yr +. sin_phiP*.z_zr in
[|n; e; -.u|]
let ecef_of_ned = fun r ->
let wgs84, _h = geo_of_ecef WGS84 r in
let sin_lambda = sin wgs84.posn_long
and cos_lambda = cos wgs84.posn_long
and sin_phiP = sin wgs84.posn_lat
and cos_phiP = cos wgs84.posn_lat in
fun ned ->
let n = ned.(0) and e = ned.(1) and u = -.ned.(2) in
let x = -.sin_lambda*.e -. cos_lambda*.sin_phiP*.n +. cos_lambda*.cos_phiP*.u +. r.(0)
and y = cos_lambda*.e -. sin_lambda*.sin_phiP*.n +. cos_phiP*.sin_lambda*.u +. r.(1)
and z = cos_phiP*.n +. sin_phiP*.u +. r.(2) in
[|x; y; z|]
(** From gpsd geoid.c *)
let bilinear = fun x1 y1 x2 y2 x y z11 z12 z21 z22 ->
match x1 = x2, y1 = y2 with (* Check for exact grid points *)
true, true -> z11
| true, false -> (z22*.(x-.x1)+.z11*.(x2-.x))/.(x2-.x1)
| false, true -> (z22*.(y-.y1)+.z11*.(y2-.y))/.(y2-.y1)
| false, false ->
let delta = (y2-.y1)*.(x2-.x1) in
(z22*.(y-.y1)*.(x-.x1)+.z12*.(y2-.y)*.(x-.x1)+.z21*.(y-.y1)*.(x2-.x)+.z11*.(y2-.y)*.(x2-.x))/.delta
(** From gpsd geoid.c
return geoid separtion (MSL-WGS84) in meters,given geographic coordinates*)
let geoid_data =
[| (* 90S *) [|-30;-30;-30;-30;-30;-30;-30;-30;-30;-30;-30;-30;-30;-30;-30;-30;-30;-30;-30;-30;-30;-30;-30;-30;-30;-30; -30;-30;-30;-30;-30;-30;-30;-30;-30;-30;-30|];
(* 80S *) [|-53;-54;-55;-52;-48;-42;-38;-38;-29;-26;-26;-24;-23;-21;-19;-16;-12; -8; -4; -1; 1; 4; 4; 6; 5; 4; 2; -6;-15;-24;-33;-40;-48;-50;-53;-52;-53|];
(* 70S *) [|-61;-60;-61;-55;-49;-44;-38;-31;-25;-16; -6; 1; 4; 5; 4; 2; 6; 12; 16; 16; 17; 21; 20; 26; 26; 22; 16; 10; -1;-16;-29;-36;-46;-55;-54;-59;-61|];
(* 60S *) [|-45;-43;-37;-32;-30;-26;-23;-22;-16;-10; -2; 10; 20; 20; 21; 24; 22; 17; 16; 19; 25; 30; 35; 35; 33; 30; 27; 10; -2;-14;-23;-30;-33;-29;-35;-43;-45|];
(* 50S *) [|-15;-18;-18;-16;-17;-15;-10;-10; -8; -2; 6; 14; 13; 3; 3; 10; 20; 27; 25; 26; 34; 39; 45; 45; 38; 39; 28; 13; -1;-15;-22;-22;-18;-15;-14;-10;-15|];
(* 40S *) [| 21; 6; 1; -7;-12;-12;-12;-10; -7; -1; 8; 23; 15; -2; -6; 6; 21; 24; 18; 26; 31; 33; 39; 41; 30; 24; 13; -2;-20;-32;-33;-27;-14; -2; 5; 20; 21|];
(* 30S *) [| 46; 22; 5; -2; -8;-13;-10; -7; -4; 1; 9; 32; 16; 4; -8; 4; 12; 15; 22; 27; 34; 29; 14; 15; 15; 7; -9;-25;-37;-39;-23;-14; 15; 33; 34; 45; 46|];
(* 20S *) [| 51; 27; 10; 0; -9;-11; -5; -2; -3; -1; 9; 35; 20; -5; -6; -5; 0; 13; 17; 23; 21; 8; -9;-10;-11;-20; -40;-47;-45;-25; 5; 23; 45; 58; 57; 63; 51|];
(* 10S *) [| 36; 22; 11; 6; -1; -8;-10; -8;-11; -9; 1; 32; 4;-18;-13; -9; 4; 14; 12; 13; -2;-14;-25;-32;-38;-60; -75;-63;-26; 0; 35; 52; 68; 76; 64; 52; 36|];
(* 00N *) [| 22; 16; 17; 13; 1;-12;-23;-20;-14; -3; 14; 10;-15;-27;-18; 3; 12; 20; 18; 12;-13; -9;-28;-49;-62;-89;-102;-63; -9; 33; 58; 73; 74; 63; 50; 32; 22|];
(* 10N *) [| 13; 12; 11; 2;-11;-28;-38;-29;-10; 3; 1;-11;-41;-42;-16; 3; 17; 33; 22; 23; 2; -3; -7;-36;-59;-90; -95;-63;-24; 12; 53; 60; 58; 46; 36; 26; 13|];
(* 20N *) [| 5; 10; 7; -7;-23;-39;-47;-34; -9;-10;-20;-45;-48;-32; -9; 17; 25; 31; 31; 26; 15; 6; 1;-29;-44;-61; -67;-59;-36;-11; 21; 39; 49; 39; 22; 10; 5|];
(* 30N *) [| -7; -5; -8;-15;-28;-40;-42;-29;-22;-26;-32;-51;-40;-17; 17; 31; 34; 44; 36; 28; 29; 17; 12;-20;-15;-40; -33;-34;-34;-28; 7; 29; 43; 20; 4; -6; -7|];
(* 40N *) [|-12;-10;-13;-20;-31;-34;-21;-16;-26;-34;-33;-35;-26; 2; 33; 59; 52; 51; 52; 48; 35; 40; 33; -9;-28;-39; -48;-59;-50;-28; 3; 23; 37; 18; -1;-11;-12|];
(* 50N *) [| -8; 8; 8; 1;-11;-19;-16;-18;-22;-35;-40;-26;-12; 24; 45; 63; 62; 59; 47; 48; 42; 28; 12;-10;-19;-33; -43;-42;-43;-29; -2; 17; 23; 22; 6; 2; -8|];
(* 60N *) [| 2; 9; 17; 10; 13; 1;-14;-30;-39;-46;-42;-21; 6; 29; 49; 65; 60; 57; 47; 41; 21; 18; 14; 7; -3;-22; -29;-32;-32;-26;-15; -2; 13; 17; 19; 6; 2|];
(* 70N *) [| 2; 2; 1; -1; -3; -7;-14;-24;-27;-25;-19; 3; 24; 37; 47; 60; 61; 58; 51; 43; 29; 20; 12; 5; -2;-10; -14;-12;-10;-14;-12; -6; -2; 3; 6; 4; 2|];
(* 80N *) [| 3; 1; -2; -3; -3; -3; -1; 3; 1; 5; 9; 11; 19; 27; 31; 34; 33; 34; 33; 34; 28; 23; 17; 13; 9; 4; 4; 1; -2; -2; 0; 2; 3; 2; 1; 1; 3|];
(* 90N *) [| 13; 13; 13; 13; 13; 13; 13; 13; 13; 13; 13; 13; 13; 13; 13; 13; 13; 13; 13; 13; 13; 13; 13; 13; 13; 13; 13; 13; 13; 13; 13; 13; 13; 13; 13; 13; 13|]|]
(* Online geoid calculator :
http://earth-info.nga.mil/GandG/wgs84/gravitymod/egm96/intpt.html
lat lon EGM96 this function
41. 1. 49.83 51.15
35 -140 -32.53 -29.5
56 6 41.82 45.08
77 13 37.71 34.18
-35 144 8.15 6.5
*)
let wgs84_hmsl = fun geo ->
let n_rows = Array.length geoid_data
and n_cols = Array.length geoid_data.(0) in
let lat = truncate ((Rad>>Deg) (norm_angle geo.posn_lat))
and lon = truncate ((Rad>>Deg) (norm_angle geo.posn_long)) in
let ilat = (lat + 90) / 10
and ilon = (lon + 180) / 10 in
assert(ilat < n_rows-1);
assert(ilon < n_cols-1);
let ilat1 = ilat and ilon1 = ilon
and ilat2 = ilat+1 and ilon2 = ilon+1 in
bilinear
(float (ilon1*10-180)) (float (ilat1*10-90))
(float (ilon2*10-180)) (float (ilat2*10-90))
(float lon) (float lat)
(float geoid_data.(ilat1).(ilon1))
(float geoid_data.(ilat1).(ilon2))
(float geoid_data.(ilat2).(ilon1))
(float geoid_data.(ilat2).(ilon2))
let wgs84_distance = fun geo1 geo2 ->
let e1 = ecef_of_geo WGS84 geo1 0.
and e2 = ecef_of_geo WGS84 geo1 0. in
ecef_distance e1 e2