My agentic slop goes here. Not intended for anyone else!
1(** Coordinate parsing module *)
2
3let parse_dms s =
4 let s = String.trim s in
5
6 (* Check for hemisphere markers *)
7 let has_s = String.contains s 'S' || String.contains s 's' in
8 let has_w = String.contains s 'W' || String.contains s 'w' in
9
10 let sign =
11 if has_s || has_w then -1.0
12 else 1.0 in
13
14 (* Remove hemisphere markers and clean up *)
15 let cleaned =
16 s
17 |> Str.global_replace (Str.regexp "[NnSsEeWw]") ""
18 |> String.trim in
19
20 (* Try to parse different formats *)
21 try
22 (* First try as simple decimal degrees *)
23 if not (String.contains cleaned (Char.chr 176) || (* degree symbol *)
24 String.contains cleaned '\'' ||
25 String.contains cleaned '"' ||
26 String.contains cleaned ' ' ||
27 String.contains cleaned ':') then
28 sign *. float_of_string cleaned
29 else
30 (* Parse DMS format *)
31 let deg_char = Char.chr 176 in (* degree symbol *)
32 let parts =
33 if String.contains cleaned deg_char then
34 (* Format: DD degree MM min SS sec or DD degree MM.MMM min *)
35 let deg_parts = String.split_on_char deg_char cleaned in
36 match deg_parts with
37 | [deg_str; rest] ->
38 let deg = float_of_string (String.trim deg_str) in
39 if String.contains rest '\'' then
40 let min_parts = String.split_on_char '\'' rest in
41 match min_parts with
42 | [min_str; sec_str] ->
43 let min = float_of_string (String.trim min_str) in
44 let sec_str = String.trim sec_str in
45 let sec_str =
46 if String.contains sec_str '"' then
47 String.sub sec_str 0 (String.index sec_str '"')
48 else sec_str in
49 let sec =
50 if sec_str = "" then 0.0
51 else float_of_string sec_str in
52 [deg; min; sec]
53 | [min_str] ->
54 let min = float_of_string (String.trim min_str) in
55 [deg; min; 0.0]
56 | _ -> raise (Error.Coordinate_error
57 (Error.Parse_error "Invalid DMS format"))
58 else
59 [deg; 0.0; 0.0]
60 | _ -> raise (Error.Coordinate_error
61 (Error.Parse_error "Invalid degree format"))
62 else if String.contains cleaned ':' then
63 (* Format: DD:MM:SS *)
64 cleaned
65 |> String.split_on_char ':'
66 |> List.map String.trim
67 |> List.map float_of_string
68 else if String.contains cleaned ' ' then
69 (* Format: DD MM SS *)
70 cleaned
71 |> String.split_on_char ' '
72 |> List.filter (fun s -> String.length s > 0)
73 |> List.map String.trim
74 |> List.map float_of_string
75 else
76 raise (Error.Coordinate_error
77 (Error.Parse_error "Unknown coordinate format"))
78 in
79
80 (* Convert to decimal degrees *)
81 match parts with
82 | [deg] -> sign *. deg
83 | [deg; min] -> sign *. (abs_float deg +. min /. 60.0)
84 | [deg; min; sec] ->
85 sign *. (abs_float deg +. min /. 60.0 +. sec /. 3600.0)
86 | _ -> raise (Error.Coordinate_error
87 (Error.Parse_error "Invalid number of components"))
88 with
89 | Failure _ -> raise (Error.Coordinate_error
90 (Error.Parse_error ("Failed to parse coordinate: " ^ s)))
91
92let parse_lat s =
93 let value = parse_dms s in
94 Lat.create value
95
96let parse_lon s =
97 let value = parse_dms s in
98 Lon.create value
99
100let parse_coord s =
101 let s = String.trim s in
102
103 (* Try comma-separated first *)
104 let parts =
105 if String.contains s ',' then
106 String.split_on_char ',' s
107 |> List.map String.trim
108 else
109 (* Try space-separated *)
110 s
111 |> String.split_on_char ' '
112 |> List.filter (fun s -> String.length s > 0)
113 |> List.map String.trim
114 in
115
116 match parts with
117 | [lat_str; lon_str] ->
118 let lat = parse_dms lat_str in
119 let lon = parse_dms lon_str in
120 Coord.create ~lat ~lon
121 | _ -> raise (Error.Coordinate_error
122 (Error.Parse_error "Expected lat,lon or lat lon format"))
123
124let try_parse_lat s =
125 try Some (parse_lat s)
126 with Error.Coordinate_error _ -> None
127
128let try_parse_lon s =
129 try Some (parse_lon s)
130 with Error.Coordinate_error _ -> None
131
132let try_parse_coord s =
133 try Some (parse_coord s)
134 with Error.Coordinate_error _ -> None