# mlgpx - an OCaml GPS Exchange Format (GPX) Library An OCaml library for parsing and generating GPX (GPS Exchange Format) 1.0 and 1.1 files, and a CLI for common manipulation and query options. ## Command Line Usage The `mlgpx` CLI provides tools for manipulating GPX files from the command line. ### Installation ```bash # Install from source dune build @install dune install # Or use opam opam install mlgpx ``` ### Convert Waypoints to Track ```bash # Basic conversion mlgpx convert waypoints.gpx track.gpx # With custom track name mlgpx convert --name "My Route" waypoints.gpx route.gpx # Sort waypoints by timestamp before conversion mlgpx convert --sort-time waypoints.gpx sorted_track.gpx # Sort by name and preserve original waypoints mlgpx convert --sort-name --preserve waypoints.gpx mixed.gpx # Verbose output with description mlgpx convert --verbose --desc "Generated route" waypoints.gpx track.gpx ``` ### File Analysis ```bash # Basic file information mlgpx info file.gpx # Detailed analysis with waypoint details mlgpx info --verbose file.gpx ``` ### Help ```bash # General help mlgpx --help # Command-specific help mlgpx convert --help mlgpx info --help ``` ## Architecture Overview The library is split into four main components: ### Core Library (`gpx`) - **Portable**: No Unix dependencies, works with `js_of_ocaml` - **Streaming**: Uses xmlm for memory-efficient XML processing - **Type-safe**: Strong typing with validation for coordinates and GPS data - **Pure functional**: No side effects in the core parsing/writing logic ### Unix Layer (`gpx_unix`) - **File I/O**: Convenient functions for reading/writing GPX files - **Result-based**: Explicit error handling with `result` types - **Validation**: Built-in validation with detailed error reporting - **Utilities**: Helper functions for common GPX operations ### Effects-Style Layer (`gpx_eio`) - **Exception-based**: Simplified error handling with exceptions - **Effects-style API**: Similar to Eio patterns but using standard Unix I/O - **Resource-safe**: Automatic file handle management - **High-level**: Convenient functions for common operations ### Command Line Interface (`mlgpx`) - **Unix-style CLI**: Built with cmdliner for proper argument parsing - **Eio-powered**: Uses Eio backend for efficient I/O operations - **Waypoint conversion**: Convert waypoints to tracksets with sorting options - **File analysis**: Inspect GPX files with detailed information display ## Key Features - ✅ **Complete GPX 1.0/1.1 support**: Waypoints, routes, tracks, metadata, extensions - ✅ **Streaming parser/writer**: Memory-efficient for large files - ✅ **Strong type safety**: Validated coordinates, GPS fix types, etc. - ✅ **Comprehensive validation**: Detailed error and warning reporting - ✅ **Extension support**: Handle custom XML elements - ✅ **Cross-platform**: Core library has no Unix dependencies ## Module Structure ``` mlgpx/ ├── lib/ │ ├── gpx/ # Portable core library │ │ ├── types.ml # Type definitions with smart constructors │ │ ├── parser.ml # Streaming XML parser │ │ ├── writer.ml # Streaming XML writer │ │ ├── validate.ml # Validation and error checking │ │ └── gpx.ml[i] # Main interface with direct access to all types │ ├── gpx_unix/ # Unix I/O layer (result-based) │ │ ├── gpx_io.ml # File operations with error handling │ │ └── gpx_unix.ml # High-level convenience API │ └── gpx_eio/ # Effects-style layer (exception-based) │ ├── gpx_io.ml # File operations with exceptions │ └── gpx_eio.ml # High-level effects-style API ├── examples/ # Usage examples └── test/ # Test suite ``` ## Type System Design ### Validated Coordinates ```ocaml type latitude = private float (* -90.0 to 90.0 *) type longitude = private float (* -180.0 to < 180.0 *) type degrees = private float (* 0.0 to < 360.0 *) (* Smart constructors with validation *) val latitude : float -> (latitude, string) result val longitude : float -> (longitude, string) result ``` ### GPX Elements - **Waypoint**: Standalone geographic point with metadata - **Route**: Ordered list of waypoints representing a planned path - **Track**: Recorded path consisting of track segments with track points - **Metadata**: Document-level information (bounds, author, etc.) ### Extension System ```ocaml type extension = { namespace : string option; name : string; attributes : (string * string) list; content : extension_content; } ``` ## API Design ### Streaming Operations ```ocaml (* Core streaming API *) val Gpx_parser.parse : Xmlm.input -> gpx result val Gpx_writer.write : Xmlm.output -> gpx -> unit result (* String convenience functions *) val Gpx_parser.parse_string : string -> gpx result val Gpx_writer.write_string : gpx -> string result ``` ### File Operations (Result-based) ```ocaml (* Simple file I/O *) val Gpx_unix.read : string -> gpx result val Gpx_unix.write : string -> gpx -> unit result (* With validation *) val Gpx_unix.read_validated : string -> gpx result val Gpx_unix.write_validated : string -> gpx -> unit result (* With backup *) val Gpx_unix.write_with_backup : string -> gpx -> string result ``` ### Effects-Style Operations (Exception-based) ```ocaml (* Simple file I/O *) val Gpx_eio.read : unit -> string -> gpx val Gpx_eio.write : unit -> string -> gpx -> unit (* With validation *) val Gpx_eio.read_validated : unit -> string -> gpx val Gpx_eio.write_validated : unit -> string -> gpx -> unit (* With backup *) val Gpx_eio.write_with_backup : unit -> string -> gpx -> string (* Utility functions *) val Gpx_eio.make_waypoint : unit -> lat:float -> lon:float -> ?name:string -> unit -> waypoint_data val Gpx_eio.make_track_from_coords : unit -> name:string -> (float * float) list -> track ``` ### Validation ```ocaml type validation_result = { issues : validation_issue list; is_valid : bool; } val Gpx_validate.validate_gpx : gpx -> validation_result val Gpx_validate.is_valid : gpx -> bool ``` ## Error Handling Strategy The library uses a comprehensive error type: ```ocaml type error = | Invalid_xml of string | Invalid_coordinate of string | Missing_required_attribute of string * string | Missing_required_element of string | Validation_error of string | Xml_error of string | IO_error of string ``` All operations return `('a, error) result` for explicit error handling. ## Performance Characteristics - **Memory usage**: O(1) for streaming operations, O(n) for complete document - **Time complexity**: O(n) parsing/writing where n = file size - **Validation**: Optional, can be disabled for performance-critical applications - **Extensions**: Parsed lazily, minimal overhead when unused ## Usage Examples ### Result-based API (Explicit Error Handling) ```ocaml open Gpx_unix let create_simple_gpx () = (* Create waypoints *) let* waypoint = make_waypoint ~lat:37.7749 ~lon:(-122.4194) ~name:"San Francisco" () in (* Create track from coordinates *) let coords = [(37.7749, -122.4194); (37.7849, -122.4094)] in let* track = make_track_from_coords ~name:"Sample Track" coords in (* Create GPX document *) let gpx = Types.make_gpx ~creator:"mlgpx example" in let gpx = { gpx with waypoints = [waypoint]; tracks = [track] } in (* Validate and write *) write_validated "output.gpx" gpx let () = match create_simple_gpx () with | Ok () -> Printf.printf "GPX created successfully\n" | Error e -> Printf.eprintf "Error: %s\n" (error_to_string e) ``` ### Effects-Style API (Exception-based) ```ocaml open Gpx_eio let create_simple_gpx () = try (* Create waypoints *) let waypoint = make_waypoint () ~lat:37.7749 ~lon:(-122.4194) ~name:"San Francisco" () in (* Create track from coordinates *) let coords = [(37.7749, -122.4194); (37.7849, -122.4094)] in let track = make_track_from_coords () ~name:"Sample Track" coords in (* Create GPX document *) let gpx = Gpx.make_gpx ~creator:"mlgpx example" in let gpx = { gpx with waypoints = [waypoint]; tracks = [track] } in (* Validate and write *) write_validated () "output.gpx" gpx; Printf.printf "GPX created successfully\n" with | Gpx.Gpx_error err -> Printf.eprintf "GPX Error: %s\n" (Gpx.error_to_string err) let () = create_simple_gpx () ```