(** Geographic vector module *) type t = { dx : float; (* km *) dy : float; (* km *) } let deg_to_rad = Float.pi /. 180.0 let rad_to_deg = 180.0 /. Float.pi let create_cartesian ~dx ~dy = { dx; dy } let create_polar ~heading ~distance = (* Convert heading to mathematical angle (east = 0, counterclockwise) *) let angle = (90.0 -. heading) *. deg_to_rad in { dx = distance *. cos angle; dy = distance *. sin angle; } let between p1 p2 = (* Calculate vector between two coordinates *) let dist = Coord.distance_haversine p1 p2 in let bearing = Coord.bearing p1 p2 in create_polar ~heading:bearing ~distance:dist let dx t = t.dx let dy t = t.dy let heading t = (* Convert from mathematical angle to geographic heading *) let angle = atan2 t.dy t.dx *. rad_to_deg in mod_float (90.0 -. angle +. 360.0) 360.0 let distance t = sqrt (t.dx ** 2.0 +. t.dy ** 2.0) let add v1 v2 = { dx = v1.dx +. v2.dx; dy = v1.dy +. v2.dy; } let sub v1 v2 = { dx = v1.dx -. v2.dx; dy = v1.dy -. v2.dy; } let scale v factor = { dx = v.dx *. factor; dy = v.dy *. factor; } let neg v = { dx = -.v.dx; dy = -.v.dy; } let rotate v degrees = let angle = degrees *. deg_to_rad in let cos_a = cos angle in let sin_a = sin angle in { dx = v.dx *. cos_a -. v.dy *. sin_a; dy = v.dx *. sin_a +. v.dy *. cos_a; } let apply coord v = Coord.offset coord ~heading:(heading v) ~distance:(distance v) let to_string t = Printf.sprintf "Vector(heading=%.2f°, distance=%.2fkm)" (heading t) (distance t)