GPS Exchange Format library/CLI in OCaml
mlgpx - OCaml GPX Library#
A high-quality OCaml library for parsing and generating GPX (GPS Exchange Format) files, designed with streaming performance and type safety in mind.
Architecture Overview#
The library is split into two 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
- Validation: Built-in validation with detailed error reporting
- Utilities: Helper functions for common GPX operations
Key Features#
- ✅ Complete GPX 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
│ │ ├── gpx_types.ml # Type definitions with smart constructors
│ │ ├── gpx_parser.ml # Streaming XML parser
│ │ ├── gpx_writer.ml # Streaming XML writer
│ │ └── gpx_validate.ml # Validation and error checking
│ └── gpx_unix/ # Unix I/O layer
│ ├── gpx_io.ml # File operations with error handling
│ └── gpx_unix.ml # High-level convenience API
├── examples/ # Usage examples
└── test/ # Test suite
Type System Design#
Validated Coordinates#
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#
type extension = {
namespace : string option;
name : string;
attributes : (string * string) list;
content : extension_content;
}
API Design#
Streaming Operations#
(* 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#
(* 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
Validation#
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:
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 Example#
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)
Dependencies#
- xmlm: Streaming XML parser/writer (core dependency)
- ptime: Time handling for timestamps
- unix: File I/O operations (Unix layer only)
Testing Strategy#
- Unit tests for coordinate validation
- Roundtrip tests (parse → write → parse)
- Validation rule testing
- Large file streaming tests
- Cross-platform compatibility tests
Future Considerations#
Potential Optimizations#
- Custom coordinate type with packed representation
- Lazy extension parsing
- Memory-mapped file reading for very large files
- Streaming validation (validate while parsing)
API Extensions#
- GPX merging/splitting utilities
- Coordinate transformation functions
- Distance/bearing calculations
- GPX statistics and analysis tools
This architecture provides a solid foundation for GPX processing in OCaml with excellent type safety, performance, and extensibility.