GPS Exchange Format library/CLI in OCaml
1(** Main GPX document type *)
2
3(** Main GPX document type *)
4type t = {
5 version : string; (* GPX version: "1.0" or "1.1" *)
6 creator : string; (* Creating application *)
7 metadata : Metadata.t option; (* Document metadata *)
8 waypoints : Waypoint.t list; (* Waypoints *)
9 routes : Route.t list; (* Routes *)
10 tracks : Track.t list; (* Tracks *)
11 extensions : Extension.t list; (* Document-level extensions *)
12}
13
14(** {2 Document Constructors} *)
15
16(** Create empty GPX document *)
17let empty ~creator = {
18 version = "1.1";
19 creator;
20 metadata = None;
21 waypoints = [];
22 routes = [];
23 tracks = [];
24 extensions = [];
25}
26
27(** Create GPX document with metadata *)
28let make ~creator ~metadata =
29 { (empty ~creator) with metadata = Some metadata }
30
31(** {2 Document Properties} *)
32
33(** Get version *)
34let version t = t.version
35
36(** Get creator *)
37let creator t = t.creator
38
39(** Get metadata *)
40let metadata t = t.metadata
41
42(** Get waypoints *)
43let waypoints t = t.waypoints
44
45(** Get routes *)
46let routes t = t.routes
47
48(** Get tracks *)
49let tracks t = t.tracks
50
51(** Get extensions *)
52let extensions t = t.extensions
53
54(** {2 Document Modification} *)
55
56(** Update metadata *)
57let with_metadata t metadata = { t with metadata = Some metadata }
58
59(** Add waypoint *)
60let add_waypoint t waypoint = { t with waypoints = t.waypoints @ [waypoint] }
61
62(** Add waypoints *)
63let add_waypoints t waypoints = { t with waypoints = t.waypoints @ waypoints }
64
65(** Add route *)
66let add_route t route = { t with routes = t.routes @ [route] }
67
68(** Add routes *)
69let add_routes t routes = { t with routes = t.routes @ routes }
70
71(** Add track *)
72let add_track t track = { t with tracks = t.tracks @ [track] }
73
74(** Add tracks *)
75let add_tracks t tracks = { t with tracks = t.tracks @ tracks }
76
77(** Add extensions *)
78let add_extensions t extensions = { t with extensions = t.extensions @ extensions }
79
80(** Clear waypoints *)
81let clear_waypoints t = { t with waypoints = [] }
82
83(** Clear routes *)
84let clear_routes t = { t with routes = [] }
85
86(** Clear tracks *)
87let clear_tracks t = { t with tracks = [] }
88
89(** {2 Document Analysis} *)
90
91(** Count waypoints *)
92let waypoint_count t = List.length t.waypoints
93
94(** Count routes *)
95let route_count t = List.length t.routes
96
97(** Count tracks *)
98let track_count t = List.length t.tracks
99
100(** Count total points *)
101let total_points t =
102 let waypoint_points = List.length t.waypoints in
103 let route_points = List.fold_left (fun acc route ->
104 acc + Route.point_count route) 0 t.routes in
105 let track_points = List.fold_left (fun acc track ->
106 acc + Track.point_count track) 0 t.tracks in
107 waypoint_points + route_points + track_points
108
109(** Check if document has elevation data *)
110let has_elevation t =
111 List.exists (fun wpt -> Waypoint.elevation wpt <> None) t.waypoints ||
112 List.exists (fun route ->
113 List.exists (fun pt -> Waypoint.elevation pt <> None) (Route.points route)
114 ) t.routes ||
115 List.exists (fun track ->
116 List.exists (fun pt -> Waypoint.elevation pt <> None) (Track.all_points track)
117 ) t.tracks
118
119(** Check if document has time data *)
120let has_time t =
121 List.exists (fun wpt -> Waypoint.time wpt <> None) t.waypoints ||
122 List.exists (fun route ->
123 List.exists (fun pt -> Waypoint.time pt <> None) (Route.points route)
124 ) t.routes ||
125 List.exists (fun track ->
126 List.exists (fun pt -> Waypoint.time pt <> None) (Track.all_points track)
127 ) t.tracks
128
129(** Check if document is empty *)
130let is_empty t =
131 waypoint_count t = 0 && route_count t = 0 && track_count t = 0
132
133(** Get statistics *)
134type stats = {
135 waypoint_count : int;
136 route_count : int;
137 track_count : int;
138 total_points : int;
139 has_elevation : bool;
140 has_time : bool;
141}
142
143let stats t = {
144 waypoint_count = waypoint_count t;
145 route_count = route_count t;
146 track_count = track_count t;
147 total_points = total_points t;
148 has_elevation = has_elevation t;
149 has_time = has_time t;
150}
151
152(** Pretty print statistics *)
153let pp_stats ppf t =
154 let s = stats t in
155 Format.fprintf ppf "@[<v>GPX Statistics:@, Waypoints: %d@, Routes: %d@, Tracks: %d@, Total points: %d@, Has elevation data: %s@, Has time data: %s@]"
156 s.waypoint_count s.route_count s.track_count s.total_points
157 (if s.has_elevation then "yes" else "no")
158 (if s.has_time then "yes" else "no")
159
160(** {2 Comparison and Utilities} *)
161
162(** Compare documents *)
163let compare t1 t2 =
164 let version_cmp = String.compare t1.version t2.version in
165 if version_cmp <> 0 then version_cmp
166 else
167 let creator_cmp = String.compare t1.creator t2.creator in
168 if creator_cmp <> 0 then creator_cmp
169 else
170 let waypoints_cmp = List.compare Waypoint.compare t1.waypoints t2.waypoints in
171 if waypoints_cmp <> 0 then waypoints_cmp
172 else
173 let routes_cmp = List.compare Route.compare t1.routes t2.routes in
174 if routes_cmp <> 0 then routes_cmp
175 else List.compare Track.compare t1.tracks t2.tracks
176
177(** Test document equality *)
178let equal t1 t2 = compare t1 t2 = 0
179
180(** Pretty print document *)
181let pp ppf t =
182 let stats = stats t in
183 Format.fprintf ppf "GPX v%s by %s (%d wpt, %d routes, %d tracks, %d total points)"
184 t.version t.creator
185 stats.waypoint_count stats.route_count stats.track_count stats.total_points
186