GPS Exchange Format library/CLI in OCaml
1# mlgpx - OCaml GPX Library 2 3An OCaml library for parsing and generating GPX (GPS Exchange Format) 1.0 and 41.1 files, and a CLI for common manipulation and query options. 5 6## Architecture Overview 7 8The library is split into four main components: 9 10### Core Library (`gpx`) 11- **Portable**: No Unix dependencies, works with js_of_ocaml 12- **Streaming**: Uses xmlm for memory-efficient XML processing 13- **Type-safe**: Strong typing with validation for coordinates and GPS data 14- **Pure functional**: No side effects in the core parsing/writing logic 15 16### Unix Layer (`gpx_unix`) 17- **File I/O**: Convenient functions for reading/writing GPX files 18- **Result-based**: Explicit error handling with `result` types 19- **Validation**: Built-in validation with detailed error reporting 20- **Utilities**: Helper functions for common GPX operations 21 22### Effects-Style Layer (`gpx_eio`) 23- **Exception-based**: Simplified error handling with exceptions 24- **Effects-style API**: Similar to Eio patterns but using standard Unix I/O 25- **Resource-safe**: Automatic file handle management 26- **High-level**: Convenient functions for common operations 27 28### Command Line Interface (`mlgpx`) 29- **Unix-style CLI**: Built with cmdliner for proper argument parsing 30- **Eio-powered**: Uses Eio backend for efficient I/O operations 31- **Waypoint conversion**: Convert waypoints to tracksets with sorting options 32- **File analysis**: Inspect GPX files with detailed information display 33 34## Key Features 35 36-**Complete GPX 1.0/1.1 support**: Waypoints, routes, tracks, metadata, extensions 37-**Streaming parser/writer**: Memory-efficient for large files 38-**Strong type safety**: Validated coordinates, GPS fix types, etc. 39-**Comprehensive validation**: Detailed error and warning reporting 40-**Extension support**: Handle custom XML elements 41-**Cross-platform**: Core library has no Unix dependencies 42 43## Module Structure 44 45``` 46mlgpx/ 47├── lib/ 48│ ├── gpx/ # Portable core library 49│ │ ├── types.ml # Type definitions with smart constructors 50│ │ ├── parser.ml # Streaming XML parser 51│ │ ├── writer.ml # Streaming XML writer 52│ │ ├── validate.ml # Validation and error checking 53│ │ └── gpx.ml[i] # Main interface with direct access to all types 54│ ├── gpx_unix/ # Unix I/O layer (result-based) 55│ │ ├── gpx_io.ml # File operations with error handling 56│ │ └── gpx_unix.ml # High-level convenience API 57│ └── gpx_eio/ # Effects-style layer (exception-based) 58│ ├── gpx_io.ml # File operations with exceptions 59│ └── gpx_eio.ml # High-level effects-style API 60├── examples/ # Usage examples 61└── test/ # Test suite 62``` 63 64## Type System Design 65 66### Validated Coordinates 67```ocaml 68type latitude = private float (* -90.0 to 90.0 *) 69type longitude = private float (* -180.0 to < 180.0 *) 70type degrees = private float (* 0.0 to < 360.0 *) 71 72(* Smart constructors with validation *) 73val latitude : float -> (latitude, string) result 74val longitude : float -> (longitude, string) result 75``` 76 77### GPX Elements 78- **Waypoint**: Standalone geographic point with metadata 79- **Route**: Ordered list of waypoints representing a planned path 80- **Track**: Recorded path consisting of track segments with track points 81- **Metadata**: Document-level information (bounds, author, etc.) 82 83### Extension System 84```ocaml 85type extension = { 86 namespace : string option; 87 name : string; 88 attributes : (string * string) list; 89 content : extension_content; 90} 91``` 92 93## API Design 94 95### Streaming Operations 96```ocaml 97(* Core streaming API *) 98val Gpx_parser.parse : Xmlm.input -> gpx result 99val Gpx_writer.write : Xmlm.output -> gpx -> unit result 100 101(* String convenience functions *) 102val Gpx_parser.parse_string : string -> gpx result 103val Gpx_writer.write_string : gpx -> string result 104``` 105 106### File Operations (Result-based) 107```ocaml 108(* Simple file I/O *) 109val Gpx_unix.read : string -> gpx result 110val Gpx_unix.write : string -> gpx -> unit result 111 112(* With validation *) 113val Gpx_unix.read_validated : string -> gpx result 114val Gpx_unix.write_validated : string -> gpx -> unit result 115 116(* With backup *) 117val Gpx_unix.write_with_backup : string -> gpx -> string result 118``` 119 120### Effects-Style Operations (Exception-based) 121```ocaml 122(* Simple file I/O *) 123val Gpx_eio.read : unit -> string -> gpx 124val Gpx_eio.write : unit -> string -> gpx -> unit 125 126(* With validation *) 127val Gpx_eio.read_validated : unit -> string -> gpx 128val Gpx_eio.write_validated : unit -> string -> gpx -> unit 129 130(* With backup *) 131val Gpx_eio.write_with_backup : unit -> string -> gpx -> string 132 133(* Utility functions *) 134val Gpx_eio.make_waypoint : unit -> lat:float -> lon:float -> ?name:string -> unit -> waypoint_data 135val Gpx_eio.make_track_from_coords : unit -> name:string -> (float * float) list -> track 136``` 137 138### Validation 139```ocaml 140type validation_result = { 141 issues : validation_issue list; 142 is_valid : bool; 143} 144 145val Gpx_validate.validate_gpx : gpx -> validation_result 146val Gpx_validate.is_valid : gpx -> bool 147``` 148 149## Error Handling Strategy 150 151The library uses a comprehensive error type: 152 153```ocaml 154type error = 155 | Invalid_xml of string 156 | Invalid_coordinate of string 157 | Missing_required_attribute of string * string 158 | Missing_required_element of string 159 | Validation_error of string 160 | Xml_error of string 161 | IO_error of string 162``` 163 164All operations return `('a, error) result` for explicit error handling. 165 166## Performance Characteristics 167 168- **Memory usage**: O(1) for streaming operations, O(n) for complete document 169- **Time complexity**: O(n) parsing/writing where n = file size 170- **Validation**: Optional, can be disabled for performance-critical applications 171- **Extensions**: Parsed lazily, minimal overhead when unused 172 173## Usage Examples 174 175### Result-based API (Explicit Error Handling) 176 177```ocaml 178open Gpx_unix 179 180let create_simple_gpx () = 181 (* Create waypoints *) 182 let* waypoint = make_waypoint ~lat:37.7749 ~lon:(-122.4194) 183 ~name:"San Francisco" () in 184 185 (* Create track from coordinates *) 186 let coords = [(37.7749, -122.4194); (37.7849, -122.4094)] in 187 let* track = make_track_from_coords ~name:"Sample Track" coords in 188 189 (* Create GPX document *) 190 let gpx = Types.make_gpx ~creator:"mlgpx example" in 191 let gpx = { gpx with waypoints = [waypoint]; tracks = [track] } in 192 193 (* Validate and write *) 194 write_validated "output.gpx" gpx 195 196let () = 197 match create_simple_gpx () with 198 | Ok () -> Printf.printf "GPX created successfully\n" 199 | Error e -> Printf.eprintf "Error: %s\n" (error_to_string e) 200``` 201 202### Effects-Style API (Exception-based) 203 204```ocaml 205open Gpx_eio 206 207let create_simple_gpx () = 208 try 209 (* Create waypoints *) 210 let waypoint = make_waypoint () ~lat:37.7749 ~lon:(-122.4194) 211 ~name:"San Francisco" () in 212 213 (* Create track from coordinates *) 214 let coords = [(37.7749, -122.4194); (37.7849, -122.4094)] in 215 let track = make_track_from_coords () ~name:"Sample Track" coords in 216 217 (* Create GPX document *) 218 let gpx = Gpx.make_gpx ~creator:"mlgpx example" in 219 let gpx = { gpx with waypoints = [waypoint]; tracks = [track] } in 220 221 (* Validate and write *) 222 write_validated () "output.gpx" gpx; 223 Printf.printf "GPX created successfully\n" 224 225 with 226 | Gpx.Gpx_error err -> 227 Printf.eprintf "GPX Error: %s\n" (Gpx.error_to_string err) 228 229let () = create_simple_gpx () 230``` 231 232## Command Line Usage 233 234The `mlgpx` CLI provides tools for manipulating GPX files from the command line. 235 236### Installation 237 238```bash 239# Install from source 240dune build @install 241dune install 242 243# Or use opam (when published) 244opam install mlgpx 245``` 246 247### Convert Waypoints to Track 248 249```bash 250# Basic conversion 251mlgpx convert waypoints.gpx track.gpx 252 253# With custom track name 254mlgpx convert --name "My Route" waypoints.gpx route.gpx 255 256# Sort waypoints by timestamp before conversion 257mlgpx convert --sort-time waypoints.gpx sorted_track.gpx 258 259# Sort by name and preserve original waypoints 260mlgpx convert --sort-name --preserve waypoints.gpx mixed.gpx 261 262# Verbose output with description 263mlgpx convert --verbose --desc "Generated route" waypoints.gpx track.gpx 264``` 265 266### File Analysis 267 268```bash 269# Basic file information 270mlgpx info file.gpx 271 272# Detailed analysis with waypoint details 273mlgpx info --verbose file.gpx 274``` 275 276### Help 277 278```bash 279# General help 280mlgpx --help 281 282# Command-specific help 283mlgpx convert --help 284mlgpx info --help 285```