(** Geographic coordinate types with validation *) (** Private coordinate types with validation constraints *) type latitude = private float type longitude = private float type degrees = private float (** Coordinate pair - main type for this module *) type t = { lat : latitude; lon : longitude; } (** Smart constructors for validated coordinates *) let latitude f = if f >= -90.0 && f <= 90.0 then Ok (Obj.magic f : latitude) else Error (Printf.sprintf "Invalid latitude: %f (must be between -90.0 and 90.0)" f) let longitude f = if f >= -180.0 && f < 180.0 then Ok (Obj.magic f : longitude) else Error (Printf.sprintf "Invalid longitude: %f (must be between -180.0 and 180.0)" f) let degrees f = if f >= 0.0 && f < 360.0 then Ok (Obj.magic f : degrees) else Error (Printf.sprintf "Invalid degrees: %f (must be between 0.0 and 360.0)" f) (** Convert back to float *) let latitude_to_float (lat : latitude) = (lat :> float) let longitude_to_float (lon : longitude) = (lon :> float) let degrees_to_float (deg : degrees) = (deg :> float) (** Create coordinate pair *) let make lat lon = { lat; lon } (** Create coordinate pair from floats with validation *) let make_from_floats lat_f lon_f = match latitude lat_f, longitude lon_f with | Ok lat, Ok lon -> Ok { lat; lon } | Error e, _ | _, Error e -> Error e (** Extract components *) let lat t = t.lat let lon t = t.lon let to_floats t = (latitude_to_float t.lat, longitude_to_float t.lon) (** Compare coordinates *) let compare t1 t2 = let lat_cmp = Float.compare (latitude_to_float t1.lat) (latitude_to_float t2.lat) in if lat_cmp <> 0 then lat_cmp else Float.compare (longitude_to_float t1.lon) (longitude_to_float t2.lon) (** Equality *) let equal t1 t2 = compare t1 t2 = 0 (** Pretty printer *) let pp ppf t = Format.fprintf ppf "(%g, %g)" (latitude_to_float t.lat) (longitude_to_float t.lon)