···3232 (* km, ISS-like altitude *)
3333 let v = 7.669 in
3434 (* km/s, circular velocity *)
3535- let pos = Kepler.Vec3.v r 0. 0. in
3636- let vel = Kepler.Vec3.v 0. (v *. cos incl) (v *. sin incl) in
3535+ let pos = Vec3.v r 0. 0. in
3636+ let vel = Vec3.v 0. (v *. cos incl) (v *. sin incl) in
3737 Globe.Satellite.v ~pos ~vel ~color ())
3838 colors
3939
···2929 && is_finite vy && is_finite vz
3030 && (px *. px) +. (py *. py) +. (pz *. pz) > 1e-10
3131 && (vx *. vx) +. (vy *. vy) +. (vz *. vz) > 1e-10
3232- then Some (Kepler.Vec3.v px py pz, Kepler.Vec3.v vx vy vz)
3232+ then Some (Vec3.v px py pz, Vec3.v vx vy vz)
3333 else None
34343535(** 1. Satellite.v must not crash on any valid state vector. *)
···7676 | Some (pos, vel) ->
7777 let sat = Satellite.v ~pos ~vel ~color:Color.cyan () in
7878 let period = Satellite.period sat in
7979- if (not (is_finite period)) || period < 1. then ()
7979+ let eccentricity = Satellite.eccentricity sat in
8080+ if
8181+ (not (is_finite period))
8282+ || period < 1.
8383+ || (not (is_finite eccentricity))
8484+ || eccentricity >= 1.
8585+ then ()
8086 else begin
8187 let frame_dt = period /. 60. in
8288 let prev = ref (Satellite.position_at sat ~dt:0.) in
···8894 let dy = p.y -. !prev.y in
8995 let dz = p.z -. !prev.z in
9096 let jump = sqrt ((dx *. dx) +. (dy *. dy) +. (dz *. dz)) in
9191- let radius = Math.Vec3.length !prev in
9292- let max_jump = Float.max 0.1 (radius *. 0.05) in
9797+ let radius =
9898+ Float.max (Math.Vec3.length !prev) (Math.Vec3.length p)
9999+ in
100100+ (* 60 samples/orbit gives a circular-orbit chord of ~0.105 * radius.
101101+ Allow extra headroom for eccentric bound orbits and numeric error. *)
102102+ let max_jump = Float.max 0.1 (radius *. 0.25) in
93103 assert (jump < max_jump)
94104 end;
95105 prev := p
···33 Converts astrodynamics position vectors (km) to GL coordinates where Earth
44 radius = 1.0 and Y axis points north. *)
5566-val of_kepler : Kepler.Vec3.t -> Math.Vec3.t
77-(** [of_kepler v] converts a Kepler position vector (km) to GL coordinates. *)
66+val of_vec3 : Vec3.t -> Math.Vec3.t
77+(** [of_vec3 v] converts a position vector (km, any frame) to GL coordinates. *)
8899-val of_coordinate : Coordinate.vec3 -> Math.Vec3.t
1010-(** [of_coordinate v] converts a {!Coordinate.vec3} to GL coordinates. *)
99+val of_kepler : Vec3.t -> Math.Vec3.t
1010+(** Alias for {!of_vec3}. *)
1111+1212+val of_coordinate : Vec3.t -> Math.Vec3.t
1313+(** Alias for {!of_vec3}. *)
11141215val of_astro : x:float -> y:float -> z:float -> Math.Vec3.t
1313-(** [of_astro ~x ~y ~z] maps (x, y, z) in km to GL coordinates. Prefer
1414- {!of_kepler} or {!of_coordinate} when you have a vector. *)
1616+(** [of_astro ~x ~y ~z] maps (x, y, z) in km to GL coordinates. *)
+18-16
lib/satellite.ml
···33 SPDX-License-Identifier: ISC
44 ---------------------------------------------------------------------------*)
5566-type propagator = dt:float -> Kepler.Vec3.t
66+type propagator = dt:float -> Vec3.t
7788type t = {
99 propagate : propagator;
···1212 epoch_unix : float;
1313 ghost_points : Math.Vec3.t option array;
1414 trail_length : int;
1515- pos : Kepler.Vec3.t;
1616- vel : Kepler.Vec3.t;
1515+ pos : Vec3.t;
1616+ vel : Vec3.t;
1717}
18181919(* ── Helpers ───────────────────────────────────────────────────────── *)
20202121-let safe_gl fallback (p : Kepler.Vec3.t) =
2121+let safe_gl fallback (p : Vec3.t) =
2222 if Float.is_finite p.x && Float.is_finite p.y && Float.is_finite p.z then
2323 Gl_coord.of_kepler p
2424 else fallback
···5757 let propagate ~dt = Kepler.Analytic.at_precomputed el ~dt in
5858 make ~propagate ~color ~epoch_unix ~period ~trail_length ~pos ~vel
59596060+let v = of_state_vector
6161+6062let of_tle ~tle ~state ~color ?(trail_length = 50) () =
6163 let epoch_unix = Sgp4.epoch_unix tle in
6264 let period = 2. *. Float.pi /. tle.no *. 60. in
···6466 (* Extract pos/vel at epoch *)
6567 let pos, vel =
6668 match Sgp4.propagate state tle 0. with
6767- | Ok (p, v) -> (Kepler.Vec3.v p.x p.y p.z, Kepler.Vec3.v v.vx v.vy v.vz)
6868- | Error _ -> (Kepler.Vec3.zero, Kepler.Vec3.zero)
6969+ | Ok (p, v) -> (Vec3.v p.x p.y p.z, Vec3.v v.vx v.vy v.vz)
7070+ | Error _ -> (Vec3.zero, Vec3.zero)
6971 in
7072 let propagate ~dt =
7173 let tsince = dt /. 60. in
7274 match Sgp4.propagate state tle tsince with
7373- | Ok (p, _v) -> Kepler.Vec3.v p.x p.y p.z
7575+ | Ok (p, _v) -> Vec3.v p.x p.y p.z
7476 | Error _ -> pos (* fallback to epoch position *)
7577 in
7678 make ~propagate ~color ~epoch_unix ~period ~trail_length ~pos ~vel
···7981 let n = Array.length points in
8082 if n = 0 then
8183 make
8282- ~propagate:(fun ~dt:_ -> Kepler.Vec3.zero)
8383- ~color ~epoch_unix:0. ~period:5400. ~trail_length ~pos:Kepler.Vec3.zero
8484- ~vel:Kepler.Vec3.zero
8484+ ~propagate:(fun ~dt:_ -> Vec3.zero)
8585+ ~color ~epoch_unix:0. ~period:5400. ~trail_length ~pos:Vec3.zero
8686+ ~vel:Vec3.zero
8587 else
8688 let mid = n / 2 in
8789 let epoch_unix, pos = points.(mid) in
···8991 let vel =
9092 if n >= 2 then
9193 let i0 = max 0 (mid - 1) and i1 = min (n - 1) (mid + 1) in
9292- let t0, (p0 : Kepler.Vec3.t) = points.(i0)
9393- and t1, (p1 : Kepler.Vec3.t) = points.(i1) in
9494+ let t0, (p0 : Vec3.t) = points.(i0)
9595+ and t1, (p1 : Vec3.t) = points.(i1) in
9496 let dt = t1 -. t0 in
9597 if dt > 0. then
9696- Kepler.Vec3.v
9898+ Vec3.v
9799 ((p1.x -. p0.x) /. dt)
98100 ((p1.y -. p0.y) /. dt)
99101 ((p1.z -. p0.z) /. dt)
100100- else Kepler.Vec3.zero
101101- else Kepler.Vec3.zero
102102+ else Vec3.zero
103103+ else Vec3.zero
102104 in
103105 (* Estimate period from data span *)
104106 let t_start = fst points.(0) and t_end = fst points.(n - 1) in
···124126 else
125127 let frac = (target -. t0) /. dt in
126128 let frac = Float.max 0. (Float.min 1. frac) in
127127- Kepler.Vec3.v
129129+ Vec3.v
128130 (p0.x +. (frac *. (p1.x -. p0.x)))
129131 (p0.y +. (frac *. (p1.y -. p0.y)))
130132 (p0.z +. (frac *. (p1.z -. p0.z)))
+15-5
lib/satellite.mli
···1717(** {1 Constructors} *)
18181919val of_state_vector :
2020- pos:Kepler.Vec3.t ->
2121- vel:Kepler.Vec3.t ->
2020+ pos:Vec3.t ->
2121+ vel:Vec3.t ->
2222 color:Color.t ->
2323 ?epoch_unix:float ->
2424 ?trail_length:int ->
···2727(** From a J2000 state vector. Uses Kepler two-body propagation. Accurate within
2828 ±1 orbit of epoch. *)
29293030+val v :
3131+ pos:Vec3.t ->
3232+ vel:Vec3.t ->
3333+ color:Color.t ->
3434+ ?epoch_unix:float ->
3535+ ?trail_length:int ->
3636+ unit ->
3737+ t
3838+(** Backward-compatible alias for {!of_state_vector}. *)
3939+3040val of_tle :
3141 tle:Sgp4.tle ->
3242 state:Sgp4.state ->
···3848 Epoch and period extracted from TLE. *)
39494050val of_ephemeris :
4141- points:(float * Kepler.Vec3.t) array ->
5151+ points:(float * Vec3.t) array ->
4252 color:Color.t ->
4353 ?trail_length:int ->
4454 unit ->
···5262val color : t -> Color.t
5363val epoch_unix : t -> float
5464val period : t -> float
5555-val pos : t -> Kepler.Vec3.t
5656-val vel : t -> Kepler.Vec3.t
6565+val pos : t -> Vec3.t
6666+val vel : t -> Vec3.t
57675868(** {1 Rendering} *)
5969
···2727 check_float "z" 1.0 gl.z
28282929let test_of_coordinate () =
3030- let v = Coordinate.vec3 6378.137 0. 6378.137 in
3030+ let v = Vec3.v 6378.137 0. 6378.137 in
3131 let gl = Gl_coord.of_coordinate v in
3232 check_float "x" 1.0 gl.x;
3333 check_float "y" 1.0 gl.y;