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
···
-
(* Temporarily disabled during refactoring *)
+
(** mlgpx Command Line Interface with pretty ANSI output *)
+
+
open Cmdliner
+
open Gpx
+
+
(* Terminal and formatting setup *)
+
let setup_fmt style_renderer =
+
Fmt_tty.setup_std_outputs ?style_renderer ();
+
()
+
+
(* Color formatters *)
+
let info_style = Fmt.(styled (`Fg `Green))
+
let warn_style = Fmt.(styled (`Fg `Yellow))
+
let error_style = Fmt.(styled (`Fg `Red))
+
let success_style = Fmt.(styled (`Fg `Green))
+
let bold_style = Fmt.(styled `Bold)
+
+
(* Logging functions *)
+
let log_info fmt =
+
Fmt.pf Format.err_formatter "[%a] " (info_style Fmt.string) "INFO";
+
Format.kfprintf (fun fmt -> Format.pp_print_newline fmt (); Format.pp_print_flush fmt ()) Format.err_formatter fmt
+
+
+
let log_error fmt =
+
Fmt.pf Format.err_formatter "[%a] " (error_style Fmt.string) "ERROR";
+
Format.kfprintf (fun fmt -> Format.pp_print_newline fmt (); Format.pp_print_flush fmt ()) Format.err_formatter fmt
+
+
let log_success fmt =
+
Format.kfprintf (fun fmt -> Format.pp_print_newline fmt (); Format.pp_print_flush fmt ()) Format.std_formatter fmt
+
+
(* Utility functions *)
+
let waypoints_to_track_segments waypoints =
+
if waypoints = [] then
+
[]
+
else
+
Track.Segment.make waypoints :: []
+
+
let sort_waypoints sort_by_time sort_by_name waypoints =
+
if sort_by_time then
+
List.sort (fun wpt1 wpt2 ->
+
match Waypoint.time wpt1, Waypoint.time wpt2 with
+
| Some t1, Some t2 -> Ptime.compare t1 t2
+
| Some _, None -> -1
+
| None, Some _ -> 1
+
| None, None -> 0
+
) waypoints
+
else if sort_by_name then
+
List.sort (fun wpt1 wpt2 ->
+
match Waypoint.name wpt1, Waypoint.name wpt2 with
+
| Some n1, Some n2 -> String.compare n1 n2
+
| Some _, None -> -1
+
| None, Some _ -> 1
+
| None, None -> 0
+
) waypoints
+
else
+
waypoints
+
+
(* Main conversion command *)
+
let convert_waypoints_to_trackset input_file output_file track_name track_desc
+
sort_by_time sort_by_name preserve_waypoints verbose style_renderer =
+
setup_fmt style_renderer;
+
let run env =
+
try
+
let fs = Eio.Stdenv.fs env in
+
+
if verbose then
+
log_info "Reading GPX file: %a" (bold_style Fmt.string) input_file;
+
+
(* Read input GPX *)
+
let gpx = Gpx_eio.read ~fs input_file in
+
+
if verbose then
+
log_info "Found %d waypoints and %d existing tracks"
+
(Doc.waypoint_count gpx)
+
(Doc.track_count gpx);
+
+
(* Check if we have waypoints to convert *)
+
if Doc.waypoints gpx = [] then (
+
log_error "Input file contains no waypoints - nothing to convert";
+
exit 1
+
);
+
+
(* Sort waypoints if requested *)
+
let sorted_waypoints = sort_waypoints sort_by_time sort_by_name (Doc.waypoints gpx) in
+
+
if verbose && (sort_by_time || sort_by_name) then
+
log_info "Sorted %d waypoints" (List.length sorted_waypoints);
+
+
(* Convert waypoints to track segments *)
+
let track_segments = waypoints_to_track_segments sorted_waypoints in
+
+
(* Create the new track *)
+
let new_track = Track.make ~name:track_name in
+
let new_track = { new_track with
+
cmt = Some "Generated from waypoints by mlgpx CLI";
+
desc = track_desc;
+
src = Some "mlgpx";
+
type_ = Some "converted";
+
trksegs = track_segments;
+
} in
+
+
if verbose then (
+
let total_points = List.fold_left (fun acc seg -> acc + Track.Segment.point_count seg) 0 track_segments in
+
log_info "Created track %a with %d segments containing %d points"
+
(bold_style Fmt.string) track_name
+
(List.length track_segments) total_points
+
);
+
+
(* Build output GPX *)
+
let output_gpx =
+
if preserve_waypoints then
+
Doc.add_track gpx new_track
+
else
+
Doc.add_track (Doc.clear_waypoints gpx) new_track in
+
let output_gpx =
+
match Doc.metadata gpx with
+
| Some meta ->
+
let desc = match Metadata.description meta with
+
| Some existing -> existing ^ " (waypoints converted to track)"
+
| None -> "Waypoints converted to track" in
+
Doc.with_metadata output_gpx { meta with desc = Some desc }
+
| None ->
+
let meta = Metadata.make ~name:"Converted" in
+
let meta = { meta with desc = Some "Waypoints converted to track" } in
+
Doc.with_metadata output_gpx meta in
+
+
(* Validate output *)
+
let validation = Gpx.validate_gpx output_gpx in
+
if not validation.is_valid then (
+
log_error "Generated GPX failed validation:";
+
List.iter (fun (issue : Gpx.validation_issue) ->
+
let level_str = match issue.level with `Error -> "ERROR" | `Warning -> "WARNING" in
+
let level_color = match issue.level with `Error -> error_style | `Warning -> warn_style in
+
Fmt.pf Format.err_formatter " %a: %s\n" (level_color Fmt.string) level_str issue.message
+
) validation.issues;
+
exit 1
+
);
+
+
if verbose then
+
log_info "Writing output to: %a" (bold_style Fmt.string) output_file;
+
+
(* Write output GPX *)
+
Gpx_eio.write ~fs output_file output_gpx;
+
+
if verbose then (
+
Fmt.pf Format.std_formatter "%a\n" (success_style Fmt.string) "Conversion completed successfully!";
+
log_info "Output contains:";
+
Fmt.pf Format.err_formatter " - %d waypoints%s\n"
+
(Doc.waypoint_count output_gpx)
+
(if preserve_waypoints then " (preserved)" else " (removed)");
+
Fmt.pf Format.err_formatter " - %d tracks (%a + %d existing)\n"
+
(Doc.track_count output_gpx)
+
(success_style Fmt.string) "1 new"
+
(Doc.track_count gpx)
+
) else (
+
log_success "Converted %d waypoints to track: %a → %a"
+
(List.length sorted_waypoints)
+
(bold_style Fmt.string) input_file
+
(bold_style Fmt.string) output_file
+
)
+
+
with
+
| Gpx.Gpx_error err ->
+
log_error "GPX Error: %s" (Error.to_string err);
+
exit 2
+
| Sys_error msg ->
+
log_error "System error: %s" msg;
+
exit 2
+
| exn ->
+
log_error "Unexpected error: %s" (Printexc.to_string exn);
+
exit 2
+
in
+
Eio_main.run run
+
+
(* Helper function to collect all timestamps from GPX *)
+
let collect_all_timestamps gpx =
+
let times = ref [] in
+
+
(* Collect from waypoints *)
+
List.iter (fun wpt ->
+
match Waypoint.time wpt with
+
| Some t -> times := t :: !times
+
| None -> ()
+
) (Doc.waypoints gpx);
+
+
(* Collect from routes *)
+
List.iter (fun route ->
+
List.iter (fun rtept ->
+
match Waypoint.time rtept with
+
| Some t -> times := t :: !times
+
| None -> ()
+
) (Route.points route)
+
) (Doc.routes gpx);
+
+
(* Collect from tracks *)
+
List.iter (fun track ->
+
List.iter (fun seg ->
+
List.iter (fun trkpt ->
+
match Waypoint.time trkpt with
+
| Some t -> times := t :: !times
+
| None -> ()
+
) (Track.Segment.points seg)
+
) (Track.segments track)
+
) (Doc.tracks gpx);
+
+
!times
+
+
(* Info command *)
+
let info_command input_file verbose style_renderer =
+
setup_fmt style_renderer;
+
let run env =
+
try
+
let fs = Eio.Stdenv.fs env in
+
+
if verbose then
+
log_info "Analyzing GPX file: %a" (bold_style Fmt.string) input_file;
+
+
let gpx = Gpx_eio.read ~fs input_file in
+
+
(* Header *)
+
Fmt.pf Format.std_formatter "%a\n" (bold_style Fmt.string) "GPX File Information";
+
+
(* Basic info *)
+
Printf.printf " Version: %s\n" (Doc.version gpx);
+
Printf.printf " Creator: %s\n" (Doc.creator gpx);
+
+
(match Doc.metadata gpx with
+
| Some meta ->
+
Printf.printf " Name: %s\n" (Option.value (Metadata.name meta) ~default:"<unnamed>");
+
Printf.printf " Description: %s\n" (Option.value (Metadata.description meta) ~default:"<none>");
+
(match Metadata.time meta with
+
| Some time -> Printf.printf " Created: %s\n" (Ptime.to_rfc3339 time)
+
| None -> ())
+
| None ->
+
Printf.printf " No metadata\n");
+
+
(* Content summary *)
+
Fmt.pf Format.std_formatter "\n%a\n" (bold_style Fmt.string) "Content Summary";
+
Printf.printf " Waypoints: %d\n" (Doc.waypoint_count gpx);
+
Printf.printf " Routes: %d\n" (Doc.route_count gpx);
+
Printf.printf " Tracks: %d\n" (Doc.track_count gpx);
+
+
(* Time range *)
+
let all_times = collect_all_timestamps gpx in
+
if all_times <> [] then (
+
let sorted_times = List.sort Ptime.compare all_times in
+
let start_time = List.hd sorted_times in
+
let stop_time = List.hd (List.rev sorted_times) in
+
+
Fmt.pf Format.std_formatter "\n%a\n" (bold_style Fmt.string) "Time Range";
+
Fmt.pf Format.std_formatter " Start: %a\n" (info_style Fmt.string) (Ptime.to_rfc3339 start_time);
+
Fmt.pf Format.std_formatter " Stop: %a\n" (info_style Fmt.string) (Ptime.to_rfc3339 stop_time);
+
+
(* Calculate duration *)
+
let duration_span = Ptime.diff stop_time start_time in
+
match Ptime.Span.to_int_s duration_span with
+
| Some seconds ->
+
let days = seconds / 86400 in
+
let hours = (seconds mod 86400) / 3600 in
+
let minutes = (seconds mod 3600) / 60 in
+
+
if days > 0 then
+
Fmt.pf Format.std_formatter " Duration: %a\n" (bold_style Fmt.string)
+
(Printf.sprintf "%d days, %d hours, %d minutes" days hours minutes)
+
else if hours > 0 then
+
Fmt.pf Format.std_formatter " Duration: %a\n" (bold_style Fmt.string)
+
(Printf.sprintf "%d hours, %d minutes" hours minutes)
+
else
+
Fmt.pf Format.std_formatter " Duration: %a\n" (bold_style Fmt.string)
+
(Printf.sprintf "%d minutes" minutes)
+
| None ->
+
(* Duration too large to represent as int *)
+
Fmt.pf Format.std_formatter " Duration: %a\n" (bold_style Fmt.string)
+
(Printf.sprintf "%.1f days" (Ptime.Span.to_float_s duration_span /. 86400.));
+
+
Printf.printf " Total points with timestamps: %d\n" (List.length all_times)
+
);
+
+
(* Detailed waypoint info *)
+
if Doc.waypoints gpx <> [] then (
+
Fmt.pf Format.std_formatter "\n%a\n" (bold_style Fmt.string) "Waypoints";
+
let waypoints_with_time = List.filter (fun wpt -> Waypoint.time wpt <> None) (Doc.waypoints gpx) in
+
let waypoints_with_elevation = List.filter (fun wpt -> Waypoint.elevation wpt <> None) (Doc.waypoints gpx) in
+
Printf.printf " - %d with timestamps\n" (List.length waypoints_with_time);
+
Printf.printf " - %d with elevation data\n" (List.length waypoints_with_elevation);
+
+
if verbose && List.length (Doc.waypoints gpx) <= 10 then (
+
Printf.printf " Details:\n";
+
List.iteri (fun i wpt ->
+
let lat, lon = Waypoint.to_floats wpt in
+
Fmt.pf Format.std_formatter " %a %s (%.6f, %.6f)%s%s\n"
+
(info_style Fmt.string) (Printf.sprintf "%d." (i + 1))
+
(Option.value (Waypoint.name wpt) ~default:"<unnamed>")
+
lat lon
+
(match Waypoint.elevation wpt with Some e -> Printf.sprintf " elev=%.1fm" e | None -> "")
+
(match Waypoint.time wpt with Some t -> " @" ^ Ptime.to_rfc3339 t | None -> "")
+
) (Doc.waypoints gpx)
+
)
+
);
+
+
(* Track info *)
+
if Doc.tracks gpx <> [] then (
+
Fmt.pf Format.std_formatter "\n%a\n" (bold_style Fmt.string) "Tracks";
+
List.iteri (fun i track ->
+
let total_points = Track.point_count track in
+
Fmt.pf Format.std_formatter " %a %s (%d segments, %d points)\n"
+
(info_style Fmt.string) (Printf.sprintf "%d." (i + 1))
+
(Option.value (Track.name track) ~default:"<unnamed>")
+
(Track.segment_count track) total_points
+
) (Doc.tracks gpx)
+
);
+
+
(* Validation *)
+
let validation = Gpx.validate_gpx gpx in
+
Printf.printf "\n";
+
if validation.is_valid then
+
Fmt.pf Format.std_formatter "Validation: %a\n" (success_style Fmt.string) "PASSED"
+
else (
+
Fmt.pf Format.std_formatter "Validation: %a\n" (error_style Fmt.string) "FAILED";
+
List.iter (fun (issue : Gpx.validation_issue) ->
+
let level_str = match issue.level with `Error -> "ERROR" | `Warning -> "WARNING" in
+
let level_color = match issue.level with `Error -> error_style | `Warning -> warn_style in
+
Fmt.pf Format.std_formatter " %a: %s\n" (level_color Fmt.string) level_str issue.message
+
) validation.issues
+
)
+
+
with
+
| Gpx.Gpx_error err ->
+
log_error "GPX Error: %s" (Error.to_string err);
+
exit 2
+
| Sys_error msg ->
+
log_error "System error: %s" msg;
+
exit 2
+
| exn ->
+
log_error "Unexpected error: %s" (Printexc.to_string exn);
+
exit 2
+
in
+
Eio_main.run run
+
+
(* CLI argument definitions *)
+
let input_file_arg =
+
let doc = "Input GPX file path" in
+
Arg.(required & pos 0 (some non_dir_file) None & info [] ~docv:"INPUT" ~doc)
+
+
let output_file_arg =
+
let doc = "Output GPX file path" in
+
Arg.(required & pos 1 (some string) None & info [] ~docv:"OUTPUT" ~doc)
+
+
let track_name_opt =
+
let doc = "Name for the generated track (default: \"Converted from waypoints\")" in
+
Arg.(value & opt string "Converted from waypoints" & info ["n"; "name"] ~docv:"NAME" ~doc)
+
+
let track_description_opt =
+
let doc = "Description for the generated track" in
+
Arg.(value & opt (some string) None & info ["d"; "desc"] ~docv:"DESC" ~doc)
+
+
let sort_by_time_flag =
+
let doc = "Sort waypoints by timestamp before conversion" in
+
Arg.(value & flag & info ["t"; "sort-time"] ~doc)
+
+
let sort_by_name_flag =
+
let doc = "Sort waypoints by name before conversion" in
+
Arg.(value & flag & info ["sort-name"] ~doc)
+
+
let preserve_waypoints_flag =
+
let doc = "Keep original waypoints in addition to generated track" in
+
Arg.(value & flag & info ["p"; "preserve"] ~doc)
+
+
let verbose_flag =
+
let doc = "Enable verbose output" in
+
Arg.(value & flag & info ["v"; "verbose"] ~doc)
+
+
(* Command definitions *)
+
let convert_cmd =
+
let doc = "Convert waypoints to trackset" in
+
let man = [
+
`S Manpage.s_description;
+
`P "Convert all waypoints in a GPX file to a single track. This is useful for \
+
converting a collection of waypoints into a navigable route or for \
+
consolidating GPS data.";
+
`P "The conversion preserves all waypoint data (coordinates, elevation, \
+
timestamps, etc.) in the track points. By default, waypoints are removed \
+
from the output file unless --preserve is used.";
+
`S Manpage.s_examples;
+
`P "Convert waypoints to track:";
+
`Pre " mlgpx convert waypoints.gpx track.gpx";
+
`P "Convert with custom track name and preserve original waypoints:";
+
`Pre " mlgpx convert -n \"My Route\" --preserve waypoints.gpx route.gpx";
+
`P "Sort waypoints by timestamp before conversion:";
+
`Pre " mlgpx convert --sort-time waypoints.gpx sorted_track.gpx";
+
] in
+
let term = Term.(const convert_waypoints_to_trackset $ input_file_arg $ output_file_arg
+
$ track_name_opt $ track_description_opt $ sort_by_time_flag
+
$ sort_by_name_flag $ preserve_waypoints_flag $ verbose_flag
+
$ Fmt_cli.style_renderer ()) in
+
Cmd.v (Cmd.info "convert" ~doc ~man) term
+
+
let info_cmd =
+
let doc = "Display information about a GPX file" in
+
let man = [
+
`S Manpage.s_description;
+
`P "Analyze and display detailed information about a GPX file including \
+
statistics, content summary, and validation results.";
+
`P "This command is useful for understanding the structure and content \
+
of GPX files before processing them.";
+
`S Manpage.s_examples;
+
`P "Show basic information:";
+
`Pre " mlgpx info file.gpx";
+
`P "Show detailed information with waypoint details:";
+
`Pre " mlgpx info -v file.gpx";
+
] in
+
let input_arg =
+
let doc = "GPX file to analyze" in
+
Arg.(required & pos 0 (some non_dir_file) None & info [] ~docv:"FILE" ~doc) in
+
let term = Term.(const info_command $ input_arg $ verbose_flag
+
$ Fmt_cli.style_renderer ()) in
+
Cmd.v (Cmd.info "info" ~doc ~man) term
+
+
(* Main CLI *)
+
let main_cmd =
+
let doc = "mlgpx - GPX file manipulation toolkit" in
+
let man = [
+
`S Manpage.s_description;
+
`P "mlgpx is a command-line toolkit for working with GPX (GPS Exchange Format) \
+
files. It provides tools for converting, analyzing, and manipulating GPS data.";
+
`S Manpage.s_commands;
+
`P "Available commands:";
+
`P "$(b,convert) - Convert waypoints to trackset";
+
`P "$(b,info) - Display GPX file information";
+
`S Manpage.s_common_options;
+
`P "$(b,--verbose), $(b,-v) - Enable verbose output";
+
`P "$(b,--color)={auto|always|never} - Control ANSI color output";
+
`P "$(b,--help) - Show command help";
+
`S Manpage.s_examples;
+
`P "Convert waypoints to track:";
+
`Pre " mlgpx convert waypoints.gpx track.gpx";
+
`P "Analyze a GPX file with colors:";
+
`Pre " mlgpx info --verbose --color=always file.gpx";
+
`P "Convert without colors for scripts:";
+
`Pre " mlgpx convert --color=never waypoints.gpx track.gpx";
+
`S Manpage.s_bugs;
+
`P "Report bugs at https://github.com/avsm/mlgpx/issues";
+
] in
+
let default_term = Term.(ret (const (`Help (`Pager, None)))) in
+
Cmd.group (Cmd.info "mlgpx" ~version:"0.1.0" ~doc ~man) ~default:default_term
+
[convert_cmd; info_cmd]
+
+
let () =
+
Printexc.record_backtrace true;
+
exit (Cmd.eval main_cmd)
-5
examples/dune
···
(public_name simple_gpx)
(name simple_gpx)
(libraries gpx xmlm))
-
-
(executable
-
(public_name effects_example)
-
(name effects_example)
-
(libraries gpx xmlm))
-117
examples/effects_example.ml
···
-
(** Simple GPX example demonstrating basic functionality **)
-
-
open Gpx
-
-
let () =
-
Printf.printf "=== Simple GPX Example ===\n\n";
-
-
try
-
(* Create some GPS coordinates *)
-
let lat1 = Coordinate.latitude 37.7749 |> Result.get_ok in
-
let lon1 = Coordinate.longitude (-122.4194) |> Result.get_ok in
-
let lat2 = Coordinate.latitude 37.7849 |> Result.get_ok in
-
let lon2 = Coordinate.longitude (-122.4094) |> Result.get_ok in
-
-
(* Create waypoints *)
-
let waypoint1 = Waypoint.make lat1 lon1 in
-
let waypoint1 = Waypoint.with_name waypoint1 "San Francisco" in
-
let waypoint2 = Waypoint.make lat2 lon2 in
-
let waypoint2 = Waypoint.with_name waypoint2 "Near SF" in
-
-
(* Create a simple track from coordinates *)
-
let track = Track.make ~name:"SF Walk" in
-
let track_segment = Track.Segment.empty in
-
let coords = [
-
(37.7749, -122.4194);
-
(37.7759, -122.4184);
-
(37.7769, -122.4174);
-
(37.7779, -122.4164);
-
] in
-
let track_segment = List.fold_left (fun seg (lat_f, lon_f) ->
-
match Coordinate.latitude lat_f, Coordinate.longitude lon_f with
-
| Ok lat, Ok lon ->
-
let pt = Waypoint.make lat lon in
-
Track.Segment.add_point seg pt
-
| _ -> seg
-
) track_segment coords in
-
let track = Track.add_segment track track_segment in
-
-
(* Create a route *)
-
let route = Route.make ~name:"SF Route" in
-
let route_coords = [
-
(37.7749, -122.4194);
-
(37.7849, -122.4094);
-
] in
-
let route = List.fold_left (fun r (lat_f, lon_f) ->
-
match Coordinate.latitude lat_f, Coordinate.longitude lon_f with
-
| Ok lat, Ok lon ->
-
let pt = Waypoint.make lat lon in
-
Route.add_point r pt
-
| _ -> r
-
) route route_coords in
-
-
(* Create GPX document with all elements *)
-
let gpx = Gpx_doc.empty ~creator:"simple-example" in
-
let gpx = Gpx_doc.add_waypoint gpx waypoint1 in
-
let gpx = Gpx_doc.add_waypoint gpx waypoint2 in
-
let gpx = Gpx_doc.add_track gpx track in
-
let gpx = Gpx_doc.add_route gpx route in
-
-
Printf.printf "Created GPX document with:\n";
-
Printf.printf " Waypoints: %d\n" (List.length (Gpx_doc.get_waypoints gpx));
-
Printf.printf " Tracks: %d\n" (List.length (Gpx_doc.get_tracks gpx));
-
Printf.printf " Routes: %d\n" (List.length (Gpx_doc.get_routes gpx));
-
Printf.printf "\n";
-
-
(* Write to file with validation *)
-
let out_chan = open_out "example_output.gpx" in
-
let dest = (`Channel out_chan) in
-
(match write ~validate:true dest gpx with
-
| Ok () ->
-
close_out out_chan;
-
Printf.printf "Wrote GPX to example_output.gpx\n"
-
| Error e ->
-
close_out out_chan;
-
Printf.eprintf "Error writing GPX: %s\n" (Error.to_string e)
-
);
-
-
(* Read it back and verify *)
-
let in_chan = open_in "example_output.gpx" in
-
let input = Xmlm.make_input (`Channel in_chan) in
-
(match parse ~validate:true input with
-
| Ok gpx2 ->
-
close_in in_chan;
-
let waypoints = Gpx_doc.get_waypoints gpx2 in
-
let tracks = Gpx_doc.get_tracks gpx2 in
-
let routes = Gpx_doc.get_routes gpx2 in
-
Printf.printf "Read back GPX document with %d waypoints, %d tracks, %d routes\n"
-
(List.length waypoints) (List.length tracks) (List.length routes);
-
-
(* Extract coordinates from track *)
-
(match tracks with
-
| track :: _ ->
-
let segments = Track.get_segments track in
-
(match segments with
-
| seg :: _ ->
-
let points = Track.Segment.get_points seg in
-
Printf.printf "Track coordinates: %d points\n" (List.length points);
-
List.iteri (fun i pt ->
-
let lat = Coordinate.latitude_to_float (Waypoint.get_lat pt) in
-
let lon = Coordinate.longitude_to_float (Waypoint.get_lon pt) in
-
Printf.printf " Point %d: %.4f, %.4f\n" i lat lon
-
) points
-
| [] -> Printf.printf "No track segments found\n")
-
| [] -> Printf.printf "No tracks found\n")
-
| Error e ->
-
close_in in_chan;
-
Printf.eprintf "Error reading back GPX: %s\n" (Error.to_string e));
-
-
Printf.printf "\nExample completed successfully!\n"
-
-
with
-
| Gpx_error err ->
-
Printf.eprintf "GPX Error: %s\n" (Error.to_string err);
-
exit 1
-
| exn ->
-
Printf.eprintf "Unexpected error: %s\n" (Printexc.to_string exn);
-
exit 1
+9 -12
examples/simple_gpx.ml
···
match result with
| Ok (lat, lon) ->
let wpt = Waypoint.make lat lon in
-
let wpt = Waypoint.with_name wpt "San Francisco" in
-
let wpt = Waypoint.with_description wpt "Golden Gate Bridge area" in
-
Printf.printf "✓ Created waypoint: %s\n" (Option.value (Waypoint.get_name wpt) ~default:"<unnamed>");
+
let wpt = { wpt with name = Some "San Francisco"; desc = Some "Golden Gate Bridge area" } in
+
Printf.printf "✓ Created waypoint: %s\n" (Option.value (Waypoint.name wpt) ~default:"<unnamed>");
(* Create GPX document *)
-
let gpx = Gpx_doc.empty ~creator:"mlgpx direct API example" in
-
let gpx = Gpx_doc.add_waypoint gpx wpt in
+
let gpx = Doc.empty ~creator:"mlgpx direct API example" in
+
let gpx = Doc.add_waypoint gpx wpt in
(* Add metadata *)
let metadata = Metadata.empty in
-
let metadata = Metadata.with_name metadata "Example GPX File" in
-
let metadata = Metadata.with_description metadata "Demonstration of mlgpx library capabilities" in
-
let gpx = Gpx_doc.with_metadata gpx metadata in
+
let metadata = { metadata with name = Some "Example GPX File"; desc = Some "Demonstration of mlgpx library capabilities" } in
+
let gpx = { gpx with metadata = Some metadata } in
(* Create a simple track with points *)
let track = Track.make ~name:"Example Track" in
-
let track = Track.with_comment track "Sample GPS track" in
-
let track = Track.with_description track "Demonstrates track creation" in
+
let track = { track with cmt = Some "Sample GPS track"; desc = Some "Demonstrates track creation" } in
(* Create track segment with points *)
let track_segment = Track.Segment.empty in
···
) track_segment points in
let track = Track.add_segment track track_segment in
-
let gpx = Gpx_doc.add_track gpx track in
+
let gpx = Doc.add_track gpx track in
Printf.printf "✓ Created track\n";
···
Printf.printf "✓ Round-trip validation: %s\n"
(if validation2.is_valid then "PASSED" else "FAILED");
Printf.printf " Waypoints: %d, Tracks: %d\n"
-
(List.length (Gpx_doc.get_waypoints gpx2)) (List.length (Gpx_doc.get_tracks gpx2))
+
(List.length (Doc.waypoints gpx2)) (List.length (Doc.tracks gpx2))
| Error e ->
Printf.printf "✗ Error reading back: %s\n" (Error.to_string e)
)
+2 -2
lib/gpx/coordinate.ml
···
| Error e, _ | _, Error e -> Error e
(** Extract components *)
-
let get_lat t = t.lat
-
let get_lon t = t.lon
+
let lat t = t.lat
+
let lon t = t.lon
let to_floats t = (latitude_to_float t.lat, longitude_to_float t.lon)
(** Compare coordinates *)
+8 -8
lib/gpx/coordinate.mli
···
(** Geographic coordinate types with validation *)
-
(** Private coordinate types with validation constraints *)
+
(** Coordinate types with validation constraints *)
type latitude = private float
type longitude = private float
type degrees = private float
-
(** Coordinate pair - main type for this module *)
+
(** Coordinate pair *)
type t = {
lat : latitude;
lon : longitude;
}
-
(** {2 Smart Constructors} *)
+
(** {2 Constructors} *)
(** Create validated latitude.
@param f Latitude in degrees (-90.0 to 90.0)
···
(** Convert degrees to float *)
val degrees_to_float : degrees -> float
-
(** {2 Coordinate Operations} *)
+
(** {2 Operations} *)
(** Create coordinate pair from validated components *)
val make : latitude -> longitude -> t
···
val make_from_floats : float -> float -> (t, string) result
(** Extract latitude component *)
-
val get_lat : t -> latitude
+
val lat : t -> latitude
(** Extract longitude component *)
-
val get_lon : t -> longitude
+
val lon : t -> longitude
(** Convert coordinate to float pair *)
val to_floats : t -> float * float
-
(** {2 Comparison and Utilities} *)
+
(** {2 Comparison and Printers} *)
(** Compare two coordinates *)
val compare : t -> t -> int
···
val equal : t -> t -> bool
(** Pretty print coordinate *)
-
val pp : Format.formatter -> t -> unit
+
val pp : Format.formatter -> t -> unit
+1 -1
lib/gpx/dune
···
(public_name mlgpx.core)
(name gpx)
(libraries xmlm ptime)
-
(modules gpx parser writer validate coordinate link extension waypoint metadata route track error gpx_doc))
+
(modules gpx parser writer validate coordinate link extension waypoint metadata route track error doc))
+8 -8
lib/gpx/extension.ml
···
(** {2 Extension Operations} *)
(** Create extension with flexible content *)
-
let make ~namespace ~name ~attributes ~content () =
+
let make ?namespace ~name ~attributes ~content () =
{ namespace; name; attributes; content }
(** Create an extension with text content *)
···
{ namespace; name; attributes; content = Mixed (text, elements) }
(** Get extension name *)
-
let get_name t = t.name
+
let name t = t.name
(** Get optional namespace *)
-
let get_namespace t = t.namespace
+
let namespace t = t.namespace
(** Get attributes *)
-
let get_attributes t = t.attributes
+
let attributes t = t.attributes
(** Get content *)
-
let get_content t = t.content
+
let content t = t.content
(** Create text content *)
let text_content text = Text text
···
let is_mixed_content = function Mixed _ -> true | _ -> false
(** Extract text content *)
-
let get_text_content = function Text s -> Some s | _ -> None
+
let text_content_extract = function Text s -> Some s | _ -> None
(** Extract element content *)
-
let get_elements_content = function Elements e -> Some e | _ -> None
+
let elements_content_extract = function Elements e -> Some e | _ -> None
(** Extract mixed content *)
-
let get_mixed_content = function Mixed (s, e) -> Some (s, e) | _ -> None
+
let mixed_content_extract = function Mixed (s, e) -> Some (s, e) | _ -> None
+8 -8
lib/gpx/extension.mli
···
(** {2 Extension Constructors} *)
(** Create extension with flexible content *)
-
val make : namespace:string option -> name:string -> attributes:(string * string) list -> content:content -> unit -> t
+
val make : ?namespace:string -> name:string -> attributes:(string * string) list -> content:content -> unit -> t
(** Create an extension with text content *)
val make_text : name:string -> ?namespace:string -> ?attributes:(string * string) list -> string -> t
···
(** {2 Extension Operations} *)
(** Get extension name *)
-
val get_name : t -> string
+
val name : t -> string
(** Get optional namespace *)
-
val get_namespace : t -> string option
+
val namespace : t -> string option
(** Get attributes *)
-
val get_attributes : t -> (string * string) list
+
val attributes : t -> (string * string) list
(** Get content *)
-
val get_content : t -> content
+
val content : t -> content
(** Find attribute value by name *)
val find_attribute : string -> t -> string option
···
val is_mixed_content : content -> bool
(** Extract text content *)
-
val get_text_content : content -> string option
+
val text_content_extract : content -> string option
(** Extract element content *)
-
val get_elements_content : content -> t list option
+
val elements_content_extract : content -> t list option
(** Extract mixed content *)
-
val get_mixed_content : content -> (string * t list) option
+
val mixed_content_extract : content -> (string * t list) option
+6 -6
lib/gpx/gpx.ml
···
module Error = Error
(** Main GPX document type *)
-
module Gpx_doc = Gpx_doc
+
module Doc = Doc
(** {1 Main Document Type} *)
(** Main GPX document type *)
-
type t = Gpx_doc.t
+
type t = Doc.t
(** {1 Error Handling} *)
···
let is_valid = Validate.is_valid
(** Get only error messages *)
-
let get_errors = Validate.get_errors
+
let errors = Validate.errors
(** Get only warning messages *)
-
let get_warnings = Validate.get_warnings
+
let warnings = Validate.warnings
(** Format validation issue for display *)
let format_issue = Validate.format_issue
···
(** {1 Constructors and Utilities} *)
(** Create new GPX document *)
-
let make_gpx ~creator = Gpx_doc.empty ~creator
+
let make_gpx ~creator = Doc.empty ~creator
(** Create empty GPX document *)
-
let empty ~creator = Gpx_doc.empty ~creator
+
let empty ~creator = Doc.empty ~creator
+443 -54
lib/gpx/gpx.mli
···
-
(** OCaml library for reading and writing GPX (GPS Exchange Format) files
+
(** OCaml library for reading and writing GPX (GPS Exchange Format) files.
+
+
{1 Overview}
+
+
GPX (GPS Exchange Format) is an XML-based format for GPS data interchange,
+
standardized by {{:https://www.topografix.com/gpx.asp}Topografix}. This library
+
provides a complete implementation of the GPX 1.1 specification with strong
+
type safety and validation.
+
+
GPX files can contain three main types of GPS data:
+
- {b Waypoints}: Individual points of interest with coordinates
+
- {b Routes}: Ordered sequences of waypoints representing a planned path
+
- {b Tracks}: Recorded GPS traces, typically from actual journeys
+
+
All coordinates in GPX use the WGS84 datum (World Geodetic System 1984),
+
the same coordinate system used by GPS satellites. Coordinates are expressed
+
as decimal degrees, with elevations in meters above mean sea level.
+
+
{2 Quick Start Example}
+
+
{[
+
open Gpx
+
+
(* Create coordinates *)
+
let lat = Coordinate.latitude 37.7749 |> Result.get_ok in
+
let lon = Coordinate.longitude (-122.4194) |> Result.get_ok in
+
+
(* Create a waypoint *)
+
let waypoint = Waypoint.make lat lon
+
|> Waypoint.with_name "San Francisco"
+
|> Waypoint.with_description "Golden Gate Bridge area" in
+
+
(* Create GPX document *)
+
let gpx = make_gpx ~creator:"my-app"
+
|> Doc.add_waypoint waypoint in
+
+
(* Write to file or string *)
+
match write_string gpx with
+
| Ok xml -> print_endline xml
+
| Error e -> Printf.eprintf "Error: %s\n" (Error.to_string e)
+
]}
This library provides a clean, modular interface for working with GPX files,
-
the standard format for GPS data exchange. *)
+
with separate modules for each major component of the GPX specification. *)
(** {1 Core Modules}
The library is organized into focused modules, each handling a specific aspect
-
of GPX data. *)
+
of GPX data. Each module provides complete functionality for its domain with
+
strong type safety and validation. *)
-
(** Geographic coordinate handling with validation *)
+
(** {2 Geographic coordinate handling with validation}
+
+
The {!Coordinate} module provides validated coordinate types for latitude,
+
longitude, and degrees. All coordinates use the WGS84 datum and are validated
+
at construction time to ensure they fall within valid ranges:
+
- Latitude: -90.0 to +90.0 degrees
+
- Longitude: -180.0 to +180.0 degrees
+
- Degrees: 0.0 to 360.0 degrees
+
+
Example: [Coordinate.latitude 37.7749] creates a validated latitude. *)
module Coordinate = Coordinate
-
(** Links, persons, and copyright information *)
+
(** {2 Links, persons, and copyright information}
+
+
The {!Link} module handles web links, author information, and copyright data
+
as defined in the GPX specification. This includes:
+
- Web links with optional text and MIME type
+
- Person records with name, email, and associated links
+
- Copyright information with author, year, and license terms *)
module Link = Link
-
(** Extension mechanism for custom GPX elements *)
+
(** {2 Extension mechanism for custom GPX elements}
+
+
The {!Extension} module provides support for custom XML elements that extend
+
the standard GPX format. Extensions allow applications to embed additional
+
data while maintaining compatibility with standard GPX readers. *)
module Extension = Extension
-
(** GPS waypoint data and fix types *)
+
(** {2 GPS waypoint data and fix types}
+
+
The {!Waypoint} module handles individual GPS points, including waypoints,
+
route points, and track points. Each waypoint contains:
+
- Required coordinates (latitude/longitude)
+
- Optional elevation in meters above mean sea level
+
- Optional timestamp
+
- Optional metadata like name, description, symbol
+
- Optional GPS quality information (accuracy, satellite count, etc.)
+
+
Fix types indicate GPS quality: none, 2D, 3D, DGPS, or PPS. *)
module Waypoint = Waypoint
-
(** GPX metadata including bounds *)
+
(** {2 GPX metadata including bounds}
+
+
The {!Metadata} module handles document-level information:
+
- File name and description
+
- Author and copyright information
+
- Creation time and keywords
+
- Geographic bounding box of all data
+
- Links to related resources *)
module Metadata = Metadata
-
(** Route data and calculations *)
+
(** {2 Route data and calculations}
+
+
The {!Route} module handles planned paths represented as ordered sequences
+
of waypoints. Routes typically represent intended journeys rather than
+
recorded tracks. Each route can include:
+
- Ordered list of waypoints (route points)
+
- Route metadata (name, description, links)
+
- Distance calculations between points *)
module Route = Route
-
(** Track data with segments *)
+
(** {2 Track data with segments}
+
+
The {!Track} module handles recorded GPS traces, typically representing
+
actual journeys. Tracks are divided into segments to handle GPS interruptions:
+
- Track segments contain ordered track points
+
- Each track point is a timestamped waypoint
+
- Multiple segments per track handle GPS signal loss
+
- Distance and time calculations available *)
module Track = Track
-
(** Error handling *)
+
(** {2 Error handling}
+
+
The {!Error} module provides comprehensive error handling for GPX operations:
+
- XML parsing and validation errors
+
- Coordinate validation errors
+
- Missing required elements or attributes
+
- File I/O errors *)
module Error = Error
-
(** Main GPX document type *)
-
module Gpx_doc = Gpx_doc
+
(** {2 Main GPX document type}
+
+
The {!Doc} module represents complete GPX documents containing:
+
- Document metadata (creator, version)
+
- Collections of waypoints, routes, and tracks
+
- Document-level extensions
+
- Statistics and analysis functions *)
+
module Doc = Doc
(** {1 Main Document Type} *)
-
(** Main GPX document type *)
-
type t = Gpx_doc.t
+
(** A complete GPX document containing waypoints, routes, tracks, and metadata.
+
+
This is the main type representing a complete GPX file. GPX documents must
+
have a creator string (identifying the creating application) and follow the
+
GPX 1.1 specification format. *)
+
type t = Doc.t
(** {1 Error Handling} *)
-
(** Error types *)
+
(** Comprehensive error type covering all possible GPX operation failures.
+
+
Errors can occur during:
+
- XML parsing (malformed XML, invalid structure)
+
- Coordinate validation (out of range values)
+
- Missing required GPX elements or attributes
+
- File I/O operations *)
type error = Error.t
-
(** GPX exception raised for errors *)
+
(** GPX exception raised for unrecoverable errors.
+
+
Most functions return [Result.t] for error handling, but this exception
+
may be raised in exceptional circumstances. *)
exception Gpx_error of error
-
(** {1 Parsing Functions} *)
+
(** {1 Parsing Functions}
+
+
Parse GPX data from various sources. All parsing functions support optional
+
validation to check compliance with GPX specification constraints. *)
-
(** Parse GPX from XML input.
+
(** Parse GPX from XML input source.
-
@param validate Whether to validate the document after parsing
-
@param input XMLm input source
-
@return Parsed GPX document or error *)
+
Reads GPX data from an {!Xmlm.input} source, which can be created from
+
files, strings, or other input sources using the {{:https://erratique.ch/software/xmlm}Xmlm} library.
+
+
@param validate If [true] (default [false]), validates the parsed document
+
against GPX specification rules. Validation checks coordinate
+
ranges, required elements, and data consistency.
+
@param input XMLm input source created with [Xmlm.make_input]
+
@return [Ok gpx] with parsed document, or [Error e] if parsing fails
+
+
Example:
+
{[
+
let input = Xmlm.make_input (`String (0, gpx_xml_string)) in
+
match parse ~validate:true input with
+
| Ok gpx -> Printf.printf "Parsed %d waypoints\n" (List.length (Doc.waypoints gpx))
+
| Error e -> Printf.eprintf "Parse error: %s\n" (Error.to_string e)
+
]} *)
val parse : ?validate:bool -> Xmlm.input -> (t, error) result
-
(** Parse GPX from string.
+
(** Parse GPX from XML string.
+
+
Convenience function for parsing GPX data from a string. Equivalent to
+
creating an {!Xmlm.input} from the string and calling {!parse}.
-
@param validate Whether to validate the document after parsing
-
@param s XML string to parse
-
@return Parsed GPX document or error *)
+
@param validate If [true] (default [false]), validates the parsed document
+
@param s Complete GPX XML document as a string
+
@return [Ok gpx] with parsed document, or [Error e] if parsing fails
+
+
Example:
+
{[
+
let gpx_xml = {|<?xml version="1.0"?>
+
<gpx version="1.1" creator="my-app">
+
<wpt lat="37.7749" lon="-122.4194">
+
<name>San Francisco</name>
+
</wpt>
+
</gpx>|} in
+
match parse_string ~validate:true gpx_xml with
+
| Ok gpx -> print_endline "Successfully parsed GPX"
+
| Error e -> Printf.eprintf "Error: %s\n" (Error.to_string e)
+
]} *)
val parse_string : ?validate:bool -> string -> (t, error) result
-
(** {1 Writing Functions} *)
+
(** {1 Writing Functions}
+
+
Generate GPX XML from document structures. All writing functions support
+
optional validation before output generation. *)
-
(** Write GPX to XML output.
+
(** Write GPX to XML output destination.
-
@param validate Whether to validate before writing
-
@param output XMLm output destination
+
Generates standard GPX 1.1 XML and writes it to an {!Xmlm.dest} destination.
+
The output destination can target files, buffers, or other sinks.
+
+
@param validate If [true] (default [false]), validates the document before
+
writing to ensure GPX specification compliance
+
@param dest XMLm output destination created with [Xmlm.make_output]
@param gpx GPX document to write
-
@return Success or error *)
+
@return [Ok ()] on success, or [Error e] if writing fails
+
+
Example:
+
{[
+
let output = Buffer.create 1024 in
+
let dest = Xmlm.make_output (`Buffer output) in
+
match write ~validate:true dest gpx with
+
| Ok () -> Buffer.contents output
+
| Error e -> failwith (Error.to_string e)
+
]} *)
val write : ?validate:bool -> Xmlm.dest -> t -> (unit, error) result
-
(** Write GPX to string.
+
(** Write GPX to XML string.
+
+
Convenience function that generates a complete GPX XML document as a string.
+
The output includes XML declaration and proper namespace declarations.
+
+
@param validate If [true] (default [false]), validates before writing
+
@param gpx GPX document to serialize
+
@return [Ok xml_string] with complete GPX XML, or [Error e] if generation fails
-
@param validate Whether to validate before writing
-
@param gpx GPX document to write
-
@return XML string or error *)
+
Example:
+
{[
+
match write_string ~validate:true gpx with
+
| Ok xml ->
+
print_endline "Generated GPX:";
+
print_endline xml
+
| Error e ->
+
Printf.eprintf "Failed to generate GPX: %s\n" (Error.to_string e)
+
]} *)
val write_string : ?validate:bool -> t -> (string, error) result
(** {1 Validation Functions}
-
Validate GPX documents for correctness and best practices. *)
+
Comprehensive validation against GPX specification rules and best practices.
+
Validation checks coordinate ranges, required elements, data consistency,
+
and common issues that may cause problems for GPS applications. *)
-
(** Validation issue with severity level *)
+
(** A validation issue found during GPX document checking.
+
+
Issues are classified as either errors (specification violations that make
+
the GPX invalid) or warnings (best practice violations or suspicious data). *)
type validation_issue = Validate.validation_issue = {
-
level : [`Error | `Warning]; (** Severity level *)
-
message : string; (** Issue description *)
-
location : string option; (** Location in document *)
+
level : [`Error | `Warning]; (** [`Error] for specification violations, [`Warning] for best practice issues *)
+
message : string; (** Human-readable description of the issue *)
+
location : string option; (** Optional location context (e.g., "waypoint 1", "track segment 2") *)
}
-
(** Result of validation containing all issues found *)
+
(** Complete validation result with all issues and validity status.
+
+
The [is_valid] field indicates whether the document contains any errors.
+
Documents with only warnings are considered valid. *)
type validation_result = Validate.validation_result = {
-
issues : validation_issue list; (** All validation issues *)
-
is_valid : bool; (** Whether document is valid *)
+
issues : validation_issue list; (** All validation issues found, both errors and warnings *)
+
is_valid : bool; (** [true] if no errors found (warnings are allowed) *)
}
-
(** Validate complete GPX document *)
+
(** Perform comprehensive validation of a GPX document.
+
+
Checks all aspects of the GPX document against the specification:
+
- Coordinate ranges (latitude -90 to +90, longitude -180 to +180)
+
- Required elements and attributes
+
- Data consistency (e.g., time ordering in tracks)
+
- Reasonable value ranges for GPS quality metrics
+
- Proper structure and nesting
+
+
@param gpx The GPX document to validate
+
@return Complete validation result with all issues found
+
+
Example:
+
{[
+
let result = validate_gpx gpx in
+
if result.is_valid then
+
Printf.printf "Document is valid with %d warnings\n"
+
(List.length (List.filter (fun i -> i.level = `Warning) result.issues))
+
else begin
+
print_endline "Document has errors:";
+
List.iter (fun issue ->
+
if issue.level = `Error then
+
Printf.printf " ERROR: %s\n" (format_issue issue)
+
) result.issues
+
end
+
]} *)
val validate_gpx : t -> validation_result
-
(** Quick validation - returns true if document is valid *)
+
(** Quick validation check - returns true if document has no errors.
+
+
Equivalent to [(validate_gpx gpx).is_valid] but potentially more efficient
+
as it can stop at the first error found.
+
+
@param gpx The GPX document to validate
+
@return [true] if valid (no errors), [false] if errors found *)
val is_valid : t -> bool
-
(** Get only error messages *)
-
val get_errors : t -> validation_issue list
+
(** Get only validation errors (specification violations).
+
+
Returns only the issues marked as errors, filtering out warnings.
+
If this list is empty, the document is valid according to the GPX specification.
+
+
@param gpx The GPX document to validate
+
@return List of error-level validation issues *)
+
val errors : t -> validation_issue list
-
(** Get only warning messages *)
-
val get_warnings : t -> validation_issue list
+
(** Get only validation warnings (best practice violations).
+
+
Returns only the issues marked as warnings. These don't make the document
+
invalid but may indicate potential problems or areas for improvement.
+
+
@param gpx The GPX document to validate
+
@return List of warning-level validation issues *)
+
val warnings : t -> validation_issue list
-
(** Format validation issue for display *)
+
(** Format a validation issue for human-readable display.
+
+
Combines the issue message with location context if available.
+
+
@param issue The validation issue to format
+
@return Formatted string suitable for display to users
+
+
Example output: ["Error in waypoint 1: Latitude out of range (-95.0)"] *)
val format_issue : validation_issue -> string
-
(** {1 Constructors and Utilities} *)
+
(** {1 Document Constructors and Utilities}
+
+
Functions for creating GPX documents and basic document operations. *)
-
(** Create new GPX document with required fields *)
+
(** Create a new GPX document with the required creator field.
+
+
Every GPX document must identify its creating application through the
+
[creator] attribute. This is required by the GPX specification and helps
+
identify the source of GPS data.
+
+
The created document:
+
- Uses GPX version 1.1 (the current standard)
+
- Contains no waypoints, routes, or tracks initially
+
- Has no metadata initially
+
- Can be extended using {!Doc} module functions
+
+
@param creator Name of the creating application (e.g., "MyGPS App v1.0")
+
@return Empty GPX document ready for data addition
+
+
Example:
+
{[
+
let gpx = make_gpx ~creator:"MyTracker v2.1" in
+
let gpx = Doc.add_waypoint gpx some_waypoint in
+
let gpx = Doc.add_track gpx some_track in
+
(* gpx now contains waypoints and tracks *)
+
]} *)
val make_gpx : creator:string -> t
-
(** Create empty GPX document *)
-
val empty : creator:string -> t
+
(** Create an empty GPX document with the required creator field.
+
+
Alias for {!make_gpx} provided for consistency with module naming patterns.
+
Creates a document with no GPS data that can be populated using the
+
{!Doc} module functions.
+
+
@param creator Name of the creating application
+
@return Empty GPX document
+
+
Example:
+
{[
+
let gpx = empty ~creator:"GPS Logger" in
+
assert (List.length (Doc.waypoints gpx) = 0);
+
assert (List.length (Doc.tracks gpx) = 0);
+
assert (Doc.creator gpx = "GPS Logger");
+
]} *)
+
val empty : creator:string -> t
+
+
(** {1 Common Patterns and Best Practices}
+
+
{2 Reading GPX Files}
+
+
The most common use case is reading existing GPX files:
+
{[
+
(* From a file using platform-specific modules *)
+
match Gpx_unix.read "track.gpx" with
+
| Ok gpx -> process_gpx gpx
+
| Error e -> handle_error e
+
+
(* From a string *)
+
match parse_string ~validate:true gpx_content with
+
| Ok gpx -> process_gpx gpx
+
| Error e -> handle_error e
+
]}
+
+
{2 Creating GPX Files}
+
+
To create new GPX files with waypoints:
+
{[
+
(* Create coordinates *)
+
let lat = Coordinate.latitude 37.7749 |> Result.get_ok in
+
let lon = Coordinate.longitude (-122.4194) |> Result.get_ok in
+
+
(* Create waypoint *)
+
let waypoint = Waypoint.make lat lon
+
|> Waypoint.with_name "Golden Gate"
+
|> Waypoint.with_description "Famous San Francisco bridge" in
+
+
(* Create document *)
+
let gpx = make_gpx ~creator:"My App v1.0"
+
|> Doc.add_waypoint waypoint in
+
+
(* Write to file *)
+
match Gpx_unix.write "output.gpx" gpx with
+
| Ok () -> print_endline "File written successfully"
+
| Error e -> Printf.eprintf "Write error: %s\n" (Error.to_string e)
+
]}
+
+
{2 Working with Tracks}
+
+
Tracks represent recorded GPS traces with timestamped points:
+
{[
+
(* Create track points with timestamps *)
+
let points = List.map (fun (lat_f, lon_f, time) ->
+
let lat = Coordinate.latitude lat_f |> Result.get_ok in
+
let lon = Coordinate.longitude lon_f |> Result.get_ok in
+
Waypoint.make lat lon |> Waypoint.with_time (Some time)
+
) gps_data in
+
+
(* Create track segment *)
+
let segment = Track.Segment.make points in
+
+
(* Create track *)
+
let track = Track.make ~name:"Morning Run"
+
|> Track.add_segment segment in
+
+
(* Add to document *)
+
let gpx = make_gpx ~creator:"Fitness App"
+
|> Doc.add_track track
+
]}
+
+
{2 Coordinate Systems and Units}
+
+
- All coordinates use WGS84 datum (World Geodetic System 1984)
+
- Latitude ranges from -90.0 (South Pole) to +90.0 (North Pole)
+
- Longitude ranges from -180.0 to +180.0 degrees
+
- Elevations are in meters above mean sea level
+
- Times use RFC 3339 format (ISO 8601 subset)
+
+
{2 Validation Recommendations}
+
+
- Always validate when parsing untrusted GPX data
+
- Validate before writing to catch data consistency issues
+
- Handle both errors and warnings appropriately
+
- Use {!val:is_valid} for quick checks, {!validate_gpx} for detailed analysis
+
+
{2 Performance Considerations}
+
+
- The library uses streaming XML parsing for memory efficiency
+
- Large GPX files with many track points are handled efficiently
+
- Coordinate validation occurs at construction time
+
- Consider using platform-specific modules ({!Gpx_unix}, [Gpx_eio]) for file I/O
+
+
{2 Extension Support}
+
+
The library supports GPX extensions for custom data:
+
{[
+
(* Create extension *)
+
let ext = Extension.make_text
+
~name:"temperature"
+
~namespace:"http://example.com/weather"
+
"25.5" in
+
+
(* Add to waypoint *)
+
let waypoint = Waypoint.make lat lon
+
|> Waypoint.add_extensions [ext]
+
]} *)
+
+
(** {1 Related Modules and Libraries}
+
+
This core module provides the foundation. For complete applications, consider:
+
+
- {!Gpx_unix}: File I/O operations using standard Unix libraries
+
- [Gpx_eio]: Concurrent file I/O using the Eio effects library
+
- {{:https://erratique.ch/software/xmlm}Xmlm}: Underlying XML processing library
+
- {{:https://erratique.ch/software/ptime}Ptime}: Time representation used for timestamps
+
+
{2 External Links}
+
+
- {{:https://www.topografix.com/gpx.asp}Official GPX specification}
+
- {{:https://www.topografix.com/GPX/1/1/gpx.xsd}GPX 1.1 XML Schema}
+
- {{:https://en.wikipedia.org/wiki/GPS_Exchange_Format}GPX Format on Wikipedia}
+
- {{:https://en.wikipedia.org/wiki/World_Geodetic_System}WGS84 Coordinate System} *)
+17 -23
lib/gpx/gpx_doc.ml lib/gpx/doc.ml
···
(** {2 Document Properties} *)
(** Get version *)
-
let get_version t = t.version
+
let version t = t.version
(** Get creator *)
-
let get_creator t = t.creator
+
let creator t = t.creator
(** Get metadata *)
-
let get_metadata t = t.metadata
+
let metadata t = t.metadata
(** Get waypoints *)
-
let get_waypoints t = t.waypoints
+
let waypoints t = t.waypoints
(** Get routes *)
-
let get_routes t = t.routes
+
let routes t = t.routes
(** Get tracks *)
-
let get_tracks t = t.tracks
+
let tracks t = t.tracks
(** Get extensions *)
-
let get_extensions t = t.extensions
+
let extensions t = t.extensions
(** {2 Document Modification} *)
-
(** Set version *)
-
let with_version t version = { t with version }
-
-
(** Set metadata *)
+
(** Update metadata *)
let with_metadata t metadata = { t with metadata = Some metadata }
-
(** Set metadata *)
-
let set_metadata metadata t = { t with metadata = Some metadata }
-
(** Add waypoint *)
let add_waypoint t waypoint = { t with waypoints = t.waypoints @ [waypoint] }
···
(** Check if document has elevation data *)
let has_elevation t =
-
List.exists (fun wpt -> Waypoint.get_elevation wpt <> None) t.waypoints ||
+
List.exists (fun wpt -> Waypoint.elevation wpt <> None) t.waypoints ||
List.exists (fun route ->
-
List.exists (fun pt -> Waypoint.get_elevation pt <> None) (Route.get_points route)
+
List.exists (fun pt -> Waypoint.elevation pt <> None) (Route.points route)
) t.routes ||
List.exists (fun track ->
-
List.exists (fun pt -> Waypoint.get_elevation pt <> None) (Track.all_points track)
+
List.exists (fun pt -> Waypoint.elevation pt <> None) (Track.all_points track)
) t.tracks
(** Check if document has time data *)
let has_time t =
-
List.exists (fun wpt -> Waypoint.get_time wpt <> None) t.waypoints ||
+
List.exists (fun wpt -> Waypoint.time wpt <> None) t.waypoints ||
List.exists (fun route ->
-
List.exists (fun pt -> Waypoint.get_time pt <> None) (Route.get_points route)
+
List.exists (fun pt -> Waypoint.time pt <> None) (Route.points route)
) t.routes ||
List.exists (fun track ->
-
List.exists (fun pt -> Waypoint.get_time pt <> None) (Track.all_points track)
+
List.exists (fun pt -> Waypoint.time pt <> None) (Track.all_points track)
) t.tracks
(** Check if document is empty *)
···
has_time : bool;
}
-
let get_stats t = {
+
let stats t = {
waypoint_count = waypoint_count t;
route_count = route_count t;
track_count = track_count t;
···
(** Pretty print document *)
let pp ppf t =
-
let stats = get_stats t in
+
let stats = stats t in
Format.fprintf ppf "GPX v%s by %s (%d wpt, %d routes, %d tracks, %d total points)"
t.version t.creator
stats.waypoint_count stats.route_count stats.track_count stats.total_points
(** Print document statistics *)
let print_stats t =
-
let stats = get_stats t in
+
let stats = stats t in
Printf.printf "GPX Statistics:\n";
Printf.printf " Version: %s\n" t.version;
Printf.printf " Creator: %s\n" t.creator;
+9 -15
lib/gpx/gpx_doc.mli lib/gpx/doc.mli
···
(** {2 Document Properties} *)
(** Get version *)
-
val get_version : t -> string
+
val version : t -> string
(** Get creator *)
-
val get_creator : t -> string
+
val creator : t -> string
(** Get metadata *)
-
val get_metadata : t -> Metadata.t option
+
val metadata : t -> Metadata.t option
(** Get waypoints *)
-
val get_waypoints : t -> Waypoint.t list
+
val waypoints : t -> Waypoint.t list
(** Get routes *)
-
val get_routes : t -> Route.t list
+
val routes : t -> Route.t list
(** Get tracks *)
-
val get_tracks : t -> Track.t list
+
val tracks : t -> Track.t list
(** Get extensions *)
-
val get_extensions : t -> Extension.t list
+
val extensions : t -> Extension.t list
(** {2 Document Modification} *)
-
(** Set version *)
-
val with_version : t -> string -> t
-
-
(** Set metadata *)
+
(** Update metadata *)
val with_metadata : t -> Metadata.t -> t
-
-
(** Set metadata *)
-
val set_metadata : Metadata.t -> t -> t
(** Add waypoint *)
val add_waypoint : t -> Waypoint.t -> t
···
val is_empty : t -> bool
(** Get document statistics *)
-
val get_stats : t -> stats
+
val stats : t -> stats
(** {2 Comparison and Utilities} *)
+11 -11
lib/gpx/link.ml
···
let make ~href ?text ?type_ () = { href; text; type_ }
(** Get href from link *)
-
let get_href t = t.href
+
let href t = t.href
(** Get optional text from link *)
-
let get_text t = t.text
+
let text t = t.text
(** Get optional type from link *)
-
let get_type t = t.type_
+
let type_ t = t.type_
-
(** Set text on link *)
+
(** Update text *)
let with_text t text = { t with text = Some text }
-
(** Set type on link *)
+
(** Update type *)
let with_type t type_ = { t with type_ = Some type_ }
(** Compare links *)
···
let make_person ?name ?email ?link () = { name; email; link }
(** Get person name *)
-
let get_person_name (p : person) = p.name
+
let person_name (p : person) = p.name
(** Get person email *)
-
let get_person_email (p : person) = p.email
+
let person_email (p : person) = p.email
(** Get person link *)
-
let get_person_link (p : person) = p.link
+
let person_link (p : person) = p.link
(** Compare persons *)
let compare_person p1 p2 =
···
let make_copyright ~author ?year ?license () = { author; year; license }
(** Get copyright author *)
-
let get_copyright_author (c : copyright) = c.author
+
let copyright_author (c : copyright) = c.author
(** Get copyright year *)
-
let get_copyright_year (c : copyright) = c.year
+
let copyright_year (c : copyright) = c.year
(** Get copyright license *)
-
let get_copyright_license (c : copyright) = c.license
+
let copyright_license (c : copyright) = c.license
(** Compare copyrights *)
let compare_copyright c1 c2 =
+12 -11
lib/gpx/link.mli
···
val make : href:string -> ?text:string -> ?type_:string -> unit -> t
(** Get href from link *)
-
val get_href : t -> string
+
val href : t -> string
(** Get optional text from link *)
-
val get_text : t -> string option
+
val text : t -> string option
(** Get optional type from link *)
-
val get_type : t -> string option
+
val type_ : t -> string option
-
(** Set text on link *)
+
(** Update text *)
val with_text : t -> string -> t
-
(** Set type on link *)
+
(** Update type *)
val with_type : t -> string -> t
+
(** Compare links *)
val compare : t -> t -> int
···
val make_person : ?name:string -> ?email:string -> ?link:t -> unit -> person
(** Get person name *)
-
val get_person_name : person -> string option
+
val person_name : person -> string option
(** Get person email *)
-
val get_person_email : person -> string option
+
val person_email : person -> string option
(** Get person link *)
-
val get_person_link : person -> t option
+
val person_link : person -> t option
(** Compare persons *)
val compare_person : person -> person -> int
···
val make_copyright : author:string -> ?year:int -> ?license:string -> unit -> copyright
(** Get copyright author *)
-
val get_copyright_author : copyright -> string
+
val copyright_author : copyright -> string
(** Get copyright year *)
-
val get_copyright_year : copyright -> int option
+
val copyright_year : copyright -> int option
(** Get copyright license *)
-
val get_copyright_license : copyright -> string option
+
val copyright_license : copyright -> string option
(** Compare copyrights *)
val compare_copyright : copyright -> copyright -> int
+20 -31
lib/gpx/metadata.ml
···
| Error e, _, _, _ | _, Error e, _, _ | _, _, Error e, _ | _, _, _, Error e -> Error e
(** Get corner coordinates *)
-
let get_min_coords t = Coordinate.make t.minlat t.minlon
-
let get_max_coords t = Coordinate.make t.maxlat t.maxlon
+
let min_coords t = Coordinate.make t.minlat t.minlon
+
let max_coords t = Coordinate.make t.maxlat t.maxlon
(** Get all bounds as tuple *)
-
let get_bounds t = (t.minlat, t.minlon, t.maxlat, t.maxlon)
+
let bounds t = (t.minlat, t.minlon, t.maxlat, t.maxlon)
(** Check if coordinate is within bounds *)
let contains bounds coord =
-
let lat = Coordinate.get_lat coord in
-
let lon = Coordinate.get_lon coord in
+
let lat = Coordinate.lat coord in
+
let lon = Coordinate.lon coord in
Coordinate.latitude_to_float bounds.minlat <= Coordinate.latitude_to_float lat &&
Coordinate.latitude_to_float lat <= Coordinate.latitude_to_float bounds.maxlat &&
Coordinate.longitude_to_float bounds.minlon <= Coordinate.longitude_to_float lon &&
···
let make ~name = { empty with name = Some name }
(** Get name *)
-
let get_name t = t.name
+
let name t = t.name
(** Get description *)
-
let get_description t = t.desc
+
let description t = t.desc
(** Get author *)
-
let get_author t = t.author
+
let author t = t.author
(** Get copyright *)
-
let get_copyright t = t.copyright
+
let copyright t = t.copyright
(** Get links *)
-
let get_links t = t.links
+
let links t = t.links
(** Get time *)
-
let get_time t = t.time
+
let time t = t.time
(** Get keywords *)
-
let get_keywords t = t.keywords
+
let keywords t = t.keywords
(** Get bounds *)
-
let get_bounds t = t.bounds
+
let bounds_opt t = t.bounds
-
(** Set name *)
-
let set_name name t = { t with name = Some name }
-
-
(** Set description *)
-
let set_description desc t = { t with desc = Some desc }
-
-
(** Set author *)
-
let set_author author t = { t with author = Some author }
-
-
(** Add link *)
-
let add_link t link = { t with links = link :: t.links }
-
-
(** Functional setters for building metadata *)
-
-
(** Set name *)
+
(** Update name *)
let with_name t name = { t with name = Some name }
-
(** Set description *)
+
(** Update description *)
let with_description t desc = { t with desc = Some desc }
-
(** Set keywords *)
+
(** Update keywords *)
let with_keywords t keywords = { t with keywords = Some keywords }
-
(** Set time *)
+
(** Update time *)
let with_time t time = { t with time }
+
+
(** Add link *)
+
let add_link t link = { t with links = link :: t.links }
(** Add extensions *)
let add_extensions t extensions = { t with extensions = extensions @ t.extensions }
+16 -25
lib/gpx/metadata.mli
···
val make_from_floats : minlat:float -> minlon:float -> maxlat:float -> maxlon:float -> (t, string) result
(** Get minimum corner coordinates *)
-
val get_min_coords : t -> Coordinate.t
+
val min_coords : t -> Coordinate.t
(** Get maximum corner coordinates *)
-
val get_max_coords : t -> Coordinate.t
+
val max_coords : t -> Coordinate.t
(** Get all bounds as tuple *)
-
val get_bounds : t -> (Coordinate.latitude * Coordinate.longitude * Coordinate.latitude * Coordinate.longitude)
+
val bounds : t -> (Coordinate.latitude * Coordinate.longitude * Coordinate.latitude * Coordinate.longitude)
(** Check if coordinate is within bounds *)
val contains : t -> Coordinate.t -> bool
···
val make : name:string -> t
(** Get name *)
-
val get_name : t -> string option
+
val name : t -> string option
(** Get description *)
-
val get_description : t -> string option
+
val description : t -> string option
(** Get author *)
-
val get_author : t -> Link.person option
+
val author : t -> Link.person option
(** Get copyright *)
-
val get_copyright : t -> Link.copyright option
+
val copyright : t -> Link.copyright option
(** Get links *)
-
val get_links : t -> Link.t list
+
val links : t -> Link.t list
(** Get time *)
-
val get_time : t -> Ptime.t option
+
val time : t -> Ptime.t option
(** Get keywords *)
-
val get_keywords : t -> string option
+
val keywords : t -> string option
(** Get bounds *)
-
val get_bounds : t -> bounds option
-
-
(** Set name *)
-
val set_name : string -> t -> t
-
-
(** Set description *)
-
val set_description : string -> t -> t
-
-
(** Set author *)
-
val set_author : Link.person -> t -> t
+
val bounds_opt : t -> bounds option
-
(** Functional setters for building metadata *)
+
(** Functional operations for building metadata *)
-
(** Set name *)
+
(** Update name *)
val with_name : t -> string -> t
-
(** Set description *)
+
(** Update description *)
val with_description : t -> string -> t
-
(** Set keywords *)
+
(** Update keywords *)
val with_keywords : t -> string -> t
-
(** Set time *)
+
(** Update time *)
val with_time : t -> Ptime.t option -> t
(** Add link *)
+9 -9
lib/gpx/parser.ml
···
let namespace = if ns = "" then None else Some ns in
let attributes = List.map (fun ((_, n), v) -> (n, v)) attrs in
let* content = parse_extension_content parser in
-
Ok (Extension.make ~namespace ~name ~attributes ~content ())
+
Ok (Extension.make ?namespace ~name ~attributes ~content ())
and parse_extension_content parser =
Buffer.clear parser.text_buffer;
···
in
let* (version, creator) = find_gpx_root () in
-
let gpx = Gpx_doc.empty ~creator in
-
parse_gpx_elements parser (Gpx_doc.with_version gpx version)
+
let gpx = Doc.empty ~creator in
+
parse_gpx_elements parser { gpx with version }
and parse_gpx_elements parser gpx =
let rec loop gpx =
···
(match name with
| "metadata" ->
let* metadata = parse_metadata parser in
-
loop (Gpx_doc.with_metadata gpx metadata)
+
loop (Doc.with_metadata gpx metadata)
| "wpt" ->
let* (lat, lon) = parse_coordinates attrs "wpt" in
let* waypoint = parse_waypoint_data parser lat lon in
-
loop (Gpx_doc.add_waypoint gpx waypoint)
+
loop (Doc.add_waypoint gpx waypoint)
| "rte" ->
let* route = parse_route parser in
-
loop (Gpx_doc.add_route gpx route)
+
loop (Doc.add_route gpx route)
| "trk" ->
let* track = parse_track parser in
-
loop (Gpx_doc.add_track gpx track)
+
loop (Doc.add_track gpx track)
| "extensions" ->
let* extensions = parse_extensions parser in
-
loop (Gpx_doc.add_extensions gpx extensions)
+
loop (Doc.add_extensions gpx extensions)
| _ ->
let* _ = skip_element parser in
loop gpx)
···
(** Parse from string *)
let parse_string ?(validate=false) s =
let input = Xmlm.make_input (`String (0, s)) in
-
parse ~validate input
+
parse ~validate input
+2 -2
lib/gpx/parser.mli
···
(** GPX streaming parser using xmlm *)
(** Parse a GPX document from an xmlm input source *)
-
val parse : ?validate:bool -> Xmlm.input -> (Gpx_doc.t, Error.t) result
+
val parse : ?validate:bool -> Xmlm.input -> (Doc.t, Error.t) result
(** Parse a GPX document from a string *)
-
val parse_string : ?validate:bool -> string -> (Gpx_doc.t, Error.t) result
+
val parse_string : ?validate:bool -> string -> (Doc.t, Error.t) result
+12 -17
lib/gpx/route.ml
···
let make_rtept (lat_f, lon_f) =
match Waypoint.make_from_floats ~lat:lat_f ~lon:lon_f () with
| Ok wpt -> wpt
-
| Error e -> failwith e
+
| Error e -> invalid_arg e
in
let rtepts = List.map make_rtept coords in
{ empty with name = Some name; rtepts }
(** Get route name *)
-
let get_name t = t.name
+
let name t = t.name
(** Get route description *)
-
let get_description t = t.desc
+
let description t = t.desc
(** Get route points *)
-
let get_points t = t.rtepts
+
let points t = t.rtepts
(** Get route point count *)
let point_count t = List.length t.rtepts
-
(** Set name *)
-
let set_name name t = { t with name = Some name }
-
-
(** Set description *)
-
let set_description desc t = { t with desc = Some desc }
(** Clear all points *)
let clear_points t = { t with rtepts = [] }
···
| [] -> None
| p :: _ -> Some p
-
(** {2 Functional Setters} *)
+
(** {2 Functional Operations} *)
-
(** Set name *)
+
(** Update name *)
let with_name t name = { t with name = Some name }
-
(** Set comment *)
+
(** Update comment *)
let with_comment t cmt = { t with cmt = Some cmt }
-
(** Set description *)
+
(** Update description *)
let with_description t desc = { t with desc = Some desc }
-
(** Set source *)
+
(** Update source *)
let with_source t src = { t with src = Some src }
-
(** Set number *)
+
(** Update number *)
let with_number t number = { t with number = Some number }
-
(** Set type *)
+
(** Update type *)
let with_type t type_ = { t with type_ = Some type_ }
(** Add point *)
···
let pp ppf t =
match t.name with
| Some name -> Format.fprintf ppf "\"%s\" (%d points)" name (point_count t)
-
| None -> Format.fprintf ppf "(unnamed route, %d points)" (point_count t)
+
| None -> Format.fprintf ppf "(unnamed route, %d points)" (point_count t)
+13 -19
lib/gpx/route.mli
···
(** Create route from coordinate list.
@param name Route name
@param coords List of (latitude, longitude) pairs
-
@raises Failure on invalid coordinates *)
+
@raises Invalid_argument on invalid coordinates *)
val make_from_coords : name:string -> (float * float) list -> t
(** {2 Route Properties} *)
(** Get route name *)
-
val get_name : t -> string option
+
val name : t -> string option
(** Get route description *)
-
val get_description : t -> string option
+
val description : t -> string option
(** Get route points *)
-
val get_points : t -> point list
+
val points : t -> point list
(** Get route point count *)
val point_count : t -> int
···
(** {2 Route Modification} *)
-
(** Set name *)
-
val set_name : string -> t -> t
-
-
(** Set description *)
-
val set_description : string -> t -> t
-
(** Clear all points *)
val clear_points : t -> t
···
(** Get last point *)
val last_point : t -> point option
-
(** {2 Functional Setters} *)
+
(** {2 Functional Operations} *)
-
(** Set name *)
+
(** Update name *)
val with_name : t -> string -> t
-
(** Set comment *)
-
val with_comment : t -> string -> t
+
(** Update comment *)
+
val with_comment : t -> string -> t
-
(** Set description *)
+
(** Update description *)
val with_description : t -> string -> t
-
(** Set source *)
+
(** Update source *)
val with_source : t -> string -> t
-
(** Set number *)
+
(** Update number *)
val with_number : t -> int -> t
-
(** Set type *)
+
(** Update type *)
val with_type : t -> string -> t
(** Add point *)
···
val equal : t -> t -> bool
(** Pretty print route *)
-
val pp : Format.formatter -> t -> unit
+
val pp : Format.formatter -> t -> unit
+13 -18
lib/gpx/track.ml
···
let make_trkpt (lat_f, lon_f) =
match Waypoint.make_from_floats ~lat:lat_f ~lon:lon_f () with
| Ok wpt -> wpt
-
| Error e -> failwith e
+
| Error e -> invalid_arg e
in
let trkpts = List.map make_trkpt coords in
{ trkpts; extensions = [] }
(** Get points *)
-
let get_points t = t.trkpts
+
let points t = t.trkpts
(** Get point count *)
let point_count t = List.length t.trkpts
···
{ empty with name = Some name; trksegs = [segment] }
(** Get track name *)
-
let get_name t = t.name
+
let name t = t.name
(** Get track description *)
-
let get_description t = t.desc
+
let description t = t.desc
(** Get track segments *)
-
let get_segments t = t.trksegs
+
let segments t = t.trksegs
(** Get segment count *)
let segment_count t = List.length t.trksegs
···
let point_count t =
List.fold_left (fun acc seg -> acc + Segment.point_count seg) 0 t.trksegs
-
(** Set name *)
-
let set_name name t = { t with name = Some name }
-
-
(** Set description *)
-
let set_description desc t = { t with desc = Some desc }
(** Clear all segments *)
let clear_segments t = { t with trksegs = [] }
···
(** Test track equality *)
let equal t1 t2 = compare t1 t2 = 0
-
(** {2 Functional Setters} *)
+
(** {2 Functional Operations} *)
-
(** Set name *)
+
(** Update name *)
let with_name t name = { t with name = Some name }
-
(** Set comment *)
+
(** Update comment *)
let with_comment t cmt = { t with cmt = Some cmt }
-
(** Set description *)
+
(** Update description *)
let with_description t desc = { t with desc = Some desc }
-
(** Set source *)
+
(** Update source *)
let with_source t src = { t with src = Some src }
-
(** Set number *)
+
(** Update number *)
let with_number t number = { t with number = Some number }
-
(** Set type *)
+
(** Update type *)
let with_type t type_ = { t with type_ = Some type_ }
(** Add segment *)
···
| Some name -> Format.fprintf ppf "\"%s\" (%d segments, %d points)"
name (segment_count t) (point_count t)
| None -> Format.fprintf ppf "(unnamed track, %d segments, %d points)"
-
(segment_count t) (point_count t)
+
(segment_count t) (point_count t)
+13 -19
lib/gpx/track.mli
···
val make : point list -> t
(** Create segment from coordinate list.
-
@raises Failure on invalid coordinates *)
+
@raises Invalid_argument on invalid coordinates *)
val make_from_coords : (float * float) list -> t
(** Get points *)
-
val get_points : t -> point list
+
val points : t -> point list
(** Get point count *)
val point_count : t -> int
···
(** {2 Track Properties} *)
(** Get track name *)
-
val get_name : t -> string option
+
val name : t -> string option
(** Get track description *)
-
val get_description : t -> string option
+
val description : t -> string option
(** Get track segments *)
-
val get_segments : t -> segment list
+
val segments : t -> segment list
(** Get segment count *)
val segment_count : t -> int
···
val is_empty : t -> bool
(** {2 Track Modification} *)
-
-
(** Set name *)
-
val set_name : string -> t -> t
-
-
(** Set description *)
-
val set_description : string -> t -> t
(** Clear all segments *)
val clear_segments : t -> t
···
(** Test track equality *)
val equal : t -> t -> bool
-
(** {2 Functional Setters} *)
+
(** {2 Functional Operations} *)
-
(** Set name *)
+
(** Update name *)
val with_name : t -> string -> t
-
(** Set comment *)
+
(** Update comment *)
val with_comment : t -> string -> t
-
(** Set description *)
+
(** Update description *)
val with_description : t -> string -> t
-
(** Set source *)
+
(** Update source *)
val with_source : t -> string -> t
-
(** Set number *)
+
(** Update number *)
val with_number : t -> int -> t
-
(** Set type *)
+
(** Update type *)
val with_type : t -> string -> t
(** Add segment *)
···
val add_extensions : t -> Extension.t list -> t
(** Pretty print track *)
-
val pp : Format.formatter -> t -> unit
+
val pp : Format.formatter -> t -> unit
+23 -23
lib/gpx/validate.ml
···
let issues = ref [] in
(* Check for negative satellite count *)
-
(match Waypoint.get_sat wpt with
+
(match Waypoint.sat wpt with
| Some sat when sat < 0 ->
issues := make_warning ~location ("Negative satellite count: " ^ string_of_int sat) :: !issues
| _ -> ());
···
| _ -> ()
in
-
check_precision "hdop" (Waypoint.get_hdop wpt);
-
check_precision "vdop" (Waypoint.get_vdop wpt);
-
check_precision "pdop" (Waypoint.get_pdop wpt);
+
check_precision "hdop" (Waypoint.hdop wpt);
+
check_precision "vdop" (Waypoint.vdop wpt);
+
check_precision "pdop" (Waypoint.pdop wpt);
(* Check elevation reasonableness *)
-
(match Waypoint.get_elevation wpt with
+
(match Waypoint.elevation wpt with
| Some ele when ele < -15000.0 ->
issues := make_warning ~location (Printf.sprintf "Very low elevation: %.2f m" ele) :: !issues
| Some ele when ele > 15000.0 ->
···
| _ -> ());
(* Check DGPS age *)
-
(match Waypoint.get_ageofdgpsdata wpt with
+
(match Waypoint.ageofdgpsdata wpt with
| Some age when age < 0.0 ->
issues := make_error ~location "Negative DGPS age" :: !issues
| _ -> ());
···
let issues = ref [] in
let location = "bounds" in
-
let (minlat, minlon, maxlat, maxlon) = Metadata.Bounds.get_bounds bounds in
+
let (minlat, minlon, maxlat, maxlon) = Metadata.Bounds.bounds bounds in
if Coordinate.latitude_to_float minlat >= Coordinate.latitude_to_float maxlat then
issues := make_error ~location "minlat must be less than maxlat" :: !issues;
···
let issues = ref [] in
(* Validate bounds if present *)
-
(match Metadata.get_bounds metadata with
+
(match Metadata.bounds_opt metadata with
| Some bounds -> issues := validate_bounds bounds @ !issues
| None -> ());
(* Check for reasonable copyright year *)
-
(match Metadata.get_copyright metadata with
+
(match Metadata.copyright metadata with
| Some copyright ->
-
(match Link.get_copyright_year copyright with
+
(match Link.copyright_year copyright with
| Some year when year < 1900 || year > 2100 ->
issues := make_warning ~location:"metadata.copyright"
(Printf.sprintf "Unusual copyright year: %d" year) :: !issues
···
let location = "route" in
(* Check for empty route *)
-
let points = Route.get_points route in
+
let points = Route.points route in
if points = [] then
issues := make_warning ~location "Route has no points" :: !issues;
···
let location = Printf.sprintf "track.trkseg[%d]" seg_idx in
(* Check for empty segment *)
-
let points = Track.Segment.get_points trkseg in
+
let points = Track.Segment.points trkseg in
if points = [] then
issues := make_warning ~location "Track segment has no points" :: !issues;
···
let rec check_time_order prev_time = function
| [] -> ()
| trkpt :: rest ->
-
(match (prev_time, Waypoint.get_time trkpt) with
+
(match (prev_time, Waypoint.time trkpt) with
| (Some prev, Some curr) when Ptime.compare prev curr > 0 ->
issues := make_warning ~location "Track points not in chronological order" :: !issues
| _ -> ());
-
check_time_order (Waypoint.get_time trkpt) rest
+
check_time_order (Waypoint.time trkpt) rest
in
check_time_order None points;
···
let location = "track" in
(* Check for empty track *)
-
let segments = Track.get_segments track in
+
let segments = Track.segments track in
if segments = [] then
issues := make_warning ~location "Track has no segments" :: !issues;
···
let issues = ref [] in
(* Check GPX version *)
-
let version = Gpx_doc.get_version gpx in
+
let version = Doc.version gpx in
if version <> "1.0" && version <> "1.1" then
issues := make_error ~location:"gpx"
(Printf.sprintf "Unsupported GPX version: %s (supported: 1.0, 1.1)" version) :: !issues
···
"GPX 1.0 detected - consider upgrading to GPX 1.1 for better compatibility" :: !issues;
(* Check for empty creator *)
-
let creator = Gpx_doc.get_creator gpx in
+
let creator = Doc.creator gpx in
if String.trim creator = "" then
issues := make_error ~location:"gpx" "Creator cannot be empty" :: !issues;
(* Validate metadata *)
-
(match Gpx_doc.get_metadata gpx with
+
(match Doc.metadata gpx with
| Some metadata -> issues := validate_metadata metadata @ !issues
| None -> ());
(* Validate waypoints *)
-
let waypoints = Gpx_doc.get_waypoints gpx in
+
let waypoints = Doc.waypoints gpx in
List.iteri (fun i wpt ->
let location = Printf.sprintf "waypoint[%d]" i in
issues := validate_waypoint_data wpt location @ !issues
) waypoints;
(* Validate routes *)
-
let routes = Gpx_doc.get_routes gpx in
+
let routes = Doc.routes gpx in
List.iteri (fun _i route ->
issues := validate_route route @ !issues
) routes;
(* Validate tracks *)
-
let tracks = Gpx_doc.get_tracks gpx in
+
let tracks = Doc.tracks gpx in
List.iteri (fun _i track ->
issues := validate_track track @ !issues
) tracks;
···
result.is_valid
(** Get only error messages *)
-
let get_errors gpx =
+
let errors gpx =
let result = validate_gpx gpx in
List.filter (fun issue -> issue.level = `Error) result.issues
(** Get only warning messages *)
-
let get_warnings gpx =
+
let warnings gpx =
let result = validate_gpx gpx in
List.filter (fun issue -> issue.level = `Warning) result.issues
+4 -4
lib/gpx/validate.mli
···
}
(** Validate a complete GPX document *)
-
val validate_gpx : Gpx_doc.t -> validation_result
+
val validate_gpx : Doc.t -> validation_result
(** Quick validation - returns true if document is valid *)
-
val is_valid : Gpx_doc.t -> bool
+
val is_valid : Doc.t -> bool
(** Get only error messages *)
-
val get_errors : Gpx_doc.t -> validation_issue list
+
val errors : Doc.t -> validation_issue list
(** Get only warning messages *)
-
val get_warnings : Gpx_doc.t -> validation_issue list
+
val warnings : Doc.t -> validation_issue list
(** Format validation issue for display *)
val format_issue : validation_issue -> string
+39 -53
lib/gpx/waypoint.ml
···
| Error e, _ | _, Error e -> Error e
(** Get coordinate pair *)
-
let get_coordinate t = Coordinate.make t.lat t.lon
+
let coordinate t = Coordinate.make t.lat t.lon
(** Get latitude *)
-
let get_lat t = t.lat
+
let lat t = t.lat
(** Get longitude *)
-
let get_lon t = t.lon
+
let lon t = t.lon
(** Get coordinate as float pair *)
let to_floats t = (Coordinate.latitude_to_float t.lat, Coordinate.longitude_to_float t.lon)
(** Get elevation *)
-
let get_elevation t = t.ele
+
let elevation t = t.ele
(** Get time *)
-
let get_time t = t.time
+
let time t = t.time
(** Get name *)
-
let get_name t = t.name
+
let name t = t.name
(** Get description *)
-
let get_description t = t.desc
+
let description t = t.desc
(** Get comment *)
-
let get_comment t = t.cmt
+
let comment t = t.cmt
(** Get source *)
-
let get_source t = t.src
+
let source t = t.src
(** Get symbol *)
-
let get_symbol t = t.sym
+
let symbol t = t.sym
(** Get type *)
-
let get_type t = t.type_
+
let type_ t = t.type_
(** Get fix type *)
-
let get_fix t = t.fix
+
let fix t = t.fix
(** Get satellite count *)
-
let get_sat t = t.sat
+
let sat t = t.sat
(** Get horizontal dilution of precision *)
-
let get_hdop t = t.hdop
+
let hdop t = t.hdop
(** Get vertical dilution of precision *)
-
let get_vdop t = t.vdop
+
let vdop t = t.vdop
(** Get position dilution of precision *)
-
let get_pdop t = t.pdop
+
let pdop t = t.pdop
(** Get magnetic variation *)
-
let get_magvar t = t.magvar
+
let magvar t = t.magvar
(** Get geoid height *)
-
let get_geoidheight t = t.geoidheight
+
let geoidheight t = t.geoidheight
(** Get age of DGPS data *)
-
let get_ageofdgpsdata t = t.ageofdgpsdata
+
let ageofdgpsdata t = t.ageofdgpsdata
(** Get DGPS ID *)
-
let get_dgpsid t = t.dgpsid
+
let dgpsid t = t.dgpsid
(** Get links *)
-
let get_links t = t.links
+
let links t = t.links
(** Get extensions *)
-
let get_extensions t = t.extensions
-
-
(** Set name *)
-
let set_name name t = { t with name = Some name }
-
-
(** Set description *)
-
let set_description desc t = { t with desc = Some desc }
-
-
(** Set elevation *)
-
let set_elevation ele t = { t with ele = Some ele }
-
-
(** Set time *)
-
let set_time time t = { t with time = Some time }
+
let extensions t = t.extensions
-
(** Functional setters for building waypoints *)
-
-
(** Set elevation *)
+
(** Update elevation *)
let with_elevation t ele = { t with ele = Some ele }
-
(** Set time *)
+
(** Update time *)
let with_time t time = { t with time }
-
(** Set name *)
+
(** Update name *)
let with_name t name = { t with name = Some name }
-
(** Set comment *)
+
(** Update comment *)
let with_comment t cmt = { t with cmt = Some cmt }
-
(** Set description *)
+
(** Update description *)
let with_description t desc = { t with desc = Some desc }
-
(** Set source *)
+
(** Update source *)
let with_source t src = { t with src = Some src }
-
(** Set symbol *)
+
(** Update symbol *)
let with_symbol t sym = { t with sym = Some sym }
-
(** Set type *)
+
(** Update type *)
let with_type t type_ = { t with type_ = Some type_ }
-
(** Set fix type *)
+
(** Update fix *)
let with_fix t fix = { t with fix }
-
(** Set satellite count *)
+
(** Update satellite count *)
let with_sat t sat = { t with sat = Some sat }
-
(** Set horizontal dilution of precision *)
+
(** Update HDOP *)
let with_hdop t hdop = { t with hdop = Some hdop }
-
(** Set vertical dilution of precision *)
+
(** Update VDOP *)
let with_vdop t vdop = { t with vdop = Some vdop }
-
(** Set position dilution of precision *)
+
(** Update PDOP *)
let with_pdop t pdop = { t with pdop = Some pdop }
-
(** Set magnetic variation *)
+
(** Update magnetic variation *)
let with_magvar t magvar = { t with magvar = Some magvar }
-
(** Set geoid height *)
+
(** Update geoid height *)
let with_geoidheight t geoidheight = { t with geoidheight = Some geoidheight }
-
(** Set age of DGPS data *)
+
(** Update age of DGPS data *)
let with_ageofdgpsdata t ageofdgpsdata = { t with ageofdgpsdata = Some ageofdgpsdata }
-
(** Set DGPS ID *)
+
(** Update DGPS ID *)
let with_dgpsid t dgpsid = { t with dgpsid = Some dgpsid }
(** Add link *)
+40 -52
lib/gpx/waypoint.mli
···
val make_from_floats : lat:float -> lon:float -> ?name:string -> ?desc:string -> unit -> (t, string) result
(** Get coordinate pair *)
-
val get_coordinate : t -> Coordinate.t
+
val coordinate : t -> Coordinate.t
(** Get latitude *)
-
val get_lat : t -> Coordinate.latitude
+
val lat : t -> Coordinate.latitude
(** Get longitude *)
-
val get_lon : t -> Coordinate.longitude
+
val lon : t -> Coordinate.longitude
(** Get coordinate as float pair *)
val to_floats : t -> float * float
(** Get elevation *)
-
val get_elevation : t -> float option
+
val elevation : t -> float option
(** Get time *)
-
val get_time : t -> Ptime.t option
+
val time : t -> Ptime.t option
(** Get name *)
-
val get_name : t -> string option
+
val name : t -> string option
(** Get description *)
-
val get_description : t -> string option
+
val description : t -> string option
(** Get comment *)
-
val get_comment : t -> string option
+
val comment : t -> string option
(** Get source *)
-
val get_source : t -> string option
+
val source : t -> string option
(** Get symbol *)
-
val get_symbol : t -> string option
+
val symbol : t -> string option
(** Get type *)
-
val get_type : t -> string option
+
val type_ : t -> string option
(** Get fix type *)
-
val get_fix : t -> fix_type option
+
val fix : t -> fix_type option
(** Get satellite count *)
-
val get_sat : t -> int option
+
val sat : t -> int option
(** Get horizontal dilution of precision *)
-
val get_hdop : t -> float option
+
val hdop : t -> float option
(** Get vertical dilution of precision *)
-
val get_vdop : t -> float option
+
val vdop : t -> float option
(** Get position dilution of precision *)
-
val get_pdop : t -> float option
+
val pdop : t -> float option
(** Get magnetic variation *)
-
val get_magvar : t -> Coordinate.degrees option
+
val magvar : t -> Coordinate.degrees option
(** Get geoid height *)
-
val get_geoidheight : t -> float option
+
val geoidheight : t -> float option
(** Get age of DGPS data *)
-
val get_ageofdgpsdata : t -> float option
+
val ageofdgpsdata : t -> float option
(** Get DGPS ID *)
-
val get_dgpsid : t -> int option
+
val dgpsid : t -> int option
(** Get links *)
-
val get_links : t -> Link.t list
+
val links : t -> Link.t list
(** Get extensions *)
-
val get_extensions : t -> Extension.t list
-
-
(** Set name *)
-
val set_name : string -> t -> t
-
-
(** Set description *)
-
val set_description : string -> t -> t
-
-
(** Set elevation *)
-
val set_elevation : float -> t -> t
+
val extensions : t -> Extension.t list
-
(** Set time *)
-
val set_time : Ptime.t -> t -> t
+
(** Functional operations for building waypoints *)
-
(** Functional setters for building waypoints *)
-
-
(** Set elevation *)
+
(** Update elevation *)
val with_elevation : t -> float -> t
-
(** Set time *)
+
(** Update time *)
val with_time : t -> Ptime.t option -> t
-
(** Set name *)
+
(** Update name *)
val with_name : t -> string -> t
-
(** Set comment *)
+
(** Update comment *)
val with_comment : t -> string -> t
-
(** Set description *)
+
(** Update description *)
val with_description : t -> string -> t
-
(** Set source *)
+
(** Update source *)
val with_source : t -> string -> t
-
(** Set symbol *)
+
(** Update symbol *)
val with_symbol : t -> string -> t
-
(** Set type *)
+
(** Update type *)
val with_type : t -> string -> t
-
(** Set fix type *)
+
(** Update fix *)
val with_fix : t -> fix_type option -> t
-
(** Set satellite count *)
+
(** Update satellite count *)
val with_sat : t -> int -> t
-
(** Set horizontal dilution of precision *)
+
(** Update HDOP *)
val with_hdop : t -> float -> t
-
(** Set vertical dilution of precision *)
+
(** Update VDOP *)
val with_vdop : t -> float -> t
-
(** Set position dilution of precision *)
+
(** Update PDOP *)
val with_pdop : t -> float -> t
-
(** Set magnetic variation *)
+
(** Update magnetic variation *)
val with_magvar : t -> Coordinate.degrees -> t
-
(** Set geoid height *)
+
(** Update geoid height *)
val with_geoidheight : t -> float -> t
-
(** Set age of DGPS data *)
+
(** Update age of DGPS data *)
val with_ageofdgpsdata : t -> float -> t
-
(** Set DGPS ID *)
+
(** Update DGPS ID *)
val with_dgpsid : t -> int -> t
(** Add link *)
+11 -11
lib/gpx/writer.ml
···
let result =
try
(* Write XML declaration and GPX root element *)
-
let version = Gpx_doc.get_version gpx in
-
let creator = Gpx_doc.get_creator gpx in
+
let version = Doc.version gpx in
+
let creator = Doc.creator gpx in
let attrs = [
(("", "version"), version);
(("", "creator"), creator);
···
let* () = output_element_start writer "gpx" attrs in
(* Write metadata if present *)
-
let* () = match Gpx_doc.get_metadata gpx with
+
let* () = match Doc.metadata gpx with
| Some metadata ->
let* () = output_element_start writer "metadata" [] in
(* Write basic metadata fields *)
-
let* () = output_optional_text_element writer "name" (Metadata.get_name metadata) in
-
let* () = output_optional_text_element writer "desc" (Metadata.get_description metadata) in
-
let* () = output_optional_text_element writer "keywords" (Metadata.get_keywords metadata) in
+
let* () = output_optional_text_element writer "name" (Metadata.name metadata) in
+
let* () = output_optional_text_element writer "desc" (Metadata.description metadata) in
+
let* () = output_optional_text_element writer "keywords" (Metadata.keywords metadata) in
output_element_end writer
| None -> Ok ()
in
(* Write waypoints *)
-
let waypoints = Gpx_doc.get_waypoints gpx in
+
let waypoints = Doc.waypoints gpx in
let rec write_waypoints = function
| [] -> Ok ()
| wpt :: rest ->
-
let lat = Coordinate.latitude_to_float (Waypoint.get_lat wpt) in
-
let lon = Coordinate.longitude_to_float (Waypoint.get_lon wpt) in
+
let lat = Coordinate.latitude_to_float (Waypoint.lat wpt) in
+
let lon = Coordinate.longitude_to_float (Waypoint.lon wpt) in
let attrs = [
(("", "lat"), Printf.sprintf "%.6f" lat);
(("", "lon"), Printf.sprintf "%.6f" lon);
] in
let* () = output_element_start writer "wpt" attrs in
-
let* () = output_optional_text_element writer "name" (Waypoint.get_name wpt) in
-
let* () = output_optional_text_element writer "desc" (Waypoint.get_description wpt) in
+
let* () = output_optional_text_element writer "name" (Waypoint.name wpt) in
+
let* () = output_optional_text_element writer "desc" (Waypoint.description wpt) in
let* () = output_element_end writer in
write_waypoints rest
in
+2 -2
lib/gpx/writer.mli
···
(** GPX streaming writer using xmlm *)
(** Write a GPX document to an xmlm output destination *)
-
val write : ?validate:bool -> Xmlm.dest -> Gpx_doc.t -> (unit, Error.t) result
+
val write : ?validate:bool -> Xmlm.dest -> Doc.t -> (unit, Error.t) result
(** Write a GPX document to a string *)
-
val write_string : ?validate:bool -> Gpx_doc.t -> (string, Error.t) result
+
val write_string : ?validate:bool -> Doc.t -> (string, Error.t) result
+12 -13
lib/gpx_eio/gpx_eio.ml
···
match (Gpx.Coordinate.latitude lat, Gpx.Coordinate.longitude lon) with
| (Ok lat, Ok lon) ->
let wpt = Gpx.Waypoint.make lat lon in
-
Gpx.Waypoint.with_name wpt (Option.value name ~default:"") |>
-
fun wpt -> Gpx.Waypoint.with_description wpt (Option.value desc ~default:"")
+
{ wpt with name; desc }
| (Error e, _) | (_, Error e) -> failwith ("Invalid coordinate: " ^ e)
(** Create simple track from coordinate list *)
···
(** Count total points in GPX *)
let count_points gpx =
-
let waypoints = Gpx.Gpx_doc.get_waypoints gpx in
-
let routes = Gpx.Gpx_doc.get_routes gpx in
-
let tracks = Gpx.Gpx_doc.get_tracks gpx in
+
let waypoints = Gpx.Doc.waypoints gpx in
+
let routes = Gpx.Doc.routes gpx in
+
let tracks = Gpx.Doc.tracks gpx in
List.length waypoints +
-
List.fold_left (fun acc r -> acc + List.length (Gpx.Route.get_points r)) 0 routes +
+
List.fold_left (fun acc r -> acc + List.length (Gpx.Route.points r)) 0 routes +
List.fold_left (fun acc t -> acc + Gpx.Track.point_count t) 0 tracks
(** Get GPX statistics *)
···
has_time : bool;
}
-
let get_stats gpx =
-
let waypoints = Gpx.Gpx_doc.get_waypoints gpx in
-
let routes = Gpx.Gpx_doc.get_routes gpx in
-
let tracks = Gpx.Gpx_doc.get_tracks gpx in
+
let stats gpx =
+
let waypoints = Gpx.Doc.waypoints gpx in
+
let routes = Gpx.Doc.routes gpx in
+
let tracks = Gpx.Doc.tracks gpx in
{
waypoint_count = List.length waypoints;
route_count = List.length routes;
track_count = List.length tracks;
total_points = count_points gpx;
-
has_elevation = List.exists (fun w -> Gpx.Waypoint.get_elevation w <> None) waypoints;
-
has_time = List.exists (fun w -> Gpx.Waypoint.get_time w <> None) waypoints;
+
has_elevation = List.exists (fun w -> Gpx.Waypoint.elevation w <> None) waypoints;
+
has_time = List.exists (fun w -> Gpx.Waypoint.time w <> None) waypoints;
}
(** Pretty print GPX statistics *)
let print_stats gpx =
-
let stats = get_stats gpx in
+
let stats = stats gpx in
Printf.printf "GPX Statistics:\\n";
Printf.printf " Waypoints: %d\\n" stats.waypoint_count;
Printf.printf " Routes: %d\\n" stats.route_count;
+1 -1
lib/gpx_eio/gpx_eio.mli
···
(** Get GPX document statistics.
@param gpx GPX document
@return Statistics summary *)
-
val get_stats : Gpx.t -> gpx_stats
+
val stats : Gpx.t -> gpx_stats
(** Print GPX statistics to stdout.
@param gpx GPX document *)
+12 -13
lib/gpx_unix/gpx_unix.ml
···
match (Coordinate.latitude lat, Coordinate.longitude lon) with
| (Ok lat, Ok lon) ->
let wpt = Waypoint.make lat lon in
-
let wpt = match name with Some n -> Waypoint.with_name wpt n | None -> wpt in
-
let wpt = match desc with Some d -> Waypoint.with_description wpt d | None -> wpt in
+
let wpt = { wpt with name; desc } in
Ok wpt
| (Error e, _) | (_, Error e) -> Error (Gpx.Error.invalid_coordinate e)
···
(** Count total points in GPX *)
let count_points gpx =
-
let waypoints = Gpx_doc.get_waypoints gpx in
-
let routes = Gpx_doc.get_routes gpx in
-
let tracks = Gpx_doc.get_tracks gpx in
+
let waypoints = Doc.waypoints gpx in
+
let routes = Doc.routes gpx in
+
let tracks = Doc.tracks gpx in
List.length waypoints +
-
List.fold_left (fun acc r -> acc + List.length (Route.get_points r)) 0 routes +
+
List.fold_left (fun acc r -> acc + List.length (Route.points r)) 0 routes +
List.fold_left (fun acc t -> acc + Track.point_count t) 0 tracks
(** Get GPX statistics *)
···
has_time : bool;
}
-
let get_stats gpx =
-
let waypoints = Gpx_doc.get_waypoints gpx in
-
let routes = Gpx_doc.get_routes gpx in
-
let tracks = Gpx_doc.get_tracks gpx in
+
let stats gpx =
+
let waypoints = Doc.waypoints gpx in
+
let routes = Doc.routes gpx in
+
let tracks = Doc.tracks gpx in
{
waypoint_count = List.length waypoints;
route_count = List.length routes;
track_count = List.length tracks;
total_points = count_points gpx;
-
has_elevation = List.exists (fun w -> Waypoint.get_elevation w <> None) waypoints;
-
has_time = List.exists (fun w -> Waypoint.get_time w <> None) waypoints;
+
has_elevation = List.exists (fun w -> Waypoint.elevation w <> None) waypoints;
+
has_time = List.exists (fun w -> Waypoint.time w <> None) waypoints;
}
(** Pretty print GPX statistics *)
let print_stats gpx =
-
let stats = get_stats gpx in
+
let stats = stats gpx in
Printf.printf "GPX Statistics:\n";
Printf.printf " Waypoints: %d\n" stats.waypoint_count;
Printf.printf " Routes: %d\n" stats.route_count;
+1 -1
lib/gpx_unix/gpx_unix.mli
···
}
(** Get GPX statistics *)
-
val get_stats : t -> gpx_stats
+
val stats : t -> gpx_stats
(** Pretty print GPX statistics *)
val print_stats : t -> unit
+34 -34
test/test_corpus.ml
···
let content = read_test_file "simple_waypoints.gpx" in
match parse_string content with
| Ok gpx ->
-
let waypoints = Gpx_doc.get_waypoints gpx in
+
let waypoints = Doc.waypoints gpx in
Printf.printf "Waypoints count: %d\n" (List.length waypoints);
Printf.printf "First waypoint name: %s\n"
(match waypoints with
-
| wpt :: _ -> (match Waypoint.get_name wpt with Some n -> n | None -> "None")
+
| wpt :: _ -> (match Waypoint.name wpt with Some n -> n | None -> "None")
| [] -> "None");
-
Printf.printf "Creator: %s\n" (Gpx_doc.get_creator gpx);
+
Printf.printf "Creator: %s\n" (Doc.creator gpx);
[%expect {|
Waypoints count: 3
First waypoint name: San Francisco
···
let content = read_test_file "detailed_waypoints.gpx" in
match parse_string content with
| Ok gpx ->
-
let waypoints = Gpx_doc.get_waypoints gpx in
-
let metadata = Gpx_doc.get_metadata gpx in
+
let waypoints = Doc.waypoints gpx in
+
let metadata = Doc.metadata gpx in
Printf.printf "Waypoints count: %d\n" (List.length waypoints);
Printf.printf "Has metadata time: %b\n"
-
(match metadata with Some md -> Metadata.get_time md <> None | None -> false);
+
(match metadata with Some md -> Metadata.time md <> None | None -> false);
Printf.printf "Has bounds: %b\n"
-
(match metadata with Some md -> Metadata.get_bounds md <> None | None -> false);
+
(match metadata with Some md -> Metadata.bounds_opt md <> None | None -> false);
(match waypoints with
| wpt :: _ ->
-
Printf.printf "First waypoint has elevation: %b\n" (Waypoint.get_elevation wpt <> None);
-
Printf.printf "First waypoint has time: %b\n" (Waypoint.get_time wpt <> None);
-
Printf.printf "First waypoint has links: %b\n" (Waypoint.get_links wpt <> [])
+
Printf.printf "First waypoint has elevation: %b\n" (Waypoint.elevation wpt <> None);
+
Printf.printf "First waypoint has time: %b\n" (Waypoint.time wpt <> None);
+
Printf.printf "First waypoint has links: %b\n" (Waypoint.links wpt <> [])
| [] -> ());
[%expect {|
Waypoints count: 2
···
let content = read_test_file "simple_route.gpx" in
match parse_string content with
| Ok gpx ->
-
let routes = Gpx_doc.get_routes gpx in
+
let routes = Doc.routes gpx in
Printf.printf "Routes count: %d\n" (List.length routes);
(match routes with
| rte :: _ ->
Printf.printf "Route name: %s\n"
-
(match Route.get_name rte with Some n -> n | None -> "None");
+
(match Route.name rte with Some n -> n | None -> "None");
Printf.printf "Route points count: %d\n" (Route.point_count rte);
Printf.printf "Route has number: %b\n" false (* TODO: add get_number to Route *)
| [] -> ());
···
let content = read_test_file "simple_track.gpx" in
match parse_string content with
| Ok gpx ->
-
let tracks = Gpx_doc.get_tracks gpx in
+
let tracks = Doc.tracks gpx in
Printf.printf "Tracks count: %d\n" (List.length tracks);
(match tracks with
| trk :: _ ->
Printf.printf "Track name: %s\n"
-
(match Track.get_name trk with Some n -> n | None -> "None");
+
(match Track.name trk with Some n -> n | None -> "None");
Printf.printf "Track segments: %d\n" (Track.segment_count trk);
-
let segments = Track.get_segments trk in
+
let segments = Track.segments trk in
(match segments with
| seg :: _ ->
Printf.printf "First segment points: %d\n" (Track.Segment.point_count seg);
-
let points = Track.Segment.get_points seg in
+
let points = Track.Segment.points seg in
(match points with
| pt :: _ ->
-
Printf.printf "First point has elevation: %b\n" (Waypoint.get_elevation pt <> None);
-
Printf.printf "First point has time: %b\n" (Waypoint.get_time pt <> None)
+
Printf.printf "First point has elevation: %b\n" (Waypoint.elevation pt <> None);
+
Printf.printf "First point has time: %b\n" (Waypoint.time pt <> None)
| [] -> ())
| [] -> ())
| [] -> ());
···
let content = read_test_file "multi_segment_track.gpx" in
match parse_string content with
| Ok gpx ->
-
let tracks = Gpx_doc.get_tracks gpx in
+
let tracks = Doc.tracks gpx in
Printf.printf "Tracks count: %d\n" (List.length tracks);
(match tracks with
| trk :: _ ->
···
let content = read_test_file "comprehensive.gpx" in
match parse_string content with
| Ok gpx ->
-
let waypoints = Gpx_doc.get_waypoints gpx in
-
let routes = Gpx_doc.get_routes gpx in
-
let tracks = Gpx_doc.get_tracks gpx in
-
let metadata = Gpx_doc.get_metadata gpx in
+
let waypoints = Doc.waypoints gpx in
+
let routes = Doc.routes gpx in
+
let tracks = Doc.tracks gpx in
+
let metadata = Doc.metadata gpx in
Printf.printf "Waypoints: %d\n" (List.length waypoints);
Printf.printf "Routes: %d\n" (List.length routes);
Printf.printf "Tracks: %d\n" (List.length tracks);
Printf.printf "Has author: %b\n"
-
(match metadata with Some md -> Metadata.get_author md <> None | None -> false);
+
(match metadata with Some md -> Metadata.author md <> None | None -> false);
Printf.printf "Has copyright: %b\n"
-
(match metadata with Some md -> Metadata.get_copyright md <> None | None -> false);
+
(match metadata with Some md -> Metadata.copyright md <> None | None -> false);
Printf.printf "Has keywords: %b\n"
-
(match metadata with Some md -> Metadata.get_keywords md <> None | None -> false);
+
(match metadata with Some md -> Metadata.keywords md <> None | None -> false);
[%expect {|
Waypoints: 2
Routes: 1
···
match parse_string content with
| Ok gpx ->
Printf.printf "Minimal GPX parsed successfully\n";
-
let waypoints = Gpx_doc.get_waypoints gpx in
-
let routes = Gpx_doc.get_routes gpx in
-
let tracks = Gpx_doc.get_tracks gpx in
+
let waypoints = Doc.waypoints gpx in
+
let routes = Doc.routes gpx in
+
let tracks = Doc.tracks gpx in
Printf.printf "Waypoints: %d\n" (List.length waypoints);
Printf.printf "Routes: %d\n" (List.length routes);
Printf.printf "Tracks: %d\n" (List.length tracks);
···
match parse_string content with
| Ok gpx ->
Printf.printf "Edge cases parsed successfully\n";
-
let waypoints = Gpx_doc.get_waypoints gpx in
-
let tracks = Gpx_doc.get_tracks gpx in
+
let waypoints = Doc.waypoints gpx in
+
let tracks = Doc.tracks gpx in
Printf.printf "Waypoints: %d\n" (List.length waypoints);
Printf.printf "Tracks: %d\n" (List.length tracks);
(* Check coordinate ranges *)
···
(match parse_string xml_output with
| Ok gpx2 ->
Printf.printf "Round-trip successful\n";
-
let waypoints = Gpx_doc.get_waypoints gpx in
-
let waypoints2 = Gpx_doc.get_waypoints gpx2 in
+
let waypoints = Doc.waypoints gpx in
+
let waypoints2 = Doc.waypoints gpx2 in
Printf.printf "Original waypoints: %d\n" (List.length waypoints);
Printf.printf "Round-trip waypoints: %d\n" (List.length waypoints2);
-
Printf.printf "Creators match: %b\n" (Gpx_doc.get_creator gpx = Gpx_doc.get_creator gpx2);
+
Printf.printf "Creators match: %b\n" (Doc.creator gpx = Doc.creator gpx2);
[%expect {|
Round-trip successful
Original waypoints: 3
+15 -15
test/test_corpus_unix_eio.ml
···
(** Helper to compare GPX documents *)
let compare_gpx_basic gpx1 gpx2 =
let open Gpx in
-
Gpx_doc.get_creator gpx1 = Gpx_doc.get_creator gpx2 &&
-
List.length (Gpx_doc.get_waypoints gpx1) = List.length (Gpx_doc.get_waypoints gpx2) &&
-
List.length (Gpx_doc.get_routes gpx1) = List.length (Gpx_doc.get_routes gpx2) &&
-
List.length (Gpx_doc.get_tracks gpx1) = List.length (Gpx_doc.get_tracks gpx2)
+
Doc.creator gpx1 = Doc.creator gpx2 &&
+
List.length (Doc.waypoints gpx1) = List.length (Doc.waypoints gpx2) &&
+
List.length (Doc.routes gpx1) = List.length (Doc.routes gpx2) &&
+
List.length (Doc.tracks gpx1) = List.length (Doc.tracks gpx2)
(** Test Unix implementation can read all test files *)
let test_unix_parsing filename () =
···
let validation = Gpx.validate_gpx gpx in
check bool "GPX is valid" true validation.is_valid;
check bool "Has some content" true (
-
List.length (Gpx.Gpx_doc.get_waypoints gpx) > 0 ||
-
List.length (Gpx.Gpx_doc.get_routes gpx) > 0 ||
-
List.length (Gpx.Gpx_doc.get_tracks gpx) > 0
+
List.length (Gpx.Doc.waypoints gpx) > 0 ||
+
List.length (Gpx.Doc.routes gpx) > 0 ||
+
List.length (Gpx.Doc.tracks gpx) > 0
)
| Error err ->
failf "Unix parsing failed for %s: %s" filename (Gpx.Error.to_string err)
···
let validation = Gpx.validate_gpx gpx in
check bool "GPX is valid" true validation.is_valid;
check bool "Has some content" true (
-
List.length (Gpx.Gpx_doc.get_waypoints gpx) > 0 ||
-
List.length (Gpx.Gpx_doc.get_routes gpx) > 0 ||
-
List.length (Gpx.Gpx_doc.get_tracks gpx) > 0
+
List.length (Gpx.Doc.waypoints gpx) > 0 ||
+
List.length (Gpx.Doc.routes gpx) > 0 ||
+
List.length (Gpx.Doc.tracks gpx) > 0
)
with
| Gpx.Gpx_error err ->
···
| Ok gpx_unix, Ok gpx_eio ->
check bool "Unix and Eio produce equivalent results" true
(compare_gpx_basic gpx_unix gpx_eio);
-
check string "Creators match" (Gpx.Gpx_doc.get_creator gpx_unix) (Gpx.Gpx_doc.get_creator gpx_eio);
+
check string "Creators match" (Gpx.Doc.creator gpx_unix) (Gpx.Doc.creator gpx_eio);
check int "Waypoint counts match"
-
(List.length (Gpx.Gpx_doc.get_waypoints gpx_unix)) (List.length (Gpx.Gpx_doc.get_waypoints gpx_eio));
+
(List.length (Gpx.Doc.waypoints gpx_unix)) (List.length (Gpx.Doc.waypoints gpx_eio));
check int "Route counts match"
-
(List.length (Gpx.Gpx_doc.get_routes gpx_unix)) (List.length (Gpx.Gpx_doc.get_routes gpx_eio));
+
(List.length (Gpx.Doc.routes gpx_unix)) (List.length (Gpx.Doc.routes gpx_eio));
check int "Track counts match"
-
(List.length (Gpx.Gpx_doc.get_tracks gpx_unix)) (List.length (Gpx.Gpx_doc.get_tracks gpx_eio))
+
(List.length (Gpx.Doc.tracks gpx_unix)) (List.length (Gpx.Doc.tracks gpx_eio))
| Error _, Error _ ->
(* Both failed - that's consistent *)
check bool "Both Unix and Eio failed consistently" true true
···
check bool "Round-trip preserves basic structure" true
(compare_gpx_basic gpx_original gpx_roundtrip);
check string "Creator preserved"
-
(Gpx.Gpx_doc.get_creator gpx_original) (Gpx.Gpx_doc.get_creator gpx_roundtrip)
+
(Gpx.Doc.creator gpx_original) (Gpx.Doc.creator gpx_roundtrip)
| Error _ ->
failf "Round-trip parse failed for %s" filename)
| Error _ ->
+11 -12
test/test_gpx.ml
···
let test_gpx_creation () =
let creator = "test" in
-
let gpx = Gpx_doc.empty ~creator in
-
assert (Gpx_doc.get_creator gpx = creator);
-
assert (Gpx_doc.get_version gpx = "1.1");
-
assert (Gpx_doc.get_waypoints gpx = []);
+
let gpx = Doc.empty ~creator in
+
assert (Doc.creator gpx = creator);
+
assert (Doc.version gpx = "1.1");
+
assert (Doc.waypoints gpx = []);
Printf.printf "✓ GPX creation tests passed\n"
···
match parse_string gpx_xml with
| Ok gpx ->
-
assert (Gpx_doc.get_creator gpx = "test");
-
let waypoints = Gpx_doc.get_waypoints gpx in
+
assert (Doc.creator gpx = "test");
+
let waypoints = Doc.waypoints gpx in
assert (List.length waypoints = 1);
let wpt = List.hd waypoints in
-
assert (Waypoint.get_name wpt = Some "San Francisco");
+
assert (Waypoint.name wpt = Some "San Francisco");
Printf.printf "✓ Simple parsing tests passed\n"
| Error e ->
Printf.printf "✗ Parsing failed: %s\n" (Error.to_string e);
···
let lat = Result.get_ok (Coordinate.latitude 37.7749) in
let lon = Result.get_ok (Coordinate.longitude (-122.4194)) in
let wpt = Waypoint.make lat lon in
-
let wpt = Waypoint.with_name wpt "Test Point" in
-
let wpt = Waypoint.with_description wpt "A test waypoint" in
-
let gpx = Gpx_doc.empty ~creator:"test" in
-
let gpx = Gpx_doc.add_waypoint gpx wpt in
+
let wpt = { wpt with name = Some "Test Point"; desc = Some "A test waypoint" } in
+
let gpx = Doc.empty ~creator:"test" in
+
let gpx = Doc.add_waypoint gpx wpt in
match write_string gpx with
| Ok xml_string ->
···
assert false
let test_validation () =
-
let gpx = Gpx_doc.empty ~creator:"" in
+
let gpx = Doc.empty ~creator:"" in
let validation = validate_gpx gpx in
assert (not validation.is_valid);
let errors = List.filter (fun issue -> issue.level = `Error) validation.issues in