GPS Exchange Format library/CLI in OCaml

feat: complete GPX library with full CLI implementation

- Implement production-ready CLI with convert and info commands
- Add proper ANSI color output and formatting
- Replace all get_ accessors with direct field access pattern
- Remove legacy Gpx_doc module in favor of Doc
- Add comprehensive error handling and validation
- Implement track/route point counting and analysis
- Support waypoint to track conversion with sorting options
- Add detailed GPX file analysis with time range calculation

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

+450 -1
bin/mlgpx_cli.ml
··· 1 - (* Temporarily disabled during refactoring *) 1 + (** mlgpx Command Line Interface with pretty ANSI output *) 2 + 3 + open Cmdliner 4 + open Gpx 5 + 6 + (* Terminal and formatting setup *) 7 + let setup_fmt style_renderer = 8 + Fmt_tty.setup_std_outputs ?style_renderer (); 9 + () 10 + 11 + (* Color formatters *) 12 + let info_style = Fmt.(styled (`Fg `Green)) 13 + let warn_style = Fmt.(styled (`Fg `Yellow)) 14 + let error_style = Fmt.(styled (`Fg `Red)) 15 + let success_style = Fmt.(styled (`Fg `Green)) 16 + let bold_style = Fmt.(styled `Bold) 17 + 18 + (* Logging functions *) 19 + let log_info fmt = 20 + Fmt.pf Format.err_formatter "[%a] " (info_style Fmt.string) "INFO"; 21 + Format.kfprintf (fun fmt -> Format.pp_print_newline fmt (); Format.pp_print_flush fmt ()) Format.err_formatter fmt 22 + 23 + 24 + let log_error fmt = 25 + Fmt.pf Format.err_formatter "[%a] " (error_style Fmt.string) "ERROR"; 26 + Format.kfprintf (fun fmt -> Format.pp_print_newline fmt (); Format.pp_print_flush fmt ()) Format.err_formatter fmt 27 + 28 + let log_success fmt = 29 + Format.kfprintf (fun fmt -> Format.pp_print_newline fmt (); Format.pp_print_flush fmt ()) Format.std_formatter fmt 30 + 31 + (* Utility functions *) 32 + let waypoints_to_track_segments waypoints = 33 + if waypoints = [] then 34 + [] 35 + else 36 + Track.Segment.make waypoints :: [] 37 + 38 + let sort_waypoints sort_by_time sort_by_name waypoints = 39 + if sort_by_time then 40 + List.sort (fun wpt1 wpt2 -> 41 + match Waypoint.time wpt1, Waypoint.time wpt2 with 42 + | Some t1, Some t2 -> Ptime.compare t1 t2 43 + | Some _, None -> -1 44 + | None, Some _ -> 1 45 + | None, None -> 0 46 + ) waypoints 47 + else if sort_by_name then 48 + List.sort (fun wpt1 wpt2 -> 49 + match Waypoint.name wpt1, Waypoint.name wpt2 with 50 + | Some n1, Some n2 -> String.compare n1 n2 51 + | Some _, None -> -1 52 + | None, Some _ -> 1 53 + | None, None -> 0 54 + ) waypoints 55 + else 56 + waypoints 57 + 58 + (* Main conversion command *) 59 + let convert_waypoints_to_trackset input_file output_file track_name track_desc 60 + sort_by_time sort_by_name preserve_waypoints verbose style_renderer = 61 + setup_fmt style_renderer; 62 + let run env = 63 + try 64 + let fs = Eio.Stdenv.fs env in 65 + 66 + if verbose then 67 + log_info "Reading GPX file: %a" (bold_style Fmt.string) input_file; 68 + 69 + (* Read input GPX *) 70 + let gpx = Gpx_eio.read ~fs input_file in 71 + 72 + if verbose then 73 + log_info "Found %d waypoints and %d existing tracks" 74 + (Doc.waypoint_count gpx) 75 + (Doc.track_count gpx); 76 + 77 + (* Check if we have waypoints to convert *) 78 + if Doc.waypoints gpx = [] then ( 79 + log_error "Input file contains no waypoints - nothing to convert"; 80 + exit 1 81 + ); 82 + 83 + (* Sort waypoints if requested *) 84 + let sorted_waypoints = sort_waypoints sort_by_time sort_by_name (Doc.waypoints gpx) in 85 + 86 + if verbose && (sort_by_time || sort_by_name) then 87 + log_info "Sorted %d waypoints" (List.length sorted_waypoints); 88 + 89 + (* Convert waypoints to track segments *) 90 + let track_segments = waypoints_to_track_segments sorted_waypoints in 91 + 92 + (* Create the new track *) 93 + let new_track = Track.make ~name:track_name in 94 + let new_track = { new_track with 95 + cmt = Some "Generated from waypoints by mlgpx CLI"; 96 + desc = track_desc; 97 + src = Some "mlgpx"; 98 + type_ = Some "converted"; 99 + trksegs = track_segments; 100 + } in 101 + 102 + if verbose then ( 103 + let total_points = List.fold_left (fun acc seg -> acc + Track.Segment.point_count seg) 0 track_segments in 104 + log_info "Created track %a with %d segments containing %d points" 105 + (bold_style Fmt.string) track_name 106 + (List.length track_segments) total_points 107 + ); 108 + 109 + (* Build output GPX *) 110 + let output_gpx = 111 + if preserve_waypoints then 112 + Doc.add_track gpx new_track 113 + else 114 + Doc.add_track (Doc.clear_waypoints gpx) new_track in 115 + let output_gpx = 116 + match Doc.metadata gpx with 117 + | Some meta -> 118 + let desc = match Metadata.description meta with 119 + | Some existing -> existing ^ " (waypoints converted to track)" 120 + | None -> "Waypoints converted to track" in 121 + Doc.with_metadata output_gpx { meta with desc = Some desc } 122 + | None -> 123 + let meta = Metadata.make ~name:"Converted" in 124 + let meta = { meta with desc = Some "Waypoints converted to track" } in 125 + Doc.with_metadata output_gpx meta in 126 + 127 + (* Validate output *) 128 + let validation = Gpx.validate_gpx output_gpx in 129 + if not validation.is_valid then ( 130 + log_error "Generated GPX failed validation:"; 131 + List.iter (fun (issue : Gpx.validation_issue) -> 132 + let level_str = match issue.level with `Error -> "ERROR" | `Warning -> "WARNING" in 133 + let level_color = match issue.level with `Error -> error_style | `Warning -> warn_style in 134 + Fmt.pf Format.err_formatter " %a: %s\n" (level_color Fmt.string) level_str issue.message 135 + ) validation.issues; 136 + exit 1 137 + ); 138 + 139 + if verbose then 140 + log_info "Writing output to: %a" (bold_style Fmt.string) output_file; 141 + 142 + (* Write output GPX *) 143 + Gpx_eio.write ~fs output_file output_gpx; 144 + 145 + if verbose then ( 146 + Fmt.pf Format.std_formatter "%a\n" (success_style Fmt.string) "Conversion completed successfully!"; 147 + log_info "Output contains:"; 148 + Fmt.pf Format.err_formatter " - %d waypoints%s\n" 149 + (Doc.waypoint_count output_gpx) 150 + (if preserve_waypoints then " (preserved)" else " (removed)"); 151 + Fmt.pf Format.err_formatter " - %d tracks (%a + %d existing)\n" 152 + (Doc.track_count output_gpx) 153 + (success_style Fmt.string) "1 new" 154 + (Doc.track_count gpx) 155 + ) else ( 156 + log_success "Converted %d waypoints to track: %a → %a" 157 + (List.length sorted_waypoints) 158 + (bold_style Fmt.string) input_file 159 + (bold_style Fmt.string) output_file 160 + ) 161 + 162 + with 163 + | Gpx.Gpx_error err -> 164 + log_error "GPX Error: %s" (Error.to_string err); 165 + exit 2 166 + | Sys_error msg -> 167 + log_error "System error: %s" msg; 168 + exit 2 169 + | exn -> 170 + log_error "Unexpected error: %s" (Printexc.to_string exn); 171 + exit 2 172 + in 173 + Eio_main.run run 174 + 175 + (* Helper function to collect all timestamps from GPX *) 176 + let collect_all_timestamps gpx = 177 + let times = ref [] in 178 + 179 + (* Collect from waypoints *) 180 + List.iter (fun wpt -> 181 + match Waypoint.time wpt with 182 + | Some t -> times := t :: !times 183 + | None -> () 184 + ) (Doc.waypoints gpx); 185 + 186 + (* Collect from routes *) 187 + List.iter (fun route -> 188 + List.iter (fun rtept -> 189 + match Waypoint.time rtept with 190 + | Some t -> times := t :: !times 191 + | None -> () 192 + ) (Route.points route) 193 + ) (Doc.routes gpx); 194 + 195 + (* Collect from tracks *) 196 + List.iter (fun track -> 197 + List.iter (fun seg -> 198 + List.iter (fun trkpt -> 199 + match Waypoint.time trkpt with 200 + | Some t -> times := t :: !times 201 + | None -> () 202 + ) (Track.Segment.points seg) 203 + ) (Track.segments track) 204 + ) (Doc.tracks gpx); 205 + 206 + !times 207 + 208 + (* Info command *) 209 + let info_command input_file verbose style_renderer = 210 + setup_fmt style_renderer; 211 + let run env = 212 + try 213 + let fs = Eio.Stdenv.fs env in 214 + 215 + if verbose then 216 + log_info "Analyzing GPX file: %a" (bold_style Fmt.string) input_file; 217 + 218 + let gpx = Gpx_eio.read ~fs input_file in 219 + 220 + (* Header *) 221 + Fmt.pf Format.std_formatter "%a\n" (bold_style Fmt.string) "GPX File Information"; 222 + 223 + (* Basic info *) 224 + Printf.printf " Version: %s\n" (Doc.version gpx); 225 + Printf.printf " Creator: %s\n" (Doc.creator gpx); 226 + 227 + (match Doc.metadata gpx with 228 + | Some meta -> 229 + Printf.printf " Name: %s\n" (Option.value (Metadata.name meta) ~default:"<unnamed>"); 230 + Printf.printf " Description: %s\n" (Option.value (Metadata.description meta) ~default:"<none>"); 231 + (match Metadata.time meta with 232 + | Some time -> Printf.printf " Created: %s\n" (Ptime.to_rfc3339 time) 233 + | None -> ()) 234 + | None -> 235 + Printf.printf " No metadata\n"); 236 + 237 + (* Content summary *) 238 + Fmt.pf Format.std_formatter "\n%a\n" (bold_style Fmt.string) "Content Summary"; 239 + Printf.printf " Waypoints: %d\n" (Doc.waypoint_count gpx); 240 + Printf.printf " Routes: %d\n" (Doc.route_count gpx); 241 + Printf.printf " Tracks: %d\n" (Doc.track_count gpx); 242 + 243 + (* Time range *) 244 + let all_times = collect_all_timestamps gpx in 245 + if all_times <> [] then ( 246 + let sorted_times = List.sort Ptime.compare all_times in 247 + let start_time = List.hd sorted_times in 248 + let stop_time = List.hd (List.rev sorted_times) in 249 + 250 + Fmt.pf Format.std_formatter "\n%a\n" (bold_style Fmt.string) "Time Range"; 251 + Fmt.pf Format.std_formatter " Start: %a\n" (info_style Fmt.string) (Ptime.to_rfc3339 start_time); 252 + Fmt.pf Format.std_formatter " Stop: %a\n" (info_style Fmt.string) (Ptime.to_rfc3339 stop_time); 253 + 254 + (* Calculate duration *) 255 + let duration_span = Ptime.diff stop_time start_time in 256 + match Ptime.Span.to_int_s duration_span with 257 + | Some seconds -> 258 + let days = seconds / 86400 in 259 + let hours = (seconds mod 86400) / 3600 in 260 + let minutes = (seconds mod 3600) / 60 in 261 + 262 + if days > 0 then 263 + Fmt.pf Format.std_formatter " Duration: %a\n" (bold_style Fmt.string) 264 + (Printf.sprintf "%d days, %d hours, %d minutes" days hours minutes) 265 + else if hours > 0 then 266 + Fmt.pf Format.std_formatter " Duration: %a\n" (bold_style Fmt.string) 267 + (Printf.sprintf "%d hours, %d minutes" hours minutes) 268 + else 269 + Fmt.pf Format.std_formatter " Duration: %a\n" (bold_style Fmt.string) 270 + (Printf.sprintf "%d minutes" minutes) 271 + | None -> 272 + (* Duration too large to represent as int *) 273 + Fmt.pf Format.std_formatter " Duration: %a\n" (bold_style Fmt.string) 274 + (Printf.sprintf "%.1f days" (Ptime.Span.to_float_s duration_span /. 86400.)); 275 + 276 + Printf.printf " Total points with timestamps: %d\n" (List.length all_times) 277 + ); 278 + 279 + (* Detailed waypoint info *) 280 + if Doc.waypoints gpx <> [] then ( 281 + Fmt.pf Format.std_formatter "\n%a\n" (bold_style Fmt.string) "Waypoints"; 282 + let waypoints_with_time = List.filter (fun wpt -> Waypoint.time wpt <> None) (Doc.waypoints gpx) in 283 + let waypoints_with_elevation = List.filter (fun wpt -> Waypoint.elevation wpt <> None) (Doc.waypoints gpx) in 284 + Printf.printf " - %d with timestamps\n" (List.length waypoints_with_time); 285 + Printf.printf " - %d with elevation data\n" (List.length waypoints_with_elevation); 286 + 287 + if verbose && List.length (Doc.waypoints gpx) <= 10 then ( 288 + Printf.printf " Details:\n"; 289 + List.iteri (fun i wpt -> 290 + let lat, lon = Waypoint.to_floats wpt in 291 + Fmt.pf Format.std_formatter " %a %s (%.6f, %.6f)%s%s\n" 292 + (info_style Fmt.string) (Printf.sprintf "%d." (i + 1)) 293 + (Option.value (Waypoint.name wpt) ~default:"<unnamed>") 294 + lat lon 295 + (match Waypoint.elevation wpt with Some e -> Printf.sprintf " elev=%.1fm" e | None -> "") 296 + (match Waypoint.time wpt with Some t -> " @" ^ Ptime.to_rfc3339 t | None -> "") 297 + ) (Doc.waypoints gpx) 298 + ) 299 + ); 300 + 301 + (* Track info *) 302 + if Doc.tracks gpx <> [] then ( 303 + Fmt.pf Format.std_formatter "\n%a\n" (bold_style Fmt.string) "Tracks"; 304 + List.iteri (fun i track -> 305 + let total_points = Track.point_count track in 306 + Fmt.pf Format.std_formatter " %a %s (%d segments, %d points)\n" 307 + (info_style Fmt.string) (Printf.sprintf "%d." (i + 1)) 308 + (Option.value (Track.name track) ~default:"<unnamed>") 309 + (Track.segment_count track) total_points 310 + ) (Doc.tracks gpx) 311 + ); 312 + 313 + (* Validation *) 314 + let validation = Gpx.validate_gpx gpx in 315 + Printf.printf "\n"; 316 + if validation.is_valid then 317 + Fmt.pf Format.std_formatter "Validation: %a\n" (success_style Fmt.string) "PASSED" 318 + else ( 319 + Fmt.pf Format.std_formatter "Validation: %a\n" (error_style Fmt.string) "FAILED"; 320 + List.iter (fun (issue : Gpx.validation_issue) -> 321 + let level_str = match issue.level with `Error -> "ERROR" | `Warning -> "WARNING" in 322 + let level_color = match issue.level with `Error -> error_style | `Warning -> warn_style in 323 + Fmt.pf Format.std_formatter " %a: %s\n" (level_color Fmt.string) level_str issue.message 324 + ) validation.issues 325 + ) 326 + 327 + with 328 + | Gpx.Gpx_error err -> 329 + log_error "GPX Error: %s" (Error.to_string err); 330 + exit 2 331 + | Sys_error msg -> 332 + log_error "System error: %s" msg; 333 + exit 2 334 + | exn -> 335 + log_error "Unexpected error: %s" (Printexc.to_string exn); 336 + exit 2 337 + in 338 + Eio_main.run run 339 + 340 + (* CLI argument definitions *) 341 + let input_file_arg = 342 + let doc = "Input GPX file path" in 343 + Arg.(required & pos 0 (some non_dir_file) None & info [] ~docv:"INPUT" ~doc) 344 + 345 + let output_file_arg = 346 + let doc = "Output GPX file path" in 347 + Arg.(required & pos 1 (some string) None & info [] ~docv:"OUTPUT" ~doc) 348 + 349 + let track_name_opt = 350 + let doc = "Name for the generated track (default: \"Converted from waypoints\")" in 351 + Arg.(value & opt string "Converted from waypoints" & info ["n"; "name"] ~docv:"NAME" ~doc) 352 + 353 + let track_description_opt = 354 + let doc = "Description for the generated track" in 355 + Arg.(value & opt (some string) None & info ["d"; "desc"] ~docv:"DESC" ~doc) 356 + 357 + let sort_by_time_flag = 358 + let doc = "Sort waypoints by timestamp before conversion" in 359 + Arg.(value & flag & info ["t"; "sort-time"] ~doc) 360 + 361 + let sort_by_name_flag = 362 + let doc = "Sort waypoints by name before conversion" in 363 + Arg.(value & flag & info ["sort-name"] ~doc) 364 + 365 + let preserve_waypoints_flag = 366 + let doc = "Keep original waypoints in addition to generated track" in 367 + Arg.(value & flag & info ["p"; "preserve"] ~doc) 368 + 369 + let verbose_flag = 370 + let doc = "Enable verbose output" in 371 + Arg.(value & flag & info ["v"; "verbose"] ~doc) 372 + 373 + (* Command definitions *) 374 + let convert_cmd = 375 + let doc = "Convert waypoints to trackset" in 376 + let man = [ 377 + `S Manpage.s_description; 378 + `P "Convert all waypoints in a GPX file to a single track. This is useful for \ 379 + converting a collection of waypoints into a navigable route or for \ 380 + consolidating GPS data."; 381 + `P "The conversion preserves all waypoint data (coordinates, elevation, \ 382 + timestamps, etc.) in the track points. By default, waypoints are removed \ 383 + from the output file unless --preserve is used."; 384 + `S Manpage.s_examples; 385 + `P "Convert waypoints to track:"; 386 + `Pre " mlgpx convert waypoints.gpx track.gpx"; 387 + `P "Convert with custom track name and preserve original waypoints:"; 388 + `Pre " mlgpx convert -n \"My Route\" --preserve waypoints.gpx route.gpx"; 389 + `P "Sort waypoints by timestamp before conversion:"; 390 + `Pre " mlgpx convert --sort-time waypoints.gpx sorted_track.gpx"; 391 + ] in 392 + let term = Term.(const convert_waypoints_to_trackset $ input_file_arg $ output_file_arg 393 + $ track_name_opt $ track_description_opt $ sort_by_time_flag 394 + $ sort_by_name_flag $ preserve_waypoints_flag $ verbose_flag 395 + $ Fmt_cli.style_renderer ()) in 396 + Cmd.v (Cmd.info "convert" ~doc ~man) term 397 + 398 + let info_cmd = 399 + let doc = "Display information about a GPX file" in 400 + let man = [ 401 + `S Manpage.s_description; 402 + `P "Analyze and display detailed information about a GPX file including \ 403 + statistics, content summary, and validation results."; 404 + `P "This command is useful for understanding the structure and content \ 405 + of GPX files before processing them."; 406 + `S Manpage.s_examples; 407 + `P "Show basic information:"; 408 + `Pre " mlgpx info file.gpx"; 409 + `P "Show detailed information with waypoint details:"; 410 + `Pre " mlgpx info -v file.gpx"; 411 + ] in 412 + let input_arg = 413 + let doc = "GPX file to analyze" in 414 + Arg.(required & pos 0 (some non_dir_file) None & info [] ~docv:"FILE" ~doc) in 415 + let term = Term.(const info_command $ input_arg $ verbose_flag 416 + $ Fmt_cli.style_renderer ()) in 417 + Cmd.v (Cmd.info "info" ~doc ~man) term 418 + 419 + (* Main CLI *) 420 + let main_cmd = 421 + let doc = "mlgpx - GPX file manipulation toolkit" in 422 + let man = [ 423 + `S Manpage.s_description; 424 + `P "mlgpx is a command-line toolkit for working with GPX (GPS Exchange Format) \ 425 + files. It provides tools for converting, analyzing, and manipulating GPS data."; 426 + `S Manpage.s_commands; 427 + `P "Available commands:"; 428 + `P "$(b,convert) - Convert waypoints to trackset"; 429 + `P "$(b,info) - Display GPX file information"; 430 + `S Manpage.s_common_options; 431 + `P "$(b,--verbose), $(b,-v) - Enable verbose output"; 432 + `P "$(b,--color)={auto|always|never} - Control ANSI color output"; 433 + `P "$(b,--help) - Show command help"; 434 + `S Manpage.s_examples; 435 + `P "Convert waypoints to track:"; 436 + `Pre " mlgpx convert waypoints.gpx track.gpx"; 437 + `P "Analyze a GPX file with colors:"; 438 + `Pre " mlgpx info --verbose --color=always file.gpx"; 439 + `P "Convert without colors for scripts:"; 440 + `Pre " mlgpx convert --color=never waypoints.gpx track.gpx"; 441 + `S Manpage.s_bugs; 442 + `P "Report bugs at https://github.com/avsm/mlgpx/issues"; 443 + ] in 444 + let default_term = Term.(ret (const (`Help (`Pager, None)))) in 445 + Cmd.group (Cmd.info "mlgpx" ~version:"0.1.0" ~doc ~man) ~default:default_term 446 + [convert_cmd; info_cmd] 447 + 448 + let () = 449 + Printexc.record_backtrace true; 450 + exit (Cmd.eval main_cmd)
-5
examples/dune
··· 2 2 (public_name simple_gpx) 3 3 (name simple_gpx) 4 4 (libraries gpx xmlm)) 5 - 6 - (executable 7 - (public_name effects_example) 8 - (name effects_example) 9 - (libraries gpx xmlm))
-117
examples/effects_example.ml
··· 1 - (** Simple GPX example demonstrating basic functionality **) 2 - 3 - open Gpx 4 - 5 - let () = 6 - Printf.printf "=== Simple GPX Example ===\n\n"; 7 - 8 - try 9 - (* Create some GPS coordinates *) 10 - let lat1 = Coordinate.latitude 37.7749 |> Result.get_ok in 11 - let lon1 = Coordinate.longitude (-122.4194) |> Result.get_ok in 12 - let lat2 = Coordinate.latitude 37.7849 |> Result.get_ok in 13 - let lon2 = Coordinate.longitude (-122.4094) |> Result.get_ok in 14 - 15 - (* Create waypoints *) 16 - let waypoint1 = Waypoint.make lat1 lon1 in 17 - let waypoint1 = Waypoint.with_name waypoint1 "San Francisco" in 18 - let waypoint2 = Waypoint.make lat2 lon2 in 19 - let waypoint2 = Waypoint.with_name waypoint2 "Near SF" in 20 - 21 - (* Create a simple track from coordinates *) 22 - let track = Track.make ~name:"SF Walk" in 23 - let track_segment = Track.Segment.empty in 24 - let coords = [ 25 - (37.7749, -122.4194); 26 - (37.7759, -122.4184); 27 - (37.7769, -122.4174); 28 - (37.7779, -122.4164); 29 - ] in 30 - let track_segment = List.fold_left (fun seg (lat_f, lon_f) -> 31 - match Coordinate.latitude lat_f, Coordinate.longitude lon_f with 32 - | Ok lat, Ok lon -> 33 - let pt = Waypoint.make lat lon in 34 - Track.Segment.add_point seg pt 35 - | _ -> seg 36 - ) track_segment coords in 37 - let track = Track.add_segment track track_segment in 38 - 39 - (* Create a route *) 40 - let route = Route.make ~name:"SF Route" in 41 - let route_coords = [ 42 - (37.7749, -122.4194); 43 - (37.7849, -122.4094); 44 - ] in 45 - let route = List.fold_left (fun r (lat_f, lon_f) -> 46 - match Coordinate.latitude lat_f, Coordinate.longitude lon_f with 47 - | Ok lat, Ok lon -> 48 - let pt = Waypoint.make lat lon in 49 - Route.add_point r pt 50 - | _ -> r 51 - ) route route_coords in 52 - 53 - (* Create GPX document with all elements *) 54 - let gpx = Gpx_doc.empty ~creator:"simple-example" in 55 - let gpx = Gpx_doc.add_waypoint gpx waypoint1 in 56 - let gpx = Gpx_doc.add_waypoint gpx waypoint2 in 57 - let gpx = Gpx_doc.add_track gpx track in 58 - let gpx = Gpx_doc.add_route gpx route in 59 - 60 - Printf.printf "Created GPX document with:\n"; 61 - Printf.printf " Waypoints: %d\n" (List.length (Gpx_doc.get_waypoints gpx)); 62 - Printf.printf " Tracks: %d\n" (List.length (Gpx_doc.get_tracks gpx)); 63 - Printf.printf " Routes: %d\n" (List.length (Gpx_doc.get_routes gpx)); 64 - Printf.printf "\n"; 65 - 66 - (* Write to file with validation *) 67 - let out_chan = open_out "example_output.gpx" in 68 - let dest = (`Channel out_chan) in 69 - (match write ~validate:true dest gpx with 70 - | Ok () -> 71 - close_out out_chan; 72 - Printf.printf "Wrote GPX to example_output.gpx\n" 73 - | Error e -> 74 - close_out out_chan; 75 - Printf.eprintf "Error writing GPX: %s\n" (Error.to_string e) 76 - ); 77 - 78 - (* Read it back and verify *) 79 - let in_chan = open_in "example_output.gpx" in 80 - let input = Xmlm.make_input (`Channel in_chan) in 81 - (match parse ~validate:true input with 82 - | Ok gpx2 -> 83 - close_in in_chan; 84 - let waypoints = Gpx_doc.get_waypoints gpx2 in 85 - let tracks = Gpx_doc.get_tracks gpx2 in 86 - let routes = Gpx_doc.get_routes gpx2 in 87 - Printf.printf "Read back GPX document with %d waypoints, %d tracks, %d routes\n" 88 - (List.length waypoints) (List.length tracks) (List.length routes); 89 - 90 - (* Extract coordinates from track *) 91 - (match tracks with 92 - | track :: _ -> 93 - let segments = Track.get_segments track in 94 - (match segments with 95 - | seg :: _ -> 96 - let points = Track.Segment.get_points seg in 97 - Printf.printf "Track coordinates: %d points\n" (List.length points); 98 - List.iteri (fun i pt -> 99 - let lat = Coordinate.latitude_to_float (Waypoint.get_lat pt) in 100 - let lon = Coordinate.longitude_to_float (Waypoint.get_lon pt) in 101 - Printf.printf " Point %d: %.4f, %.4f\n" i lat lon 102 - ) points 103 - | [] -> Printf.printf "No track segments found\n") 104 - | [] -> Printf.printf "No tracks found\n") 105 - | Error e -> 106 - close_in in_chan; 107 - Printf.eprintf "Error reading back GPX: %s\n" (Error.to_string e)); 108 - 109 - Printf.printf "\nExample completed successfully!\n" 110 - 111 - with 112 - | Gpx_error err -> 113 - Printf.eprintf "GPX Error: %s\n" (Error.to_string err); 114 - exit 1 115 - | exn -> 116 - Printf.eprintf "Unexpected error: %s\n" (Printexc.to_string exn); 117 - exit 1
+9 -12
examples/simple_gpx.ml
··· 17 17 match result with 18 18 | Ok (lat, lon) -> 19 19 let wpt = Waypoint.make lat lon in 20 - let wpt = Waypoint.with_name wpt "San Francisco" in 21 - let wpt = Waypoint.with_description wpt "Golden Gate Bridge area" in 22 - Printf.printf "✓ Created waypoint: %s\n" (Option.value (Waypoint.get_name wpt) ~default:"<unnamed>"); 20 + let wpt = { wpt with name = Some "San Francisco"; desc = Some "Golden Gate Bridge area" } in 21 + Printf.printf "✓ Created waypoint: %s\n" (Option.value (Waypoint.name wpt) ~default:"<unnamed>"); 23 22 24 23 (* Create GPX document *) 25 - let gpx = Gpx_doc.empty ~creator:"mlgpx direct API example" in 26 - let gpx = Gpx_doc.add_waypoint gpx wpt in 24 + let gpx = Doc.empty ~creator:"mlgpx direct API example" in 25 + let gpx = Doc.add_waypoint gpx wpt in 27 26 28 27 (* Add metadata *) 29 28 let metadata = Metadata.empty in 30 - let metadata = Metadata.with_name metadata "Example GPX File" in 31 - let metadata = Metadata.with_description metadata "Demonstration of mlgpx library capabilities" in 32 - let gpx = Gpx_doc.with_metadata gpx metadata in 29 + let metadata = { metadata with name = Some "Example GPX File"; desc = Some "Demonstration of mlgpx library capabilities" } in 30 + let gpx = { gpx with metadata = Some metadata } in 33 31 34 32 (* Create a simple track with points *) 35 33 let track = Track.make ~name:"Example Track" in 36 - let track = Track.with_comment track "Sample GPS track" in 37 - let track = Track.with_description track "Demonstrates track creation" in 34 + let track = { track with cmt = Some "Sample GPS track"; desc = Some "Demonstrates track creation" } in 38 35 39 36 (* Create track segment with points *) 40 37 let track_segment = Track.Segment.empty in ··· 53 50 ) track_segment points in 54 51 55 52 let track = Track.add_segment track track_segment in 56 - let gpx = Gpx_doc.add_track gpx track in 53 + let gpx = Doc.add_track gpx track in 57 54 58 55 Printf.printf "✓ Created track\n"; 59 56 ··· 88 85 Printf.printf "✓ Round-trip validation: %s\n" 89 86 (if validation2.is_valid then "PASSED" else "FAILED"); 90 87 Printf.printf " Waypoints: %d, Tracks: %d\n" 91 - (List.length (Gpx_doc.get_waypoints gpx2)) (List.length (Gpx_doc.get_tracks gpx2)) 88 + (List.length (Doc.waypoints gpx2)) (List.length (Doc.tracks gpx2)) 92 89 | Error e -> 93 90 Printf.printf "✗ Error reading back: %s\n" (Error.to_string e) 94 91 )
+2 -2
lib/gpx/coordinate.ml
··· 39 39 | Error e, _ | _, Error e -> Error e 40 40 41 41 (** Extract components *) 42 - let get_lat t = t.lat 43 - let get_lon t = t.lon 42 + let lat t = t.lat 43 + let lon t = t.lon 44 44 let to_floats t = (latitude_to_float t.lat, longitude_to_float t.lon) 45 45 46 46 (** Compare coordinates *)
+8 -8
lib/gpx/coordinate.mli
··· 1 1 (** Geographic coordinate types with validation *) 2 2 3 - (** Private coordinate types with validation constraints *) 3 + (** Coordinate types with validation constraints *) 4 4 type latitude = private float 5 5 type longitude = private float 6 6 type degrees = private float 7 7 8 - (** Coordinate pair - main type for this module *) 8 + (** Coordinate pair *) 9 9 type t = { 10 10 lat : latitude; 11 11 lon : longitude; 12 12 } 13 13 14 - (** {2 Smart Constructors} *) 14 + (** {2 Constructors} *) 15 15 16 16 (** Create validated latitude. 17 17 @param f Latitude in degrees (-90.0 to 90.0) ··· 39 39 (** Convert degrees to float *) 40 40 val degrees_to_float : degrees -> float 41 41 42 - (** {2 Coordinate Operations} *) 42 + (** {2 Operations} *) 43 43 44 44 (** Create coordinate pair from validated components *) 45 45 val make : latitude -> longitude -> t ··· 48 48 val make_from_floats : float -> float -> (t, string) result 49 49 50 50 (** Extract latitude component *) 51 - val get_lat : t -> latitude 51 + val lat : t -> latitude 52 52 53 53 (** Extract longitude component *) 54 - val get_lon : t -> longitude 54 + val lon : t -> longitude 55 55 56 56 (** Convert coordinate to float pair *) 57 57 val to_floats : t -> float * float 58 58 59 - (** {2 Comparison and Utilities} *) 59 + (** {2 Comparison and Printers} *) 60 60 61 61 (** Compare two coordinates *) 62 62 val compare : t -> t -> int ··· 65 65 val equal : t -> t -> bool 66 66 67 67 (** Pretty print coordinate *) 68 - val pp : Format.formatter -> t -> unit 68 + val pp : Format.formatter -> t -> unit
+1 -1
lib/gpx/dune
··· 2 2 (public_name mlgpx.core) 3 3 (name gpx) 4 4 (libraries xmlm ptime) 5 - (modules gpx parser writer validate coordinate link extension waypoint metadata route track error gpx_doc)) 5 + (modules gpx parser writer validate coordinate link extension waypoint metadata route track error doc))
+8 -8
lib/gpx/extension.ml
··· 17 17 (** {2 Extension Operations} *) 18 18 19 19 (** Create extension with flexible content *) 20 - let make ~namespace ~name ~attributes ~content () = 20 + let make ?namespace ~name ~attributes ~content () = 21 21 { namespace; name; attributes; content } 22 22 23 23 (** Create an extension with text content *) ··· 33 33 { namespace; name; attributes; content = Mixed (text, elements) } 34 34 35 35 (** Get extension name *) 36 - let get_name t = t.name 36 + let name t = t.name 37 37 38 38 (** Get optional namespace *) 39 - let get_namespace t = t.namespace 39 + let namespace t = t.namespace 40 40 41 41 (** Get attributes *) 42 - let get_attributes t = t.attributes 42 + let attributes t = t.attributes 43 43 44 44 (** Get content *) 45 - let get_content t = t.content 45 + let content t = t.content 46 46 47 47 (** Create text content *) 48 48 let text_content text = Text text ··· 135 135 let is_mixed_content = function Mixed _ -> true | _ -> false 136 136 137 137 (** Extract text content *) 138 - let get_text_content = function Text s -> Some s | _ -> None 138 + let text_content_extract = function Text s -> Some s | _ -> None 139 139 140 140 (** Extract element content *) 141 - let get_elements_content = function Elements e -> Some e | _ -> None 141 + let elements_content_extract = function Elements e -> Some e | _ -> None 142 142 143 143 (** Extract mixed content *) 144 - let get_mixed_content = function Mixed (s, e) -> Some (s, e) | _ -> None 144 + let mixed_content_extract = function Mixed (s, e) -> Some (s, e) | _ -> None
+8 -8
lib/gpx/extension.mli
··· 17 17 (** {2 Extension Constructors} *) 18 18 19 19 (** Create extension with flexible content *) 20 - val make : namespace:string option -> name:string -> attributes:(string * string) list -> content:content -> unit -> t 20 + val make : ?namespace:string -> name:string -> attributes:(string * string) list -> content:content -> unit -> t 21 21 22 22 (** Create an extension with text content *) 23 23 val make_text : name:string -> ?namespace:string -> ?attributes:(string * string) list -> string -> t ··· 31 31 (** {2 Extension Operations} *) 32 32 33 33 (** Get extension name *) 34 - val get_name : t -> string 34 + val name : t -> string 35 35 36 36 (** Get optional namespace *) 37 - val get_namespace : t -> string option 37 + val namespace : t -> string option 38 38 39 39 (** Get attributes *) 40 - val get_attributes : t -> (string * string) list 40 + val attributes : t -> (string * string) list 41 41 42 42 (** Get content *) 43 - val get_content : t -> content 43 + val content : t -> content 44 44 45 45 (** Find attribute value by name *) 46 46 val find_attribute : string -> t -> string option ··· 78 78 val is_mixed_content : content -> bool 79 79 80 80 (** Extract text content *) 81 - val get_text_content : content -> string option 81 + val text_content_extract : content -> string option 82 82 83 83 (** Extract element content *) 84 - val get_elements_content : content -> t list option 84 + val elements_content_extract : content -> t list option 85 85 86 86 (** Extract mixed content *) 87 - val get_mixed_content : content -> (string * t list) option 87 + val mixed_content_extract : content -> (string * t list) option
+6 -6
lib/gpx/gpx.ml
··· 27 27 module Error = Error 28 28 29 29 (** Main GPX document type *) 30 - module Gpx_doc = Gpx_doc 30 + module Doc = Doc 31 31 32 32 (** {1 Main Document Type} *) 33 33 34 34 (** Main GPX document type *) 35 - type t = Gpx_doc.t 35 + type t = Doc.t 36 36 37 37 (** {1 Error Handling} *) 38 38 ··· 80 80 let is_valid = Validate.is_valid 81 81 82 82 (** Get only error messages *) 83 - let get_errors = Validate.get_errors 83 + let errors = Validate.errors 84 84 85 85 (** Get only warning messages *) 86 - let get_warnings = Validate.get_warnings 86 + let warnings = Validate.warnings 87 87 88 88 (** Format validation issue for display *) 89 89 let format_issue = Validate.format_issue ··· 91 91 (** {1 Constructors and Utilities} *) 92 92 93 93 (** Create new GPX document *) 94 - let make_gpx ~creator = Gpx_doc.empty ~creator 94 + let make_gpx ~creator = Doc.empty ~creator 95 95 96 96 (** Create empty GPX document *) 97 - let empty ~creator = Gpx_doc.empty ~creator 97 + let empty ~creator = Doc.empty ~creator
+443 -54
lib/gpx/gpx.mli
··· 1 - (** OCaml library for reading and writing GPX (GPS Exchange Format) files 1 + (** OCaml library for reading and writing GPX (GPS Exchange Format) files. 2 + 3 + {1 Overview} 4 + 5 + GPX (GPS Exchange Format) is an XML-based format for GPS data interchange, 6 + standardized by {{:https://www.topografix.com/gpx.asp}Topografix}. This library 7 + provides a complete implementation of the GPX 1.1 specification with strong 8 + type safety and validation. 9 + 10 + GPX files can contain three main types of GPS data: 11 + - {b Waypoints}: Individual points of interest with coordinates 12 + - {b Routes}: Ordered sequences of waypoints representing a planned path 13 + - {b Tracks}: Recorded GPS traces, typically from actual journeys 14 + 15 + All coordinates in GPX use the WGS84 datum (World Geodetic System 1984), 16 + the same coordinate system used by GPS satellites. Coordinates are expressed 17 + as decimal degrees, with elevations in meters above mean sea level. 18 + 19 + {2 Quick Start Example} 20 + 21 + {[ 22 + open Gpx 23 + 24 + (* Create coordinates *) 25 + let lat = Coordinate.latitude 37.7749 |> Result.get_ok in 26 + let lon = Coordinate.longitude (-122.4194) |> Result.get_ok in 27 + 28 + (* Create a waypoint *) 29 + let waypoint = Waypoint.make lat lon 30 + |> Waypoint.with_name "San Francisco" 31 + |> Waypoint.with_description "Golden Gate Bridge area" in 32 + 33 + (* Create GPX document *) 34 + let gpx = make_gpx ~creator:"my-app" 35 + |> Doc.add_waypoint waypoint in 36 + 37 + (* Write to file or string *) 38 + match write_string gpx with 39 + | Ok xml -> print_endline xml 40 + | Error e -> Printf.eprintf "Error: %s\n" (Error.to_string e) 41 + ]} 2 42 3 43 This library provides a clean, modular interface for working with GPX files, 4 - the standard format for GPS data exchange. *) 44 + with separate modules for each major component of the GPX specification. *) 5 45 6 46 (** {1 Core Modules} 7 47 8 48 The library is organized into focused modules, each handling a specific aspect 9 - of GPX data. *) 49 + of GPX data. Each module provides complete functionality for its domain with 50 + strong type safety and validation. *) 10 51 11 - (** Geographic coordinate handling with validation *) 52 + (** {2 Geographic coordinate handling with validation} 53 + 54 + The {!Coordinate} module provides validated coordinate types for latitude, 55 + longitude, and degrees. All coordinates use the WGS84 datum and are validated 56 + at construction time to ensure they fall within valid ranges: 57 + - Latitude: -90.0 to +90.0 degrees 58 + - Longitude: -180.0 to +180.0 degrees 59 + - Degrees: 0.0 to 360.0 degrees 60 + 61 + Example: [Coordinate.latitude 37.7749] creates a validated latitude. *) 12 62 module Coordinate = Coordinate 13 63 14 - (** Links, persons, and copyright information *) 64 + (** {2 Links, persons, and copyright information} 65 + 66 + The {!Link} module handles web links, author information, and copyright data 67 + as defined in the GPX specification. This includes: 68 + - Web links with optional text and MIME type 69 + - Person records with name, email, and associated links 70 + - Copyright information with author, year, and license terms *) 15 71 module Link = Link 16 72 17 - (** Extension mechanism for custom GPX elements *) 73 + (** {2 Extension mechanism for custom GPX elements} 74 + 75 + The {!Extension} module provides support for custom XML elements that extend 76 + the standard GPX format. Extensions allow applications to embed additional 77 + data while maintaining compatibility with standard GPX readers. *) 18 78 module Extension = Extension 19 79 20 - (** GPS waypoint data and fix types *) 80 + (** {2 GPS waypoint data and fix types} 81 + 82 + The {!Waypoint} module handles individual GPS points, including waypoints, 83 + route points, and track points. Each waypoint contains: 84 + - Required coordinates (latitude/longitude) 85 + - Optional elevation in meters above mean sea level 86 + - Optional timestamp 87 + - Optional metadata like name, description, symbol 88 + - Optional GPS quality information (accuracy, satellite count, etc.) 89 + 90 + Fix types indicate GPS quality: none, 2D, 3D, DGPS, or PPS. *) 21 91 module Waypoint = Waypoint 22 92 23 - (** GPX metadata including bounds *) 93 + (** {2 GPX metadata including bounds} 94 + 95 + The {!Metadata} module handles document-level information: 96 + - File name and description 97 + - Author and copyright information 98 + - Creation time and keywords 99 + - Geographic bounding box of all data 100 + - Links to related resources *) 24 101 module Metadata = Metadata 25 102 26 - (** Route data and calculations *) 103 + (** {2 Route data and calculations} 104 + 105 + The {!Route} module handles planned paths represented as ordered sequences 106 + of waypoints. Routes typically represent intended journeys rather than 107 + recorded tracks. Each route can include: 108 + - Ordered list of waypoints (route points) 109 + - Route metadata (name, description, links) 110 + - Distance calculations between points *) 27 111 module Route = Route 28 112 29 - (** Track data with segments *) 113 + (** {2 Track data with segments} 114 + 115 + The {!Track} module handles recorded GPS traces, typically representing 116 + actual journeys. Tracks are divided into segments to handle GPS interruptions: 117 + - Track segments contain ordered track points 118 + - Each track point is a timestamped waypoint 119 + - Multiple segments per track handle GPS signal loss 120 + - Distance and time calculations available *) 30 121 module Track = Track 31 122 32 - (** Error handling *) 123 + (** {2 Error handling} 124 + 125 + The {!Error} module provides comprehensive error handling for GPX operations: 126 + - XML parsing and validation errors 127 + - Coordinate validation errors 128 + - Missing required elements or attributes 129 + - File I/O errors *) 33 130 module Error = Error 34 131 35 - (** Main GPX document type *) 36 - module Gpx_doc = Gpx_doc 132 + (** {2 Main GPX document type} 133 + 134 + The {!Doc} module represents complete GPX documents containing: 135 + - Document metadata (creator, version) 136 + - Collections of waypoints, routes, and tracks 137 + - Document-level extensions 138 + - Statistics and analysis functions *) 139 + module Doc = Doc 37 140 38 141 (** {1 Main Document Type} *) 39 142 40 - (** Main GPX document type *) 41 - type t = Gpx_doc.t 143 + (** A complete GPX document containing waypoints, routes, tracks, and metadata. 144 + 145 + This is the main type representing a complete GPX file. GPX documents must 146 + have a creator string (identifying the creating application) and follow the 147 + GPX 1.1 specification format. *) 148 + type t = Doc.t 42 149 43 150 (** {1 Error Handling} *) 44 151 45 - (** Error types *) 152 + (** Comprehensive error type covering all possible GPX operation failures. 153 + 154 + Errors can occur during: 155 + - XML parsing (malformed XML, invalid structure) 156 + - Coordinate validation (out of range values) 157 + - Missing required GPX elements or attributes 158 + - File I/O operations *) 46 159 type error = Error.t 47 160 48 - (** GPX exception raised for errors *) 161 + (** GPX exception raised for unrecoverable errors. 162 + 163 + Most functions return [Result.t] for error handling, but this exception 164 + may be raised in exceptional circumstances. *) 49 165 exception Gpx_error of error 50 166 51 - (** {1 Parsing Functions} *) 167 + (** {1 Parsing Functions} 168 + 169 + Parse GPX data from various sources. All parsing functions support optional 170 + validation to check compliance with GPX specification constraints. *) 52 171 53 - (** Parse GPX from XML input. 172 + (** Parse GPX from XML input source. 54 173 55 - @param validate Whether to validate the document after parsing 56 - @param input XMLm input source 57 - @return Parsed GPX document or error *) 174 + Reads GPX data from an {!Xmlm.input} source, which can be created from 175 + files, strings, or other input sources using the {{:https://erratique.ch/software/xmlm}Xmlm} library. 176 + 177 + @param validate If [true] (default [false]), validates the parsed document 178 + against GPX specification rules. Validation checks coordinate 179 + ranges, required elements, and data consistency. 180 + @param input XMLm input source created with [Xmlm.make_input] 181 + @return [Ok gpx] with parsed document, or [Error e] if parsing fails 182 + 183 + Example: 184 + {[ 185 + let input = Xmlm.make_input (`String (0, gpx_xml_string)) in 186 + match parse ~validate:true input with 187 + | Ok gpx -> Printf.printf "Parsed %d waypoints\n" (List.length (Doc.waypoints gpx)) 188 + | Error e -> Printf.eprintf "Parse error: %s\n" (Error.to_string e) 189 + ]} *) 58 190 val parse : ?validate:bool -> Xmlm.input -> (t, error) result 59 191 60 - (** Parse GPX from string. 192 + (** Parse GPX from XML string. 193 + 194 + Convenience function for parsing GPX data from a string. Equivalent to 195 + creating an {!Xmlm.input} from the string and calling {!parse}. 61 196 62 - @param validate Whether to validate the document after parsing 63 - @param s XML string to parse 64 - @return Parsed GPX document or error *) 197 + @param validate If [true] (default [false]), validates the parsed document 198 + @param s Complete GPX XML document as a string 199 + @return [Ok gpx] with parsed document, or [Error e] if parsing fails 200 + 201 + Example: 202 + {[ 203 + let gpx_xml = {|<?xml version="1.0"?> 204 + <gpx version="1.1" creator="my-app"> 205 + <wpt lat="37.7749" lon="-122.4194"> 206 + <name>San Francisco</name> 207 + </wpt> 208 + </gpx>|} in 209 + match parse_string ~validate:true gpx_xml with 210 + | Ok gpx -> print_endline "Successfully parsed GPX" 211 + | Error e -> Printf.eprintf "Error: %s\n" (Error.to_string e) 212 + ]} *) 65 213 val parse_string : ?validate:bool -> string -> (t, error) result 66 214 67 - (** {1 Writing Functions} *) 215 + (** {1 Writing Functions} 216 + 217 + Generate GPX XML from document structures. All writing functions support 218 + optional validation before output generation. *) 68 219 69 - (** Write GPX to XML output. 220 + (** Write GPX to XML output destination. 70 221 71 - @param validate Whether to validate before writing 72 - @param output XMLm output destination 222 + Generates standard GPX 1.1 XML and writes it to an {!Xmlm.dest} destination. 223 + The output destination can target files, buffers, or other sinks. 224 + 225 + @param validate If [true] (default [false]), validates the document before 226 + writing to ensure GPX specification compliance 227 + @param dest XMLm output destination created with [Xmlm.make_output] 73 228 @param gpx GPX document to write 74 - @return Success or error *) 229 + @return [Ok ()] on success, or [Error e] if writing fails 230 + 231 + Example: 232 + {[ 233 + let output = Buffer.create 1024 in 234 + let dest = Xmlm.make_output (`Buffer output) in 235 + match write ~validate:true dest gpx with 236 + | Ok () -> Buffer.contents output 237 + | Error e -> failwith (Error.to_string e) 238 + ]} *) 75 239 val write : ?validate:bool -> Xmlm.dest -> t -> (unit, error) result 76 240 77 - (** Write GPX to string. 241 + (** Write GPX to XML string. 242 + 243 + Convenience function that generates a complete GPX XML document as a string. 244 + The output includes XML declaration and proper namespace declarations. 245 + 246 + @param validate If [true] (default [false]), validates before writing 247 + @param gpx GPX document to serialize 248 + @return [Ok xml_string] with complete GPX XML, or [Error e] if generation fails 78 249 79 - @param validate Whether to validate before writing 80 - @param gpx GPX document to write 81 - @return XML string or error *) 250 + Example: 251 + {[ 252 + match write_string ~validate:true gpx with 253 + | Ok xml -> 254 + print_endline "Generated GPX:"; 255 + print_endline xml 256 + | Error e -> 257 + Printf.eprintf "Failed to generate GPX: %s\n" (Error.to_string e) 258 + ]} *) 82 259 val write_string : ?validate:bool -> t -> (string, error) result 83 260 84 261 (** {1 Validation Functions} 85 262 86 - Validate GPX documents for correctness and best practices. *) 263 + Comprehensive validation against GPX specification rules and best practices. 264 + Validation checks coordinate ranges, required elements, data consistency, 265 + and common issues that may cause problems for GPS applications. *) 87 266 88 - (** Validation issue with severity level *) 267 + (** A validation issue found during GPX document checking. 268 + 269 + Issues are classified as either errors (specification violations that make 270 + the GPX invalid) or warnings (best practice violations or suspicious data). *) 89 271 type validation_issue = Validate.validation_issue = { 90 - level : [`Error | `Warning]; (** Severity level *) 91 - message : string; (** Issue description *) 92 - location : string option; (** Location in document *) 272 + level : [`Error | `Warning]; (** [`Error] for specification violations, [`Warning] for best practice issues *) 273 + message : string; (** Human-readable description of the issue *) 274 + location : string option; (** Optional location context (e.g., "waypoint 1", "track segment 2") *) 93 275 } 94 276 95 - (** Result of validation containing all issues found *) 277 + (** Complete validation result with all issues and validity status. 278 + 279 + The [is_valid] field indicates whether the document contains any errors. 280 + Documents with only warnings are considered valid. *) 96 281 type validation_result = Validate.validation_result = { 97 - issues : validation_issue list; (** All validation issues *) 98 - is_valid : bool; (** Whether document is valid *) 282 + issues : validation_issue list; (** All validation issues found, both errors and warnings *) 283 + is_valid : bool; (** [true] if no errors found (warnings are allowed) *) 99 284 } 100 285 101 - (** Validate complete GPX document *) 286 + (** Perform comprehensive validation of a GPX document. 287 + 288 + Checks all aspects of the GPX document against the specification: 289 + - Coordinate ranges (latitude -90 to +90, longitude -180 to +180) 290 + - Required elements and attributes 291 + - Data consistency (e.g., time ordering in tracks) 292 + - Reasonable value ranges for GPS quality metrics 293 + - Proper structure and nesting 294 + 295 + @param gpx The GPX document to validate 296 + @return Complete validation result with all issues found 297 + 298 + Example: 299 + {[ 300 + let result = validate_gpx gpx in 301 + if result.is_valid then 302 + Printf.printf "Document is valid with %d warnings\n" 303 + (List.length (List.filter (fun i -> i.level = `Warning) result.issues)) 304 + else begin 305 + print_endline "Document has errors:"; 306 + List.iter (fun issue -> 307 + if issue.level = `Error then 308 + Printf.printf " ERROR: %s\n" (format_issue issue) 309 + ) result.issues 310 + end 311 + ]} *) 102 312 val validate_gpx : t -> validation_result 103 313 104 - (** Quick validation - returns true if document is valid *) 314 + (** Quick validation check - returns true if document has no errors. 315 + 316 + Equivalent to [(validate_gpx gpx).is_valid] but potentially more efficient 317 + as it can stop at the first error found. 318 + 319 + @param gpx The GPX document to validate 320 + @return [true] if valid (no errors), [false] if errors found *) 105 321 val is_valid : t -> bool 106 322 107 - (** Get only error messages *) 108 - val get_errors : t -> validation_issue list 323 + (** Get only validation errors (specification violations). 324 + 325 + Returns only the issues marked as errors, filtering out warnings. 326 + If this list is empty, the document is valid according to the GPX specification. 327 + 328 + @param gpx The GPX document to validate 329 + @return List of error-level validation issues *) 330 + val errors : t -> validation_issue list 109 331 110 - (** Get only warning messages *) 111 - val get_warnings : t -> validation_issue list 332 + (** Get only validation warnings (best practice violations). 333 + 334 + Returns only the issues marked as warnings. These don't make the document 335 + invalid but may indicate potential problems or areas for improvement. 336 + 337 + @param gpx The GPX document to validate 338 + @return List of warning-level validation issues *) 339 + val warnings : t -> validation_issue list 112 340 113 - (** Format validation issue for display *) 341 + (** Format a validation issue for human-readable display. 342 + 343 + Combines the issue message with location context if available. 344 + 345 + @param issue The validation issue to format 346 + @return Formatted string suitable for display to users 347 + 348 + Example output: ["Error in waypoint 1: Latitude out of range (-95.0)"] *) 114 349 val format_issue : validation_issue -> string 115 350 116 - (** {1 Constructors and Utilities} *) 351 + (** {1 Document Constructors and Utilities} 352 + 353 + Functions for creating GPX documents and basic document operations. *) 117 354 118 - (** Create new GPX document with required fields *) 355 + (** Create a new GPX document with the required creator field. 356 + 357 + Every GPX document must identify its creating application through the 358 + [creator] attribute. This is required by the GPX specification and helps 359 + identify the source of GPS data. 360 + 361 + The created document: 362 + - Uses GPX version 1.1 (the current standard) 363 + - Contains no waypoints, routes, or tracks initially 364 + - Has no metadata initially 365 + - Can be extended using {!Doc} module functions 366 + 367 + @param creator Name of the creating application (e.g., "MyGPS App v1.0") 368 + @return Empty GPX document ready for data addition 369 + 370 + Example: 371 + {[ 372 + let gpx = make_gpx ~creator:"MyTracker v2.1" in 373 + let gpx = Doc.add_waypoint gpx some_waypoint in 374 + let gpx = Doc.add_track gpx some_track in 375 + (* gpx now contains waypoints and tracks *) 376 + ]} *) 119 377 val make_gpx : creator:string -> t 120 378 121 - (** Create empty GPX document *) 122 - val empty : creator:string -> t 379 + (** Create an empty GPX document with the required creator field. 380 + 381 + Alias for {!make_gpx} provided for consistency with module naming patterns. 382 + Creates a document with no GPS data that can be populated using the 383 + {!Doc} module functions. 384 + 385 + @param creator Name of the creating application 386 + @return Empty GPX document 387 + 388 + Example: 389 + {[ 390 + let gpx = empty ~creator:"GPS Logger" in 391 + assert (List.length (Doc.waypoints gpx) = 0); 392 + assert (List.length (Doc.tracks gpx) = 0); 393 + assert (Doc.creator gpx = "GPS Logger"); 394 + ]} *) 395 + val empty : creator:string -> t 396 + 397 + (** {1 Common Patterns and Best Practices} 398 + 399 + {2 Reading GPX Files} 400 + 401 + The most common use case is reading existing GPX files: 402 + {[ 403 + (* From a file using platform-specific modules *) 404 + match Gpx_unix.read "track.gpx" with 405 + | Ok gpx -> process_gpx gpx 406 + | Error e -> handle_error e 407 + 408 + (* From a string *) 409 + match parse_string ~validate:true gpx_content with 410 + | Ok gpx -> process_gpx gpx 411 + | Error e -> handle_error e 412 + ]} 413 + 414 + {2 Creating GPX Files} 415 + 416 + To create new GPX files with waypoints: 417 + {[ 418 + (* Create coordinates *) 419 + let lat = Coordinate.latitude 37.7749 |> Result.get_ok in 420 + let lon = Coordinate.longitude (-122.4194) |> Result.get_ok in 421 + 422 + (* Create waypoint *) 423 + let waypoint = Waypoint.make lat lon 424 + |> Waypoint.with_name "Golden Gate" 425 + |> Waypoint.with_description "Famous San Francisco bridge" in 426 + 427 + (* Create document *) 428 + let gpx = make_gpx ~creator:"My App v1.0" 429 + |> Doc.add_waypoint waypoint in 430 + 431 + (* Write to file *) 432 + match Gpx_unix.write "output.gpx" gpx with 433 + | Ok () -> print_endline "File written successfully" 434 + | Error e -> Printf.eprintf "Write error: %s\n" (Error.to_string e) 435 + ]} 436 + 437 + {2 Working with Tracks} 438 + 439 + Tracks represent recorded GPS traces with timestamped points: 440 + {[ 441 + (* Create track points with timestamps *) 442 + let points = List.map (fun (lat_f, lon_f, time) -> 443 + let lat = Coordinate.latitude lat_f |> Result.get_ok in 444 + let lon = Coordinate.longitude lon_f |> Result.get_ok in 445 + Waypoint.make lat lon |> Waypoint.with_time (Some time) 446 + ) gps_data in 447 + 448 + (* Create track segment *) 449 + let segment = Track.Segment.make points in 450 + 451 + (* Create track *) 452 + let track = Track.make ~name:"Morning Run" 453 + |> Track.add_segment segment in 454 + 455 + (* Add to document *) 456 + let gpx = make_gpx ~creator:"Fitness App" 457 + |> Doc.add_track track 458 + ]} 459 + 460 + {2 Coordinate Systems and Units} 461 + 462 + - All coordinates use WGS84 datum (World Geodetic System 1984) 463 + - Latitude ranges from -90.0 (South Pole) to +90.0 (North Pole) 464 + - Longitude ranges from -180.0 to +180.0 degrees 465 + - Elevations are in meters above mean sea level 466 + - Times use RFC 3339 format (ISO 8601 subset) 467 + 468 + {2 Validation Recommendations} 469 + 470 + - Always validate when parsing untrusted GPX data 471 + - Validate before writing to catch data consistency issues 472 + - Handle both errors and warnings appropriately 473 + - Use {!val:is_valid} for quick checks, {!validate_gpx} for detailed analysis 474 + 475 + {2 Performance Considerations} 476 + 477 + - The library uses streaming XML parsing for memory efficiency 478 + - Large GPX files with many track points are handled efficiently 479 + - Coordinate validation occurs at construction time 480 + - Consider using platform-specific modules ({!Gpx_unix}, [Gpx_eio]) for file I/O 481 + 482 + {2 Extension Support} 483 + 484 + The library supports GPX extensions for custom data: 485 + {[ 486 + (* Create extension *) 487 + let ext = Extension.make_text 488 + ~name:"temperature" 489 + ~namespace:"http://example.com/weather" 490 + "25.5" in 491 + 492 + (* Add to waypoint *) 493 + let waypoint = Waypoint.make lat lon 494 + |> Waypoint.add_extensions [ext] 495 + ]} *) 496 + 497 + (** {1 Related Modules and Libraries} 498 + 499 + This core module provides the foundation. For complete applications, consider: 500 + 501 + - {!Gpx_unix}: File I/O operations using standard Unix libraries 502 + - [Gpx_eio]: Concurrent file I/O using the Eio effects library 503 + - {{:https://erratique.ch/software/xmlm}Xmlm}: Underlying XML processing library 504 + - {{:https://erratique.ch/software/ptime}Ptime}: Time representation used for timestamps 505 + 506 + {2 External Links} 507 + 508 + - {{:https://www.topografix.com/gpx.asp}Official GPX specification} 509 + - {{:https://www.topografix.com/GPX/1/1/gpx.xsd}GPX 1.1 XML Schema} 510 + - {{:https://en.wikipedia.org/wiki/GPS_Exchange_Format}GPX Format on Wikipedia} 511 + - {{:https://en.wikipedia.org/wiki/World_Geodetic_System}WGS84 Coordinate System} *)
+17 -23
lib/gpx/gpx_doc.ml lib/gpx/doc.ml
··· 31 31 (** {2 Document Properties} *) 32 32 33 33 (** Get version *) 34 - let get_version t = t.version 34 + let version t = t.version 35 35 36 36 (** Get creator *) 37 - let get_creator t = t.creator 37 + let creator t = t.creator 38 38 39 39 (** Get metadata *) 40 - let get_metadata t = t.metadata 40 + let metadata t = t.metadata 41 41 42 42 (** Get waypoints *) 43 - let get_waypoints t = t.waypoints 43 + let waypoints t = t.waypoints 44 44 45 45 (** Get routes *) 46 - let get_routes t = t.routes 46 + let routes t = t.routes 47 47 48 48 (** Get tracks *) 49 - let get_tracks t = t.tracks 49 + let tracks t = t.tracks 50 50 51 51 (** Get extensions *) 52 - let get_extensions t = t.extensions 52 + let extensions t = t.extensions 53 53 54 54 (** {2 Document Modification} *) 55 55 56 - (** Set version *) 57 - let with_version t version = { t with version } 58 - 59 - (** Set metadata *) 56 + (** Update metadata *) 60 57 let with_metadata t metadata = { t with metadata = Some metadata } 61 58 62 - (** Set metadata *) 63 - let set_metadata metadata t = { t with metadata = Some metadata } 64 - 65 59 (** Add waypoint *) 66 60 let add_waypoint t waypoint = { t with waypoints = t.waypoints @ [waypoint] } 67 61 ··· 114 108 115 109 (** Check if document has elevation data *) 116 110 let has_elevation t = 117 - List.exists (fun wpt -> Waypoint.get_elevation wpt <> None) t.waypoints || 111 + List.exists (fun wpt -> Waypoint.elevation wpt <> None) t.waypoints || 118 112 List.exists (fun route -> 119 - List.exists (fun pt -> Waypoint.get_elevation pt <> None) (Route.get_points route) 113 + List.exists (fun pt -> Waypoint.elevation pt <> None) (Route.points route) 120 114 ) t.routes || 121 115 List.exists (fun track -> 122 - List.exists (fun pt -> Waypoint.get_elevation pt <> None) (Track.all_points track) 116 + List.exists (fun pt -> Waypoint.elevation pt <> None) (Track.all_points track) 123 117 ) t.tracks 124 118 125 119 (** Check if document has time data *) 126 120 let has_time t = 127 - List.exists (fun wpt -> Waypoint.get_time wpt <> None) t.waypoints || 121 + List.exists (fun wpt -> Waypoint.time wpt <> None) t.waypoints || 128 122 List.exists (fun route -> 129 - List.exists (fun pt -> Waypoint.get_time pt <> None) (Route.get_points route) 123 + List.exists (fun pt -> Waypoint.time pt <> None) (Route.points route) 130 124 ) t.routes || 131 125 List.exists (fun track -> 132 - List.exists (fun pt -> Waypoint.get_time pt <> None) (Track.all_points track) 126 + List.exists (fun pt -> Waypoint.time pt <> None) (Track.all_points track) 133 127 ) t.tracks 134 128 135 129 (** Check if document is empty *) ··· 146 140 has_time : bool; 147 141 } 148 142 149 - let get_stats t = { 143 + let stats t = { 150 144 waypoint_count = waypoint_count t; 151 145 route_count = route_count t; 152 146 track_count = track_count t; ··· 177 171 178 172 (** Pretty print document *) 179 173 let pp ppf t = 180 - let stats = get_stats t in 174 + let stats = stats t in 181 175 Format.fprintf ppf "GPX v%s by %s (%d wpt, %d routes, %d tracks, %d total points)" 182 176 t.version t.creator 183 177 stats.waypoint_count stats.route_count stats.track_count stats.total_points 184 178 185 179 (** Print document statistics *) 186 180 let print_stats t = 187 - let stats = get_stats t in 181 + let stats = stats t in 188 182 Printf.printf "GPX Statistics:\n"; 189 183 Printf.printf " Version: %s\n" t.version; 190 184 Printf.printf " Creator: %s\n" t.creator;
+9 -15
lib/gpx/gpx_doc.mli lib/gpx/doc.mli
··· 32 32 (** {2 Document Properties} *) 33 33 34 34 (** Get version *) 35 - val get_version : t -> string 35 + val version : t -> string 36 36 37 37 (** Get creator *) 38 - val get_creator : t -> string 38 + val creator : t -> string 39 39 40 40 (** Get metadata *) 41 - val get_metadata : t -> Metadata.t option 41 + val metadata : t -> Metadata.t option 42 42 43 43 (** Get waypoints *) 44 - val get_waypoints : t -> Waypoint.t list 44 + val waypoints : t -> Waypoint.t list 45 45 46 46 (** Get routes *) 47 - val get_routes : t -> Route.t list 47 + val routes : t -> Route.t list 48 48 49 49 (** Get tracks *) 50 - val get_tracks : t -> Track.t list 50 + val tracks : t -> Track.t list 51 51 52 52 (** Get extensions *) 53 - val get_extensions : t -> Extension.t list 53 + val extensions : t -> Extension.t list 54 54 55 55 (** {2 Document Modification} *) 56 56 57 - (** Set version *) 58 - val with_version : t -> string -> t 59 - 60 - (** Set metadata *) 57 + (** Update metadata *) 61 58 val with_metadata : t -> Metadata.t -> t 62 - 63 - (** Set metadata *) 64 - val set_metadata : Metadata.t -> t -> t 65 59 66 60 (** Add waypoint *) 67 61 val add_waypoint : t -> Waypoint.t -> t ··· 117 111 val is_empty : t -> bool 118 112 119 113 (** Get document statistics *) 120 - val get_stats : t -> stats 114 + val stats : t -> stats 121 115 122 116 (** {2 Comparison and Utilities} *) 123 117
+11 -11
lib/gpx/link.ml
··· 27 27 let make ~href ?text ?type_ () = { href; text; type_ } 28 28 29 29 (** Get href from link *) 30 - let get_href t = t.href 30 + let href t = t.href 31 31 32 32 (** Get optional text from link *) 33 - let get_text t = t.text 33 + let text t = t.text 34 34 35 35 (** Get optional type from link *) 36 - let get_type t = t.type_ 36 + let type_ t = t.type_ 37 37 38 - (** Set text on link *) 38 + (** Update text *) 39 39 let with_text t text = { t with text = Some text } 40 40 41 - (** Set type on link *) 41 + (** Update type *) 42 42 let with_type t type_ = { t with type_ = Some type_ } 43 43 44 44 (** Compare links *) ··· 65 65 let make_person ?name ?email ?link () = { name; email; link } 66 66 67 67 (** Get person name *) 68 - let get_person_name (p : person) = p.name 68 + let person_name (p : person) = p.name 69 69 70 70 (** Get person email *) 71 - let get_person_email (p : person) = p.email 71 + let person_email (p : person) = p.email 72 72 73 73 (** Get person link *) 74 - let get_person_link (p : person) = p.link 74 + let person_link (p : person) = p.link 75 75 76 76 (** Compare persons *) 77 77 let compare_person p1 p2 = ··· 99 99 let make_copyright ~author ?year ?license () = { author; year; license } 100 100 101 101 (** Get copyright author *) 102 - let get_copyright_author (c : copyright) = c.author 102 + let copyright_author (c : copyright) = c.author 103 103 104 104 (** Get copyright year *) 105 - let get_copyright_year (c : copyright) = c.year 105 + let copyright_year (c : copyright) = c.year 106 106 107 107 (** Get copyright license *) 108 - let get_copyright_license (c : copyright) = c.license 108 + let copyright_license (c : copyright) = c.license 109 109 110 110 (** Compare copyrights *) 111 111 let compare_copyright c1 c2 =
+12 -11
lib/gpx/link.mli
··· 30 30 val make : href:string -> ?text:string -> ?type_:string -> unit -> t 31 31 32 32 (** Get href from link *) 33 - val get_href : t -> string 33 + val href : t -> string 34 34 35 35 (** Get optional text from link *) 36 - val get_text : t -> string option 36 + val text : t -> string option 37 37 38 38 (** Get optional type from link *) 39 - val get_type : t -> string option 39 + val type_ : t -> string option 40 40 41 - (** Set text on link *) 41 + (** Update text *) 42 42 val with_text : t -> string -> t 43 43 44 - (** Set type on link *) 44 + (** Update type *) 45 45 val with_type : t -> string -> t 46 + 46 47 47 48 (** Compare links *) 48 49 val compare : t -> t -> int ··· 59 60 val make_person : ?name:string -> ?email:string -> ?link:t -> unit -> person 60 61 61 62 (** Get person name *) 62 - val get_person_name : person -> string option 63 + val person_name : person -> string option 63 64 64 65 (** Get person email *) 65 - val get_person_email : person -> string option 66 + val person_email : person -> string option 66 67 67 68 (** Get person link *) 68 - val get_person_link : person -> t option 69 + val person_link : person -> t option 69 70 70 71 (** Compare persons *) 71 72 val compare_person : person -> person -> int ··· 82 83 val make_copyright : author:string -> ?year:int -> ?license:string -> unit -> copyright 83 84 84 85 (** Get copyright author *) 85 - val get_copyright_author : copyright -> string 86 + val copyright_author : copyright -> string 86 87 87 88 (** Get copyright year *) 88 - val get_copyright_year : copyright -> int option 89 + val copyright_year : copyright -> int option 89 90 90 91 (** Get copyright license *) 91 - val get_copyright_license : copyright -> string option 92 + val copyright_license : copyright -> string option 92 93 93 94 (** Compare copyrights *) 94 95 val compare_copyright : copyright -> copyright -> int
+20 -31
lib/gpx/metadata.ml
··· 45 45 | Error e, _, _, _ | _, Error e, _, _ | _, _, Error e, _ | _, _, _, Error e -> Error e 46 46 47 47 (** Get corner coordinates *) 48 - let get_min_coords t = Coordinate.make t.minlat t.minlon 49 - let get_max_coords t = Coordinate.make t.maxlat t.maxlon 48 + let min_coords t = Coordinate.make t.minlat t.minlon 49 + let max_coords t = Coordinate.make t.maxlat t.maxlon 50 50 51 51 (** Get all bounds as tuple *) 52 - let get_bounds t = (t.minlat, t.minlon, t.maxlat, t.maxlon) 52 + let bounds t = (t.minlat, t.minlon, t.maxlat, t.maxlon) 53 53 54 54 (** Check if coordinate is within bounds *) 55 55 let contains bounds coord = 56 - let lat = Coordinate.get_lat coord in 57 - let lon = Coordinate.get_lon coord in 56 + let lat = Coordinate.lat coord in 57 + let lon = Coordinate.lon coord in 58 58 Coordinate.latitude_to_float bounds.minlat <= Coordinate.latitude_to_float lat && 59 59 Coordinate.latitude_to_float lat <= Coordinate.latitude_to_float bounds.maxlat && 60 60 Coordinate.longitude_to_float bounds.minlon <= Coordinate.longitude_to_float lon && ··· 111 111 let make ~name = { empty with name = Some name } 112 112 113 113 (** Get name *) 114 - let get_name t = t.name 114 + let name t = t.name 115 115 116 116 (** Get description *) 117 - let get_description t = t.desc 117 + let description t = t.desc 118 118 119 119 (** Get author *) 120 - let get_author t = t.author 120 + let author t = t.author 121 121 122 122 (** Get copyright *) 123 - let get_copyright t = t.copyright 123 + let copyright t = t.copyright 124 124 125 125 (** Get links *) 126 - let get_links t = t.links 126 + let links t = t.links 127 127 128 128 (** Get time *) 129 - let get_time t = t.time 129 + let time t = t.time 130 130 131 131 (** Get keywords *) 132 - let get_keywords t = t.keywords 132 + let keywords t = t.keywords 133 133 134 134 (** Get bounds *) 135 - let get_bounds t = t.bounds 135 + let bounds_opt t = t.bounds 136 136 137 - (** Set name *) 138 - let set_name name t = { t with name = Some name } 139 - 140 - (** Set description *) 141 - let set_description desc t = { t with desc = Some desc } 142 - 143 - (** Set author *) 144 - let set_author author t = { t with author = Some author } 145 - 146 - (** Add link *) 147 - let add_link t link = { t with links = link :: t.links } 148 - 149 - (** Functional setters for building metadata *) 150 - 151 - (** Set name *) 137 + (** Update name *) 152 138 let with_name t name = { t with name = Some name } 153 139 154 - (** Set description *) 140 + (** Update description *) 155 141 let with_description t desc = { t with desc = Some desc } 156 142 157 - (** Set keywords *) 143 + (** Update keywords *) 158 144 let with_keywords t keywords = { t with keywords = Some keywords } 159 145 160 - (** Set time *) 146 + (** Update time *) 161 147 let with_time t time = { t with time } 148 + 149 + (** Add link *) 150 + let add_link t link = { t with links = link :: t.links } 162 151 163 152 (** Add extensions *) 164 153 let add_extensions t extensions = { t with extensions = extensions @ t.extensions }
+16 -25
lib/gpx/metadata.mli
··· 34 34 val make_from_floats : minlat:float -> minlon:float -> maxlat:float -> maxlon:float -> (t, string) result 35 35 36 36 (** Get minimum corner coordinates *) 37 - val get_min_coords : t -> Coordinate.t 37 + val min_coords : t -> Coordinate.t 38 38 39 39 (** Get maximum corner coordinates *) 40 - val get_max_coords : t -> Coordinate.t 40 + val max_coords : t -> Coordinate.t 41 41 42 42 (** Get all bounds as tuple *) 43 - val get_bounds : t -> (Coordinate.latitude * Coordinate.longitude * Coordinate.latitude * Coordinate.longitude) 43 + val bounds : t -> (Coordinate.latitude * Coordinate.longitude * Coordinate.latitude * Coordinate.longitude) 44 44 45 45 (** Check if coordinate is within bounds *) 46 46 val contains : t -> Coordinate.t -> bool ··· 67 67 val make : name:string -> t 68 68 69 69 (** Get name *) 70 - val get_name : t -> string option 70 + val name : t -> string option 71 71 72 72 (** Get description *) 73 - val get_description : t -> string option 73 + val description : t -> string option 74 74 75 75 (** Get author *) 76 - val get_author : t -> Link.person option 76 + val author : t -> Link.person option 77 77 78 78 (** Get copyright *) 79 - val get_copyright : t -> Link.copyright option 79 + val copyright : t -> Link.copyright option 80 80 81 81 (** Get links *) 82 - val get_links : t -> Link.t list 82 + val links : t -> Link.t list 83 83 84 84 (** Get time *) 85 - val get_time : t -> Ptime.t option 85 + val time : t -> Ptime.t option 86 86 87 87 (** Get keywords *) 88 - val get_keywords : t -> string option 88 + val keywords : t -> string option 89 89 90 90 (** Get bounds *) 91 - val get_bounds : t -> bounds option 92 - 93 - (** Set name *) 94 - val set_name : string -> t -> t 95 - 96 - (** Set description *) 97 - val set_description : string -> t -> t 98 - 99 - (** Set author *) 100 - val set_author : Link.person -> t -> t 91 + val bounds_opt : t -> bounds option 101 92 102 - (** Functional setters for building metadata *) 93 + (** Functional operations for building metadata *) 103 94 104 - (** Set name *) 95 + (** Update name *) 105 96 val with_name : t -> string -> t 106 97 107 - (** Set description *) 98 + (** Update description *) 108 99 val with_description : t -> string -> t 109 100 110 - (** Set keywords *) 101 + (** Update keywords *) 111 102 val with_keywords : t -> string -> t 112 103 113 - (** Set time *) 104 + (** Update time *) 114 105 val with_time : t -> Ptime.t option -> t 115 106 116 107 (** Add link *)
+9 -9
lib/gpx/parser.ml
··· 228 228 let namespace = if ns = "" then None else Some ns in 229 229 let attributes = List.map (fun ((_, n), v) -> (n, v)) attrs in 230 230 let* content = parse_extension_content parser in 231 - Ok (Extension.make ~namespace ~name ~attributes ~content ()) 231 + Ok (Extension.make ?namespace ~name ~attributes ~content ()) 232 232 233 233 and parse_extension_content parser = 234 234 Buffer.clear parser.text_buffer; ··· 290 290 in 291 291 292 292 let* (version, creator) = find_gpx_root () in 293 - let gpx = Gpx_doc.empty ~creator in 294 - parse_gpx_elements parser (Gpx_doc.with_version gpx version) 293 + let gpx = Doc.empty ~creator in 294 + parse_gpx_elements parser { gpx with version } 295 295 296 296 and parse_gpx_elements parser gpx = 297 297 let rec loop gpx = ··· 301 301 (match name with 302 302 | "metadata" -> 303 303 let* metadata = parse_metadata parser in 304 - loop (Gpx_doc.with_metadata gpx metadata) 304 + loop (Doc.with_metadata gpx metadata) 305 305 | "wpt" -> 306 306 let* (lat, lon) = parse_coordinates attrs "wpt" in 307 307 let* waypoint = parse_waypoint_data parser lat lon in 308 - loop (Gpx_doc.add_waypoint gpx waypoint) 308 + loop (Doc.add_waypoint gpx waypoint) 309 309 | "rte" -> 310 310 let* route = parse_route parser in 311 - loop (Gpx_doc.add_route gpx route) 311 + loop (Doc.add_route gpx route) 312 312 | "trk" -> 313 313 let* track = parse_track parser in 314 - loop (Gpx_doc.add_track gpx track) 314 + loop (Doc.add_track gpx track) 315 315 | "extensions" -> 316 316 let* extensions = parse_extensions parser in 317 - loop (Gpx_doc.add_extensions gpx extensions) 317 + loop (Doc.add_extensions gpx extensions) 318 318 | _ -> 319 319 let* _ = skip_element parser in 320 320 loop gpx) ··· 518 518 (** Parse from string *) 519 519 let parse_string ?(validate=false) s = 520 520 let input = Xmlm.make_input (`String (0, s)) in 521 - parse ~validate input 521 + parse ~validate input
+2 -2
lib/gpx/parser.mli
··· 1 1 (** GPX streaming parser using xmlm *) 2 2 3 3 (** Parse a GPX document from an xmlm input source *) 4 - val parse : ?validate:bool -> Xmlm.input -> (Gpx_doc.t, Error.t) result 4 + val parse : ?validate:bool -> Xmlm.input -> (Doc.t, Error.t) result 5 5 6 6 (** Parse a GPX document from a string *) 7 - val parse_string : ?validate:bool -> string -> (Gpx_doc.t, Error.t) result 7 + val parse_string : ?validate:bool -> string -> (Doc.t, Error.t) result
+12 -17
lib/gpx/route.ml
··· 33 33 let make_rtept (lat_f, lon_f) = 34 34 match Waypoint.make_from_floats ~lat:lat_f ~lon:lon_f () with 35 35 | Ok wpt -> wpt 36 - | Error e -> failwith e 36 + | Error e -> invalid_arg e 37 37 in 38 38 let rtepts = List.map make_rtept coords in 39 39 { empty with name = Some name; rtepts } 40 40 41 41 (** Get route name *) 42 - let get_name t = t.name 42 + let name t = t.name 43 43 44 44 (** Get route description *) 45 - let get_description t = t.desc 45 + let description t = t.desc 46 46 47 47 (** Get route points *) 48 - let get_points t = t.rtepts 48 + let points t = t.rtepts 49 49 50 50 (** Get route point count *) 51 51 let point_count t = List.length t.rtepts 52 52 53 - (** Set name *) 54 - let set_name name t = { t with name = Some name } 55 - 56 - (** Set description *) 57 - let set_description desc t = { t with desc = Some desc } 58 53 59 54 (** Clear all points *) 60 55 let clear_points t = { t with rtepts = [] } ··· 105 100 | [] -> None 106 101 | p :: _ -> Some p 107 102 108 - (** {2 Functional Setters} *) 103 + (** {2 Functional Operations} *) 109 104 110 - (** Set name *) 105 + (** Update name *) 111 106 let with_name t name = { t with name = Some name } 112 107 113 - (** Set comment *) 108 + (** Update comment *) 114 109 let with_comment t cmt = { t with cmt = Some cmt } 115 110 116 - (** Set description *) 111 + (** Update description *) 117 112 let with_description t desc = { t with desc = Some desc } 118 113 119 - (** Set source *) 114 + (** Update source *) 120 115 let with_source t src = { t with src = Some src } 121 116 122 - (** Set number *) 117 + (** Update number *) 123 118 let with_number t number = { t with number = Some number } 124 119 125 - (** Set type *) 120 + (** Update type *) 126 121 let with_type t type_ = { t with type_ = Some type_ } 127 122 128 123 (** Add point *) ··· 150 145 let pp ppf t = 151 146 match t.name with 152 147 | Some name -> Format.fprintf ppf "\"%s\" (%d points)" name (point_count t) 153 - | None -> Format.fprintf ppf "(unnamed route, %d points)" (point_count t) 148 + | None -> Format.fprintf ppf "(unnamed route, %d points)" (point_count t)
+13 -19
lib/gpx/route.mli
··· 27 27 (** Create route from coordinate list. 28 28 @param name Route name 29 29 @param coords List of (latitude, longitude) pairs 30 - @raises Failure on invalid coordinates *) 30 + @raises Invalid_argument on invalid coordinates *) 31 31 val make_from_coords : name:string -> (float * float) list -> t 32 32 33 33 (** {2 Route Properties} *) 34 34 35 35 (** Get route name *) 36 - val get_name : t -> string option 36 + val name : t -> string option 37 37 38 38 (** Get route description *) 39 - val get_description : t -> string option 39 + val description : t -> string option 40 40 41 41 (** Get route points *) 42 - val get_points : t -> point list 42 + val points : t -> point list 43 43 44 44 (** Get route point count *) 45 45 val point_count : t -> int ··· 49 49 50 50 (** {2 Route Modification} *) 51 51 52 - (** Set name *) 53 - val set_name : string -> t -> t 54 - 55 - (** Set description *) 56 - val set_description : string -> t -> t 57 - 58 52 (** Clear all points *) 59 53 val clear_points : t -> t 60 54 ··· 72 66 (** Get last point *) 73 67 val last_point : t -> point option 74 68 75 - (** {2 Functional Setters} *) 69 + (** {2 Functional Operations} *) 76 70 77 - (** Set name *) 71 + (** Update name *) 78 72 val with_name : t -> string -> t 79 73 80 - (** Set comment *) 81 - val with_comment : t -> string -> t 74 + (** Update comment *) 75 + val with_comment : t -> string -> t 82 76 83 - (** Set description *) 77 + (** Update description *) 84 78 val with_description : t -> string -> t 85 79 86 - (** Set source *) 80 + (** Update source *) 87 81 val with_source : t -> string -> t 88 82 89 - (** Set number *) 83 + (** Update number *) 90 84 val with_number : t -> int -> t 91 85 92 - (** Set type *) 86 + (** Update type *) 93 87 val with_type : t -> string -> t 94 88 95 89 (** Add point *) ··· 110 104 val equal : t -> t -> bool 111 105 112 106 (** Pretty print route *) 113 - val pp : Format.formatter -> t -> unit 107 + val pp : Format.formatter -> t -> unit
+13 -18
lib/gpx/track.ml
··· 38 38 let make_trkpt (lat_f, lon_f) = 39 39 match Waypoint.make_from_floats ~lat:lat_f ~lon:lon_f () with 40 40 | Ok wpt -> wpt 41 - | Error e -> failwith e 41 + | Error e -> invalid_arg e 42 42 in 43 43 let trkpts = List.map make_trkpt coords in 44 44 { trkpts; extensions = [] } 45 45 46 46 (** Get points *) 47 - let get_points t = t.trkpts 47 + let points t = t.trkpts 48 48 49 49 (** Get point count *) 50 50 let point_count t = List.length t.trkpts ··· 104 104 { empty with name = Some name; trksegs = [segment] } 105 105 106 106 (** Get track name *) 107 - let get_name t = t.name 107 + let name t = t.name 108 108 109 109 (** Get track description *) 110 - let get_description t = t.desc 110 + let description t = t.desc 111 111 112 112 (** Get track segments *) 113 - let get_segments t = t.trksegs 113 + let segments t = t.trksegs 114 114 115 115 (** Get segment count *) 116 116 let segment_count t = List.length t.trksegs ··· 119 119 let point_count t = 120 120 List.fold_left (fun acc seg -> acc + Segment.point_count seg) 0 t.trksegs 121 121 122 - (** Set name *) 123 - let set_name name t = { t with name = Some name } 124 - 125 - (** Set description *) 126 - let set_description desc t = { t with desc = Some desc } 127 122 128 123 (** Clear all segments *) 129 124 let clear_segments t = { t with trksegs = [] } ··· 172 167 (** Test track equality *) 173 168 let equal t1 t2 = compare t1 t2 = 0 174 169 175 - (** {2 Functional Setters} *) 170 + (** {2 Functional Operations} *) 176 171 177 - (** Set name *) 172 + (** Update name *) 178 173 let with_name t name = { t with name = Some name } 179 174 180 - (** Set comment *) 175 + (** Update comment *) 181 176 let with_comment t cmt = { t with cmt = Some cmt } 182 177 183 - (** Set description *) 178 + (** Update description *) 184 179 let with_description t desc = { t with desc = Some desc } 185 180 186 - (** Set source *) 181 + (** Update source *) 187 182 let with_source t src = { t with src = Some src } 188 183 189 - (** Set number *) 184 + (** Update number *) 190 185 let with_number t number = { t with number = Some number } 191 186 192 - (** Set type *) 187 + (** Update type *) 193 188 let with_type t type_ = { t with type_ = Some type_ } 194 189 195 190 (** Add segment *) ··· 207 202 | Some name -> Format.fprintf ppf "\"%s\" (%d segments, %d points)" 208 203 name (segment_count t) (point_count t) 209 204 | None -> Format.fprintf ppf "(unnamed track, %d segments, %d points)" 210 - (segment_count t) (point_count t) 205 + (segment_count t) (point_count t)
+13 -19
lib/gpx/track.mli
··· 34 34 val make : point list -> t 35 35 36 36 (** Create segment from coordinate list. 37 - @raises Failure on invalid coordinates *) 37 + @raises Invalid_argument on invalid coordinates *) 38 38 val make_from_coords : (float * float) list -> t 39 39 40 40 (** Get points *) 41 - val get_points : t -> point list 41 + val points : t -> point list 42 42 43 43 (** Get point count *) 44 44 val point_count : t -> int ··· 91 91 (** {2 Track Properties} *) 92 92 93 93 (** Get track name *) 94 - val get_name : t -> string option 94 + val name : t -> string option 95 95 96 96 (** Get track description *) 97 - val get_description : t -> string option 97 + val description : t -> string option 98 98 99 99 (** Get track segments *) 100 - val get_segments : t -> segment list 100 + val segments : t -> segment list 101 101 102 102 (** Get segment count *) 103 103 val segment_count : t -> int ··· 109 109 val is_empty : t -> bool 110 110 111 111 (** {2 Track Modification} *) 112 - 113 - (** Set name *) 114 - val set_name : string -> t -> t 115 - 116 - (** Set description *) 117 - val set_description : string -> t -> t 118 112 119 113 (** Clear all segments *) 120 114 val clear_segments : t -> t ··· 144 138 (** Test track equality *) 145 139 val equal : t -> t -> bool 146 140 147 - (** {2 Functional Setters} *) 141 + (** {2 Functional Operations} *) 148 142 149 - (** Set name *) 143 + (** Update name *) 150 144 val with_name : t -> string -> t 151 145 152 - (** Set comment *) 146 + (** Update comment *) 153 147 val with_comment : t -> string -> t 154 148 155 - (** Set description *) 149 + (** Update description *) 156 150 val with_description : t -> string -> t 157 151 158 - (** Set source *) 152 + (** Update source *) 159 153 val with_source : t -> string -> t 160 154 161 - (** Set number *) 155 + (** Update number *) 162 156 val with_number : t -> int -> t 163 157 164 - (** Set type *) 158 + (** Update type *) 165 159 val with_type : t -> string -> t 166 160 167 161 (** Add segment *) ··· 174 168 val add_extensions : t -> Extension.t list -> t 175 169 176 170 (** Pretty print track *) 177 - val pp : Format.formatter -> t -> unit 171 + val pp : Format.formatter -> t -> unit
+23 -23
lib/gpx/validate.ml
··· 29 29 let issues = ref [] in 30 30 31 31 (* Check for negative satellite count *) 32 - (match Waypoint.get_sat wpt with 32 + (match Waypoint.sat wpt with 33 33 | Some sat when sat < 0 -> 34 34 issues := make_warning ~location ("Negative satellite count: " ^ string_of_int sat) :: !issues 35 35 | _ -> ()); ··· 44 44 | _ -> () 45 45 in 46 46 47 - check_precision "hdop" (Waypoint.get_hdop wpt); 48 - check_precision "vdop" (Waypoint.get_vdop wpt); 49 - check_precision "pdop" (Waypoint.get_pdop wpt); 47 + check_precision "hdop" (Waypoint.hdop wpt); 48 + check_precision "vdop" (Waypoint.vdop wpt); 49 + check_precision "pdop" (Waypoint.pdop wpt); 50 50 51 51 (* Check elevation reasonableness *) 52 - (match Waypoint.get_elevation wpt with 52 + (match Waypoint.elevation wpt with 53 53 | Some ele when ele < -15000.0 -> 54 54 issues := make_warning ~location (Printf.sprintf "Very low elevation: %.2f m" ele) :: !issues 55 55 | Some ele when ele > 15000.0 -> ··· 57 57 | _ -> ()); 58 58 59 59 (* Check DGPS age *) 60 - (match Waypoint.get_ageofdgpsdata wpt with 60 + (match Waypoint.ageofdgpsdata wpt with 61 61 | Some age when age < 0.0 -> 62 62 issues := make_error ~location "Negative DGPS age" :: !issues 63 63 | _ -> ()); ··· 69 69 let issues = ref [] in 70 70 let location = "bounds" in 71 71 72 - let (minlat, minlon, maxlat, maxlon) = Metadata.Bounds.get_bounds bounds in 72 + let (minlat, minlon, maxlat, maxlon) = Metadata.Bounds.bounds bounds in 73 73 if Coordinate.latitude_to_float minlat >= Coordinate.latitude_to_float maxlat then 74 74 issues := make_error ~location "minlat must be less than maxlat" :: !issues; 75 75 ··· 83 83 let issues = ref [] in 84 84 85 85 (* Validate bounds if present *) 86 - (match Metadata.get_bounds metadata with 86 + (match Metadata.bounds_opt metadata with 87 87 | Some bounds -> issues := validate_bounds bounds @ !issues 88 88 | None -> ()); 89 89 90 90 (* Check for reasonable copyright year *) 91 - (match Metadata.get_copyright metadata with 91 + (match Metadata.copyright metadata with 92 92 | Some copyright -> 93 - (match Link.get_copyright_year copyright with 93 + (match Link.copyright_year copyright with 94 94 | Some year when year < 1900 || year > 2100 -> 95 95 issues := make_warning ~location:"metadata.copyright" 96 96 (Printf.sprintf "Unusual copyright year: %d" year) :: !issues ··· 105 105 let location = "route" in 106 106 107 107 (* Check for empty route *) 108 - let points = Route.get_points route in 108 + let points = Route.points route in 109 109 if points = [] then 110 110 issues := make_warning ~location "Route has no points" :: !issues; 111 111 ··· 123 123 let location = Printf.sprintf "track.trkseg[%d]" seg_idx in 124 124 125 125 (* Check for empty segment *) 126 - let points = Track.Segment.get_points trkseg in 126 + let points = Track.Segment.points trkseg in 127 127 if points = [] then 128 128 issues := make_warning ~location "Track segment has no points" :: !issues; 129 129 ··· 137 137 let rec check_time_order prev_time = function 138 138 | [] -> () 139 139 | trkpt :: rest -> 140 - (match (prev_time, Waypoint.get_time trkpt) with 140 + (match (prev_time, Waypoint.time trkpt) with 141 141 | (Some prev, Some curr) when Ptime.compare prev curr > 0 -> 142 142 issues := make_warning ~location "Track points not in chronological order" :: !issues 143 143 | _ -> ()); 144 - check_time_order (Waypoint.get_time trkpt) rest 144 + check_time_order (Waypoint.time trkpt) rest 145 145 in 146 146 check_time_order None points; 147 147 ··· 153 153 let location = "track" in 154 154 155 155 (* Check for empty track *) 156 - let segments = Track.get_segments track in 156 + let segments = Track.segments track in 157 157 if segments = [] then 158 158 issues := make_warning ~location "Track has no segments" :: !issues; 159 159 ··· 169 169 let issues = ref [] in 170 170 171 171 (* Check GPX version *) 172 - let version = Gpx_doc.get_version gpx in 172 + let version = Doc.version gpx in 173 173 if version <> "1.0" && version <> "1.1" then 174 174 issues := make_error ~location:"gpx" 175 175 (Printf.sprintf "Unsupported GPX version: %s (supported: 1.0, 1.1)" version) :: !issues ··· 178 178 "GPX 1.0 detected - consider upgrading to GPX 1.1 for better compatibility" :: !issues; 179 179 180 180 (* Check for empty creator *) 181 - let creator = Gpx_doc.get_creator gpx in 181 + let creator = Doc.creator gpx in 182 182 if String.trim creator = "" then 183 183 issues := make_error ~location:"gpx" "Creator cannot be empty" :: !issues; 184 184 185 185 (* Validate metadata *) 186 - (match Gpx_doc.get_metadata gpx with 186 + (match Doc.metadata gpx with 187 187 | Some metadata -> issues := validate_metadata metadata @ !issues 188 188 | None -> ()); 189 189 190 190 (* Validate waypoints *) 191 - let waypoints = Gpx_doc.get_waypoints gpx in 191 + let waypoints = Doc.waypoints gpx in 192 192 List.iteri (fun i wpt -> 193 193 let location = Printf.sprintf "waypoint[%d]" i in 194 194 issues := validate_waypoint_data wpt location @ !issues 195 195 ) waypoints; 196 196 197 197 (* Validate routes *) 198 - let routes = Gpx_doc.get_routes gpx in 198 + let routes = Doc.routes gpx in 199 199 List.iteri (fun _i route -> 200 200 issues := validate_route route @ !issues 201 201 ) routes; 202 202 203 203 (* Validate tracks *) 204 - let tracks = Gpx_doc.get_tracks gpx in 204 + let tracks = Doc.tracks gpx in 205 205 List.iteri (fun _i track -> 206 206 issues := validate_track track @ !issues 207 207 ) tracks; ··· 221 221 result.is_valid 222 222 223 223 (** Get only error messages *) 224 - let get_errors gpx = 224 + let errors gpx = 225 225 let result = validate_gpx gpx in 226 226 List.filter (fun issue -> issue.level = `Error) result.issues 227 227 228 228 (** Get only warning messages *) 229 - let get_warnings gpx = 229 + let warnings gpx = 230 230 let result = validate_gpx gpx in 231 231 List.filter (fun issue -> issue.level = `Warning) result.issues 232 232
+4 -4
lib/gpx/validate.mli
··· 14 14 } 15 15 16 16 (** Validate a complete GPX document *) 17 - val validate_gpx : Gpx_doc.t -> validation_result 17 + val validate_gpx : Doc.t -> validation_result 18 18 19 19 (** Quick validation - returns true if document is valid *) 20 - val is_valid : Gpx_doc.t -> bool 20 + val is_valid : Doc.t -> bool 21 21 22 22 (** Get only error messages *) 23 - val get_errors : Gpx_doc.t -> validation_issue list 23 + val errors : Doc.t -> validation_issue list 24 24 25 25 (** Get only warning messages *) 26 - val get_warnings : Gpx_doc.t -> validation_issue list 26 + val warnings : Doc.t -> validation_issue list 27 27 28 28 (** Format validation issue for display *) 29 29 val format_issue : validation_issue -> string
+39 -53
lib/gpx/waypoint.ml
··· 86 86 | Error e, _ | _, Error e -> Error e 87 87 88 88 (** Get coordinate pair *) 89 - let get_coordinate t = Coordinate.make t.lat t.lon 89 + let coordinate t = Coordinate.make t.lat t.lon 90 90 91 91 (** Get latitude *) 92 - let get_lat t = t.lat 92 + let lat t = t.lat 93 93 94 94 (** Get longitude *) 95 - let get_lon t = t.lon 95 + let lon t = t.lon 96 96 97 97 (** Get coordinate as float pair *) 98 98 let to_floats t = (Coordinate.latitude_to_float t.lat, Coordinate.longitude_to_float t.lon) 99 99 100 100 (** Get elevation *) 101 - let get_elevation t = t.ele 101 + let elevation t = t.ele 102 102 103 103 (** Get time *) 104 - let get_time t = t.time 104 + let time t = t.time 105 105 106 106 (** Get name *) 107 - let get_name t = t.name 107 + let name t = t.name 108 108 109 109 (** Get description *) 110 - let get_description t = t.desc 110 + let description t = t.desc 111 111 112 112 (** Get comment *) 113 - let get_comment t = t.cmt 113 + let comment t = t.cmt 114 114 115 115 (** Get source *) 116 - let get_source t = t.src 116 + let source t = t.src 117 117 118 118 (** Get symbol *) 119 - let get_symbol t = t.sym 119 + let symbol t = t.sym 120 120 121 121 (** Get type *) 122 - let get_type t = t.type_ 122 + let type_ t = t.type_ 123 123 124 124 (** Get fix type *) 125 - let get_fix t = t.fix 125 + let fix t = t.fix 126 126 127 127 (** Get satellite count *) 128 - let get_sat t = t.sat 128 + let sat t = t.sat 129 129 130 130 (** Get horizontal dilution of precision *) 131 - let get_hdop t = t.hdop 131 + let hdop t = t.hdop 132 132 133 133 (** Get vertical dilution of precision *) 134 - let get_vdop t = t.vdop 134 + let vdop t = t.vdop 135 135 136 136 (** Get position dilution of precision *) 137 - let get_pdop t = t.pdop 137 + let pdop t = t.pdop 138 138 139 139 (** Get magnetic variation *) 140 - let get_magvar t = t.magvar 140 + let magvar t = t.magvar 141 141 142 142 (** Get geoid height *) 143 - let get_geoidheight t = t.geoidheight 143 + let geoidheight t = t.geoidheight 144 144 145 145 (** Get age of DGPS data *) 146 - let get_ageofdgpsdata t = t.ageofdgpsdata 146 + let ageofdgpsdata t = t.ageofdgpsdata 147 147 148 148 (** Get DGPS ID *) 149 - let get_dgpsid t = t.dgpsid 149 + let dgpsid t = t.dgpsid 150 150 151 151 (** Get links *) 152 - let get_links t = t.links 152 + let links t = t.links 153 153 154 154 (** Get extensions *) 155 - let get_extensions t = t.extensions 156 - 157 - (** Set name *) 158 - let set_name name t = { t with name = Some name } 159 - 160 - (** Set description *) 161 - let set_description desc t = { t with desc = Some desc } 162 - 163 - (** Set elevation *) 164 - let set_elevation ele t = { t with ele = Some ele } 165 - 166 - (** Set time *) 167 - let set_time time t = { t with time = Some time } 155 + let extensions t = t.extensions 168 156 169 - (** Functional setters for building waypoints *) 170 - 171 - (** Set elevation *) 157 + (** Update elevation *) 172 158 let with_elevation t ele = { t with ele = Some ele } 173 159 174 - (** Set time *) 160 + (** Update time *) 175 161 let with_time t time = { t with time } 176 162 177 - (** Set name *) 163 + (** Update name *) 178 164 let with_name t name = { t with name = Some name } 179 165 180 - (** Set comment *) 166 + (** Update comment *) 181 167 let with_comment t cmt = { t with cmt = Some cmt } 182 168 183 - (** Set description *) 169 + (** Update description *) 184 170 let with_description t desc = { t with desc = Some desc } 185 171 186 - (** Set source *) 172 + (** Update source *) 187 173 let with_source t src = { t with src = Some src } 188 174 189 - (** Set symbol *) 175 + (** Update symbol *) 190 176 let with_symbol t sym = { t with sym = Some sym } 191 177 192 - (** Set type *) 178 + (** Update type *) 193 179 let with_type t type_ = { t with type_ = Some type_ } 194 180 195 - (** Set fix type *) 181 + (** Update fix *) 196 182 let with_fix t fix = { t with fix } 197 183 198 - (** Set satellite count *) 184 + (** Update satellite count *) 199 185 let with_sat t sat = { t with sat = Some sat } 200 186 201 - (** Set horizontal dilution of precision *) 187 + (** Update HDOP *) 202 188 let with_hdop t hdop = { t with hdop = Some hdop } 203 189 204 - (** Set vertical dilution of precision *) 190 + (** Update VDOP *) 205 191 let with_vdop t vdop = { t with vdop = Some vdop } 206 192 207 - (** Set position dilution of precision *) 193 + (** Update PDOP *) 208 194 let with_pdop t pdop = { t with pdop = Some pdop } 209 195 210 - (** Set magnetic variation *) 196 + (** Update magnetic variation *) 211 197 let with_magvar t magvar = { t with magvar = Some magvar } 212 198 213 - (** Set geoid height *) 199 + (** Update geoid height *) 214 200 let with_geoidheight t geoidheight = { t with geoidheight = Some geoidheight } 215 201 216 - (** Set age of DGPS data *) 202 + (** Update age of DGPS data *) 217 203 let with_ageofdgpsdata t ageofdgpsdata = { t with ageofdgpsdata = Some ageofdgpsdata } 218 204 219 - (** Set DGPS ID *) 205 + (** Update DGPS ID *) 220 206 let with_dgpsid t dgpsid = { t with dgpsid = Some dgpsid } 221 207 222 208 (** Add link *)
+40 -52
lib/gpx/waypoint.mli
··· 56 56 val make_from_floats : lat:float -> lon:float -> ?name:string -> ?desc:string -> unit -> (t, string) result 57 57 58 58 (** Get coordinate pair *) 59 - val get_coordinate : t -> Coordinate.t 59 + val coordinate : t -> Coordinate.t 60 60 61 61 (** Get latitude *) 62 - val get_lat : t -> Coordinate.latitude 62 + val lat : t -> Coordinate.latitude 63 63 64 64 (** Get longitude *) 65 - val get_lon : t -> Coordinate.longitude 65 + val lon : t -> Coordinate.longitude 66 66 67 67 (** Get coordinate as float pair *) 68 68 val to_floats : t -> float * float 69 69 70 70 (** Get elevation *) 71 - val get_elevation : t -> float option 71 + val elevation : t -> float option 72 72 73 73 (** Get time *) 74 - val get_time : t -> Ptime.t option 74 + val time : t -> Ptime.t option 75 75 76 76 (** Get name *) 77 - val get_name : t -> string option 77 + val name : t -> string option 78 78 79 79 (** Get description *) 80 - val get_description : t -> string option 80 + val description : t -> string option 81 81 82 82 (** Get comment *) 83 - val get_comment : t -> string option 83 + val comment : t -> string option 84 84 85 85 (** Get source *) 86 - val get_source : t -> string option 86 + val source : t -> string option 87 87 88 88 (** Get symbol *) 89 - val get_symbol : t -> string option 89 + val symbol : t -> string option 90 90 91 91 (** Get type *) 92 - val get_type : t -> string option 92 + val type_ : t -> string option 93 93 94 94 (** Get fix type *) 95 - val get_fix : t -> fix_type option 95 + val fix : t -> fix_type option 96 96 97 97 (** Get satellite count *) 98 - val get_sat : t -> int option 98 + val sat : t -> int option 99 99 100 100 (** Get horizontal dilution of precision *) 101 - val get_hdop : t -> float option 101 + val hdop : t -> float option 102 102 103 103 (** Get vertical dilution of precision *) 104 - val get_vdop : t -> float option 104 + val vdop : t -> float option 105 105 106 106 (** Get position dilution of precision *) 107 - val get_pdop : t -> float option 107 + val pdop : t -> float option 108 108 109 109 (** Get magnetic variation *) 110 - val get_magvar : t -> Coordinate.degrees option 110 + val magvar : t -> Coordinate.degrees option 111 111 112 112 (** Get geoid height *) 113 - val get_geoidheight : t -> float option 113 + val geoidheight : t -> float option 114 114 115 115 (** Get age of DGPS data *) 116 - val get_ageofdgpsdata : t -> float option 116 + val ageofdgpsdata : t -> float option 117 117 118 118 (** Get DGPS ID *) 119 - val get_dgpsid : t -> int option 119 + val dgpsid : t -> int option 120 120 121 121 (** Get links *) 122 - val get_links : t -> Link.t list 122 + val links : t -> Link.t list 123 123 124 124 (** Get extensions *) 125 - val get_extensions : t -> Extension.t list 126 - 127 - (** Set name *) 128 - val set_name : string -> t -> t 129 - 130 - (** Set description *) 131 - val set_description : string -> t -> t 132 - 133 - (** Set elevation *) 134 - val set_elevation : float -> t -> t 125 + val extensions : t -> Extension.t list 135 126 136 - (** Set time *) 137 - val set_time : Ptime.t -> t -> t 127 + (** Functional operations for building waypoints *) 138 128 139 - (** Functional setters for building waypoints *) 140 - 141 - (** Set elevation *) 129 + (** Update elevation *) 142 130 val with_elevation : t -> float -> t 143 131 144 - (** Set time *) 132 + (** Update time *) 145 133 val with_time : t -> Ptime.t option -> t 146 134 147 - (** Set name *) 135 + (** Update name *) 148 136 val with_name : t -> string -> t 149 137 150 - (** Set comment *) 138 + (** Update comment *) 151 139 val with_comment : t -> string -> t 152 140 153 - (** Set description *) 141 + (** Update description *) 154 142 val with_description : t -> string -> t 155 143 156 - (** Set source *) 144 + (** Update source *) 157 145 val with_source : t -> string -> t 158 146 159 - (** Set symbol *) 147 + (** Update symbol *) 160 148 val with_symbol : t -> string -> t 161 149 162 - (** Set type *) 150 + (** Update type *) 163 151 val with_type : t -> string -> t 164 152 165 - (** Set fix type *) 153 + (** Update fix *) 166 154 val with_fix : t -> fix_type option -> t 167 155 168 - (** Set satellite count *) 156 + (** Update satellite count *) 169 157 val with_sat : t -> int -> t 170 158 171 - (** Set horizontal dilution of precision *) 159 + (** Update HDOP *) 172 160 val with_hdop : t -> float -> t 173 161 174 - (** Set vertical dilution of precision *) 162 + (** Update VDOP *) 175 163 val with_vdop : t -> float -> t 176 164 177 - (** Set position dilution of precision *) 165 + (** Update PDOP *) 178 166 val with_pdop : t -> float -> t 179 167 180 - (** Set magnetic variation *) 168 + (** Update magnetic variation *) 181 169 val with_magvar : t -> Coordinate.degrees -> t 182 170 183 - (** Set geoid height *) 171 + (** Update geoid height *) 184 172 val with_geoidheight : t -> float -> t 185 173 186 - (** Set age of DGPS data *) 174 + (** Update age of DGPS data *) 187 175 val with_ageofdgpsdata : t -> float -> t 188 176 189 - (** Set DGPS ID *) 177 + (** Update DGPS ID *) 190 178 val with_dgpsid : t -> int -> t 191 179 192 180 (** Add link *)
+11 -11
lib/gpx/writer.ml
··· 42 42 let result = 43 43 try 44 44 (* Write XML declaration and GPX root element *) 45 - let version = Gpx_doc.get_version gpx in 46 - let creator = Gpx_doc.get_creator gpx in 45 + let version = Doc.version gpx in 46 + let creator = Doc.creator gpx in 47 47 let attrs = [ 48 48 (("", "version"), version); 49 49 (("", "creator"), creator); ··· 55 55 let* () = output_element_start writer "gpx" attrs in 56 56 57 57 (* Write metadata if present *) 58 - let* () = match Gpx_doc.get_metadata gpx with 58 + let* () = match Doc.metadata gpx with 59 59 | Some metadata -> 60 60 let* () = output_element_start writer "metadata" [] in 61 61 (* Write basic metadata fields *) 62 - let* () = output_optional_text_element writer "name" (Metadata.get_name metadata) in 63 - let* () = output_optional_text_element writer "desc" (Metadata.get_description metadata) in 64 - let* () = output_optional_text_element writer "keywords" (Metadata.get_keywords metadata) in 62 + let* () = output_optional_text_element writer "name" (Metadata.name metadata) in 63 + let* () = output_optional_text_element writer "desc" (Metadata.description metadata) in 64 + let* () = output_optional_text_element writer "keywords" (Metadata.keywords metadata) in 65 65 output_element_end writer 66 66 | None -> Ok () 67 67 in 68 68 69 69 (* Write waypoints *) 70 - let waypoints = Gpx_doc.get_waypoints gpx in 70 + let waypoints = Doc.waypoints gpx in 71 71 let rec write_waypoints = function 72 72 | [] -> Ok () 73 73 | wpt :: rest -> 74 - let lat = Coordinate.latitude_to_float (Waypoint.get_lat wpt) in 75 - let lon = Coordinate.longitude_to_float (Waypoint.get_lon wpt) in 74 + let lat = Coordinate.latitude_to_float (Waypoint.lat wpt) in 75 + let lon = Coordinate.longitude_to_float (Waypoint.lon wpt) in 76 76 let attrs = [ 77 77 (("", "lat"), Printf.sprintf "%.6f" lat); 78 78 (("", "lon"), Printf.sprintf "%.6f" lon); 79 79 ] in 80 80 let* () = output_element_start writer "wpt" attrs in 81 - let* () = output_optional_text_element writer "name" (Waypoint.get_name wpt) in 82 - let* () = output_optional_text_element writer "desc" (Waypoint.get_description wpt) in 81 + let* () = output_optional_text_element writer "name" (Waypoint.name wpt) in 82 + let* () = output_optional_text_element writer "desc" (Waypoint.description wpt) in 83 83 let* () = output_element_end writer in 84 84 write_waypoints rest 85 85 in
+2 -2
lib/gpx/writer.mli
··· 1 1 (** GPX streaming writer using xmlm *) 2 2 3 3 (** Write a GPX document to an xmlm output destination *) 4 - val write : ?validate:bool -> Xmlm.dest -> Gpx_doc.t -> (unit, Error.t) result 4 + val write : ?validate:bool -> Xmlm.dest -> Doc.t -> (unit, Error.t) result 5 5 6 6 (** Write a GPX document to a string *) 7 - val write_string : ?validate:bool -> Gpx_doc.t -> (string, Error.t) result 7 + val write_string : ?validate:bool -> Doc.t -> (string, Error.t) result
+12 -13
lib/gpx_eio/gpx_eio.ml
··· 25 25 match (Gpx.Coordinate.latitude lat, Gpx.Coordinate.longitude lon) with 26 26 | (Ok lat, Ok lon) -> 27 27 let wpt = Gpx.Waypoint.make lat lon in 28 - Gpx.Waypoint.with_name wpt (Option.value name ~default:"") |> 29 - fun wpt -> Gpx.Waypoint.with_description wpt (Option.value desc ~default:"") 28 + { wpt with name; desc } 30 29 | (Error e, _) | (_, Error e) -> failwith ("Invalid coordinate: " ^ e) 31 30 32 31 (** Create simple track from coordinate list *) ··· 48 47 49 48 (** Count total points in GPX *) 50 49 let count_points gpx = 51 - let waypoints = Gpx.Gpx_doc.get_waypoints gpx in 52 - let routes = Gpx.Gpx_doc.get_routes gpx in 53 - let tracks = Gpx.Gpx_doc.get_tracks gpx in 50 + let waypoints = Gpx.Doc.waypoints gpx in 51 + let routes = Gpx.Doc.routes gpx in 52 + let tracks = Gpx.Doc.tracks gpx in 54 53 List.length waypoints + 55 - List.fold_left (fun acc r -> acc + List.length (Gpx.Route.get_points r)) 0 routes + 54 + List.fold_left (fun acc r -> acc + List.length (Gpx.Route.points r)) 0 routes + 56 55 List.fold_left (fun acc t -> acc + Gpx.Track.point_count t) 0 tracks 57 56 58 57 (** Get GPX statistics *) ··· 65 64 has_time : bool; 66 65 } 67 66 68 - let get_stats gpx = 69 - let waypoints = Gpx.Gpx_doc.get_waypoints gpx in 70 - let routes = Gpx.Gpx_doc.get_routes gpx in 71 - let tracks = Gpx.Gpx_doc.get_tracks gpx in 67 + let stats gpx = 68 + let waypoints = Gpx.Doc.waypoints gpx in 69 + let routes = Gpx.Doc.routes gpx in 70 + let tracks = Gpx.Doc.tracks gpx in 72 71 { 73 72 waypoint_count = List.length waypoints; 74 73 route_count = List.length routes; 75 74 track_count = List.length tracks; 76 75 total_points = count_points gpx; 77 - has_elevation = List.exists (fun w -> Gpx.Waypoint.get_elevation w <> None) waypoints; 78 - has_time = List.exists (fun w -> Gpx.Waypoint.get_time w <> None) waypoints; 76 + has_elevation = List.exists (fun w -> Gpx.Waypoint.elevation w <> None) waypoints; 77 + has_time = List.exists (fun w -> Gpx.Waypoint.time w <> None) waypoints; 79 78 } 80 79 81 80 (** Pretty print GPX statistics *) 82 81 let print_stats gpx = 83 - let stats = get_stats gpx in 82 + let stats = stats gpx in 84 83 Printf.printf "GPX Statistics:\\n"; 85 84 Printf.printf " Waypoints: %d\\n" stats.waypoint_count; 86 85 Printf.printf " Routes: %d\\n" stats.route_count;
+1 -1
lib/gpx_eio/gpx_eio.mli
··· 142 142 (** Get GPX document statistics. 143 143 @param gpx GPX document 144 144 @return Statistics summary *) 145 - val get_stats : Gpx.t -> gpx_stats 145 + val stats : Gpx.t -> gpx_stats 146 146 147 147 (** Print GPX statistics to stdout. 148 148 @param gpx GPX document *)
+12 -13
lib/gpx_unix/gpx_unix.ml
··· 37 37 match (Coordinate.latitude lat, Coordinate.longitude lon) with 38 38 | (Ok lat, Ok lon) -> 39 39 let wpt = Waypoint.make lat lon in 40 - let wpt = match name with Some n -> Waypoint.with_name wpt n | None -> wpt in 41 - let wpt = match desc with Some d -> Waypoint.with_description wpt d | None -> wpt in 40 + let wpt = { wpt with name; desc } in 42 41 Ok wpt 43 42 | (Error e, _) | (_, Error e) -> Error (Gpx.Error.invalid_coordinate e) 44 43 ··· 87 86 88 87 (** Count total points in GPX *) 89 88 let count_points gpx = 90 - let waypoints = Gpx_doc.get_waypoints gpx in 91 - let routes = Gpx_doc.get_routes gpx in 92 - let tracks = Gpx_doc.get_tracks gpx in 89 + let waypoints = Doc.waypoints gpx in 90 + let routes = Doc.routes gpx in 91 + let tracks = Doc.tracks gpx in 93 92 List.length waypoints + 94 - List.fold_left (fun acc r -> acc + List.length (Route.get_points r)) 0 routes + 93 + List.fold_left (fun acc r -> acc + List.length (Route.points r)) 0 routes + 95 94 List.fold_left (fun acc t -> acc + Track.point_count t) 0 tracks 96 95 97 96 (** Get GPX statistics *) ··· 104 103 has_time : bool; 105 104 } 106 105 107 - let get_stats gpx = 108 - let waypoints = Gpx_doc.get_waypoints gpx in 109 - let routes = Gpx_doc.get_routes gpx in 110 - let tracks = Gpx_doc.get_tracks gpx in 106 + let stats gpx = 107 + let waypoints = Doc.waypoints gpx in 108 + let routes = Doc.routes gpx in 109 + let tracks = Doc.tracks gpx in 111 110 { 112 111 waypoint_count = List.length waypoints; 113 112 route_count = List.length routes; 114 113 track_count = List.length tracks; 115 114 total_points = count_points gpx; 116 - has_elevation = List.exists (fun w -> Waypoint.get_elevation w <> None) waypoints; 117 - has_time = List.exists (fun w -> Waypoint.get_time w <> None) waypoints; 115 + has_elevation = List.exists (fun w -> Waypoint.elevation w <> None) waypoints; 116 + has_time = List.exists (fun w -> Waypoint.time w <> None) waypoints; 118 117 } 119 118 120 119 (** Pretty print GPX statistics *) 121 120 let print_stats gpx = 122 - let stats = get_stats gpx in 121 + let stats = stats gpx in 123 122 Printf.printf "GPX Statistics:\n"; 124 123 Printf.printf " Waypoints: %d\n" stats.waypoint_count; 125 124 Printf.printf " Routes: %d\n" stats.route_count;
+1 -1
lib/gpx_unix/gpx_unix.mli
··· 61 61 } 62 62 63 63 (** Get GPX statistics *) 64 - val get_stats : t -> gpx_stats 64 + val stats : t -> gpx_stats 65 65 66 66 (** Pretty print GPX statistics *) 67 67 val print_stats : t -> unit
+34 -34
test/test_corpus.ml
··· 20 20 let content = read_test_file "simple_waypoints.gpx" in 21 21 match parse_string content with 22 22 | Ok gpx -> 23 - let waypoints = Gpx_doc.get_waypoints gpx in 23 + let waypoints = Doc.waypoints gpx in 24 24 Printf.printf "Waypoints count: %d\n" (List.length waypoints); 25 25 Printf.printf "First waypoint name: %s\n" 26 26 (match waypoints with 27 - | wpt :: _ -> (match Waypoint.get_name wpt with Some n -> n | None -> "None") 27 + | wpt :: _ -> (match Waypoint.name wpt with Some n -> n | None -> "None") 28 28 | [] -> "None"); 29 - Printf.printf "Creator: %s\n" (Gpx_doc.get_creator gpx); 29 + Printf.printf "Creator: %s\n" (Doc.creator gpx); 30 30 [%expect {| 31 31 Waypoints count: 3 32 32 First waypoint name: San Francisco ··· 39 39 let content = read_test_file "detailed_waypoints.gpx" in 40 40 match parse_string content with 41 41 | Ok gpx -> 42 - let waypoints = Gpx_doc.get_waypoints gpx in 43 - let metadata = Gpx_doc.get_metadata gpx in 42 + let waypoints = Doc.waypoints gpx in 43 + let metadata = Doc.metadata gpx in 44 44 Printf.printf "Waypoints count: %d\n" (List.length waypoints); 45 45 Printf.printf "Has metadata time: %b\n" 46 - (match metadata with Some md -> Metadata.get_time md <> None | None -> false); 46 + (match metadata with Some md -> Metadata.time md <> None | None -> false); 47 47 Printf.printf "Has bounds: %b\n" 48 - (match metadata with Some md -> Metadata.get_bounds md <> None | None -> false); 48 + (match metadata with Some md -> Metadata.bounds_opt md <> None | None -> false); 49 49 (match waypoints with 50 50 | wpt :: _ -> 51 - Printf.printf "First waypoint has elevation: %b\n" (Waypoint.get_elevation wpt <> None); 52 - Printf.printf "First waypoint has time: %b\n" (Waypoint.get_time wpt <> None); 53 - Printf.printf "First waypoint has links: %b\n" (Waypoint.get_links wpt <> []) 51 + Printf.printf "First waypoint has elevation: %b\n" (Waypoint.elevation wpt <> None); 52 + Printf.printf "First waypoint has time: %b\n" (Waypoint.time wpt <> None); 53 + Printf.printf "First waypoint has links: %b\n" (Waypoint.links wpt <> []) 54 54 | [] -> ()); 55 55 [%expect {| 56 56 Waypoints count: 2 ··· 67 67 let content = read_test_file "simple_route.gpx" in 68 68 match parse_string content with 69 69 | Ok gpx -> 70 - let routes = Gpx_doc.get_routes gpx in 70 + let routes = Doc.routes gpx in 71 71 Printf.printf "Routes count: %d\n" (List.length routes); 72 72 (match routes with 73 73 | rte :: _ -> 74 74 Printf.printf "Route name: %s\n" 75 - (match Route.get_name rte with Some n -> n | None -> "None"); 75 + (match Route.name rte with Some n -> n | None -> "None"); 76 76 Printf.printf "Route points count: %d\n" (Route.point_count rte); 77 77 Printf.printf "Route has number: %b\n" false (* TODO: add get_number to Route *) 78 78 | [] -> ()); ··· 89 89 let content = read_test_file "simple_track.gpx" in 90 90 match parse_string content with 91 91 | Ok gpx -> 92 - let tracks = Gpx_doc.get_tracks gpx in 92 + let tracks = Doc.tracks gpx in 93 93 Printf.printf "Tracks count: %d\n" (List.length tracks); 94 94 (match tracks with 95 95 | trk :: _ -> 96 96 Printf.printf "Track name: %s\n" 97 - (match Track.get_name trk with Some n -> n | None -> "None"); 97 + (match Track.name trk with Some n -> n | None -> "None"); 98 98 Printf.printf "Track segments: %d\n" (Track.segment_count trk); 99 - let segments = Track.get_segments trk in 99 + let segments = Track.segments trk in 100 100 (match segments with 101 101 | seg :: _ -> 102 102 Printf.printf "First segment points: %d\n" (Track.Segment.point_count seg); 103 - let points = Track.Segment.get_points seg in 103 + let points = Track.Segment.points seg in 104 104 (match points with 105 105 | pt :: _ -> 106 - Printf.printf "First point has elevation: %b\n" (Waypoint.get_elevation pt <> None); 107 - Printf.printf "First point has time: %b\n" (Waypoint.get_time pt <> None) 106 + Printf.printf "First point has elevation: %b\n" (Waypoint.elevation pt <> None); 107 + Printf.printf "First point has time: %b\n" (Waypoint.time pt <> None) 108 108 | [] -> ()) 109 109 | [] -> ()) 110 110 | [] -> ()); ··· 123 123 let content = read_test_file "multi_segment_track.gpx" in 124 124 match parse_string content with 125 125 | Ok gpx -> 126 - let tracks = Gpx_doc.get_tracks gpx in 126 + let tracks = Doc.tracks gpx in 127 127 Printf.printf "Tracks count: %d\n" (List.length tracks); 128 128 (match tracks with 129 129 | trk :: _ -> ··· 143 143 let content = read_test_file "comprehensive.gpx" in 144 144 match parse_string content with 145 145 | Ok gpx -> 146 - let waypoints = Gpx_doc.get_waypoints gpx in 147 - let routes = Gpx_doc.get_routes gpx in 148 - let tracks = Gpx_doc.get_tracks gpx in 149 - let metadata = Gpx_doc.get_metadata gpx in 146 + let waypoints = Doc.waypoints gpx in 147 + let routes = Doc.routes gpx in 148 + let tracks = Doc.tracks gpx in 149 + let metadata = Doc.metadata gpx in 150 150 Printf.printf "Waypoints: %d\n" (List.length waypoints); 151 151 Printf.printf "Routes: %d\n" (List.length routes); 152 152 Printf.printf "Tracks: %d\n" (List.length tracks); 153 153 Printf.printf "Has author: %b\n" 154 - (match metadata with Some md -> Metadata.get_author md <> None | None -> false); 154 + (match metadata with Some md -> Metadata.author md <> None | None -> false); 155 155 Printf.printf "Has copyright: %b\n" 156 - (match metadata with Some md -> Metadata.get_copyright md <> None | None -> false); 156 + (match metadata with Some md -> Metadata.copyright md <> None | None -> false); 157 157 Printf.printf "Has keywords: %b\n" 158 - (match metadata with Some md -> Metadata.get_keywords md <> None | None -> false); 158 + (match metadata with Some md -> Metadata.keywords md <> None | None -> false); 159 159 [%expect {| 160 160 Waypoints: 2 161 161 Routes: 1 ··· 172 172 match parse_string content with 173 173 | Ok gpx -> 174 174 Printf.printf "Minimal GPX parsed successfully\n"; 175 - let waypoints = Gpx_doc.get_waypoints gpx in 176 - let routes = Gpx_doc.get_routes gpx in 177 - let tracks = Gpx_doc.get_tracks gpx in 175 + let waypoints = Doc.waypoints gpx in 176 + let routes = Doc.routes gpx in 177 + let tracks = Doc.tracks gpx in 178 178 Printf.printf "Waypoints: %d\n" (List.length waypoints); 179 179 Printf.printf "Routes: %d\n" (List.length routes); 180 180 Printf.printf "Tracks: %d\n" (List.length tracks); ··· 194 194 match parse_string content with 195 195 | Ok gpx -> 196 196 Printf.printf "Edge cases parsed successfully\n"; 197 - let waypoints = Gpx_doc.get_waypoints gpx in 198 - let tracks = Gpx_doc.get_tracks gpx in 197 + let waypoints = Doc.waypoints gpx in 198 + let tracks = Doc.tracks gpx in 199 199 Printf.printf "Waypoints: %d\n" (List.length waypoints); 200 200 Printf.printf "Tracks: %d\n" (List.length tracks); 201 201 (* Check coordinate ranges *) ··· 245 245 (match parse_string xml_output with 246 246 | Ok gpx2 -> 247 247 Printf.printf "Round-trip successful\n"; 248 - let waypoints = Gpx_doc.get_waypoints gpx in 249 - let waypoints2 = Gpx_doc.get_waypoints gpx2 in 248 + let waypoints = Doc.waypoints gpx in 249 + let waypoints2 = Doc.waypoints gpx2 in 250 250 Printf.printf "Original waypoints: %d\n" (List.length waypoints); 251 251 Printf.printf "Round-trip waypoints: %d\n" (List.length waypoints2); 252 - Printf.printf "Creators match: %b\n" (Gpx_doc.get_creator gpx = Gpx_doc.get_creator gpx2); 252 + Printf.printf "Creators match: %b\n" (Doc.creator gpx = Doc.creator gpx2); 253 253 [%expect {| 254 254 Round-trip successful 255 255 Original waypoints: 3
+15 -15
test/test_corpus_unix_eio.ml
··· 18 18 (** Helper to compare GPX documents *) 19 19 let compare_gpx_basic gpx1 gpx2 = 20 20 let open Gpx in 21 - Gpx_doc.get_creator gpx1 = Gpx_doc.get_creator gpx2 && 22 - List.length (Gpx_doc.get_waypoints gpx1) = List.length (Gpx_doc.get_waypoints gpx2) && 23 - List.length (Gpx_doc.get_routes gpx1) = List.length (Gpx_doc.get_routes gpx2) && 24 - List.length (Gpx_doc.get_tracks gpx1) = List.length (Gpx_doc.get_tracks gpx2) 21 + Doc.creator gpx1 = Doc.creator gpx2 && 22 + List.length (Doc.waypoints gpx1) = List.length (Doc.waypoints gpx2) && 23 + List.length (Doc.routes gpx1) = List.length (Doc.routes gpx2) && 24 + List.length (Doc.tracks gpx1) = List.length (Doc.tracks gpx2) 25 25 26 26 (** Test Unix implementation can read all test files *) 27 27 let test_unix_parsing filename () = ··· 31 31 let validation = Gpx.validate_gpx gpx in 32 32 check bool "GPX is valid" true validation.is_valid; 33 33 check bool "Has some content" true ( 34 - List.length (Gpx.Gpx_doc.get_waypoints gpx) > 0 || 35 - List.length (Gpx.Gpx_doc.get_routes gpx) > 0 || 36 - List.length (Gpx.Gpx_doc.get_tracks gpx) > 0 34 + List.length (Gpx.Doc.waypoints gpx) > 0 || 35 + List.length (Gpx.Doc.routes gpx) > 0 || 36 + List.length (Gpx.Doc.tracks gpx) > 0 37 37 ) 38 38 | Error err -> 39 39 failf "Unix parsing failed for %s: %s" filename (Gpx.Error.to_string err) ··· 48 48 let validation = Gpx.validate_gpx gpx in 49 49 check bool "GPX is valid" true validation.is_valid; 50 50 check bool "Has some content" true ( 51 - List.length (Gpx.Gpx_doc.get_waypoints gpx) > 0 || 52 - List.length (Gpx.Gpx_doc.get_routes gpx) > 0 || 53 - List.length (Gpx.Gpx_doc.get_tracks gpx) > 0 51 + List.length (Gpx.Doc.waypoints gpx) > 0 || 52 + List.length (Gpx.Doc.routes gpx) > 0 || 53 + List.length (Gpx.Doc.tracks gpx) > 0 54 54 ) 55 55 with 56 56 | Gpx.Gpx_error err -> ··· 77 77 | Ok gpx_unix, Ok gpx_eio -> 78 78 check bool "Unix and Eio produce equivalent results" true 79 79 (compare_gpx_basic gpx_unix gpx_eio); 80 - check string "Creators match" (Gpx.Gpx_doc.get_creator gpx_unix) (Gpx.Gpx_doc.get_creator gpx_eio); 80 + check string "Creators match" (Gpx.Doc.creator gpx_unix) (Gpx.Doc.creator gpx_eio); 81 81 check int "Waypoint counts match" 82 - (List.length (Gpx.Gpx_doc.get_waypoints gpx_unix)) (List.length (Gpx.Gpx_doc.get_waypoints gpx_eio)); 82 + (List.length (Gpx.Doc.waypoints gpx_unix)) (List.length (Gpx.Doc.waypoints gpx_eio)); 83 83 check int "Route counts match" 84 - (List.length (Gpx.Gpx_doc.get_routes gpx_unix)) (List.length (Gpx.Gpx_doc.get_routes gpx_eio)); 84 + (List.length (Gpx.Doc.routes gpx_unix)) (List.length (Gpx.Doc.routes gpx_eio)); 85 85 check int "Track counts match" 86 - (List.length (Gpx.Gpx_doc.get_tracks gpx_unix)) (List.length (Gpx.Gpx_doc.get_tracks gpx_eio)) 86 + (List.length (Gpx.Doc.tracks gpx_unix)) (List.length (Gpx.Doc.tracks gpx_eio)) 87 87 | Error _, Error _ -> 88 88 (* Both failed - that's consistent *) 89 89 check bool "Both Unix and Eio failed consistently" true true ··· 106 106 check bool "Round-trip preserves basic structure" true 107 107 (compare_gpx_basic gpx_original gpx_roundtrip); 108 108 check string "Creator preserved" 109 - (Gpx.Gpx_doc.get_creator gpx_original) (Gpx.Gpx_doc.get_creator gpx_roundtrip) 109 + (Gpx.Doc.creator gpx_original) (Gpx.Doc.creator gpx_roundtrip) 110 110 | Error _ -> 111 111 failf "Round-trip parse failed for %s" filename) 112 112 | Error _ ->
+11 -12
test/test_gpx.ml
··· 25 25 26 26 let test_gpx_creation () = 27 27 let creator = "test" in 28 - let gpx = Gpx_doc.empty ~creator in 29 - assert (Gpx_doc.get_creator gpx = creator); 30 - assert (Gpx_doc.get_version gpx = "1.1"); 31 - assert (Gpx_doc.get_waypoints gpx = []); 28 + let gpx = Doc.empty ~creator in 29 + assert (Doc.creator gpx = creator); 30 + assert (Doc.version gpx = "1.1"); 31 + assert (Doc.waypoints gpx = []); 32 32 33 33 Printf.printf "✓ GPX creation tests passed\n" 34 34 ··· 43 43 44 44 match parse_string gpx_xml with 45 45 | Ok gpx -> 46 - assert (Gpx_doc.get_creator gpx = "test"); 47 - let waypoints = Gpx_doc.get_waypoints gpx in 46 + assert (Doc.creator gpx = "test"); 47 + let waypoints = Doc.waypoints gpx in 48 48 assert (List.length waypoints = 1); 49 49 let wpt = List.hd waypoints in 50 - assert (Waypoint.get_name wpt = Some "San Francisco"); 50 + assert (Waypoint.name wpt = Some "San Francisco"); 51 51 Printf.printf "✓ Simple parsing tests passed\n" 52 52 | Error e -> 53 53 Printf.printf "✗ Parsing failed: %s\n" (Error.to_string e); ··· 57 57 let lat = Result.get_ok (Coordinate.latitude 37.7749) in 58 58 let lon = Result.get_ok (Coordinate.longitude (-122.4194)) in 59 59 let wpt = Waypoint.make lat lon in 60 - let wpt = Waypoint.with_name wpt "Test Point" in 61 - let wpt = Waypoint.with_description wpt "A test waypoint" in 62 - let gpx = Gpx_doc.empty ~creator:"test" in 63 - let gpx = Gpx_doc.add_waypoint gpx wpt in 60 + let wpt = { wpt with name = Some "Test Point"; desc = Some "A test waypoint" } in 61 + let gpx = Doc.empty ~creator:"test" in 62 + let gpx = Doc.add_waypoint gpx wpt in 64 63 65 64 match write_string gpx with 66 65 | Ok xml_string -> ··· 72 71 assert false 73 72 74 73 let test_validation () = 75 - let gpx = Gpx_doc.empty ~creator:"" in 74 + let gpx = Doc.empty ~creator:"" in 76 75 let validation = validate_gpx gpx in 77 76 assert (not validation.is_valid); 78 77 let errors = List.filter (fun issue -> issue.level = `Error) validation.issues in