package mlgpx
Library and CLI for parsing and generating GPS Exchange (GPX) formats
Install
dune-project
Dependency
Authors
Maintainers
Sources
mlgpx-1.0.0.tbz
md5=5342bb7e601273245a9fe263e5a08770
sha512=cd73b16e988b3ed3cc427a6c6c6d6c9c745adb1eb7efaae3c34e8d006e9c03d9f9d2616cd4118564bd9873903969d3e4053b585e79dbd3e3e7d0f541e2faac83
doc/src/mlgpx.core/writer.ml.html
Source file writer.ml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
(** GPX XML writer with complete spec coverage *) (** Result binding operators *) let (let*) = Result.bind (** Helper to write XML elements *) let output_element_start writer name attrs = try Xmlm.output writer (`El_start ((("", name), attrs))); Ok () with exn -> Error (Error.xml_error (Printexc.to_string exn)) let output_element_end writer = try Xmlm.output writer `El_end; Ok () with exn -> Error (Error.xml_error (Printexc.to_string exn)) let output_data writer text = try Xmlm.output writer (`Data text); Ok () with exn -> Error (Error.xml_error (Printexc.to_string exn)) let output_text_element writer name text = let attrs = [] in let* () = output_element_start writer name attrs in let* () = output_data writer text in output_element_end writer let output_optional_text_element writer name = function | Some text -> output_text_element writer name text | None -> Ok () let output_optional_float_element writer name = function | Some value -> output_text_element writer name (Printf.sprintf "%.6f" value) | None -> Ok () let output_optional_degrees_element writer name = function | Some degrees -> output_text_element writer name (Printf.sprintf "%.6f" (Coordinate.degrees_to_float degrees)) | None -> Ok () let output_optional_int_element writer name = function | Some value -> output_text_element writer name (string_of_int value) | None -> Ok () let output_optional_time_element writer name = function | Some time -> output_text_element writer name (Ptime.to_rfc3339 time) | None -> Ok () let output_optional_fix_element writer name = function | Some fix_type -> output_text_element writer name (Waypoint.fix_type_to_string fix_type) | None -> Ok () (** Write link elements *) let output_link writer link = let href = Link.href link in let attrs = [(("", "href"), href)] in let* () = output_element_start writer "link" attrs in let* () = output_optional_text_element writer "text" (Link.text link) in let* () = output_optional_text_element writer "type" (Link.type_ link) in output_element_end writer let output_links writer links = let rec write_links = function | [] -> Ok () | link :: rest -> let* () = output_link writer link in write_links rest in write_links links (** Write person (author) element *) let output_person writer person = let* () = output_element_start writer "author" [] in let* () = output_optional_text_element writer "name" (Link.person_name person) in let* () = match Link.person_email person with | Some email -> (* Parse email into id and domain *) (match String.index_opt email '@' with | Some at_pos -> let id = String.sub email 0 at_pos in let domain = String.sub email (at_pos + 1) (String.length email - at_pos - 1) in let attrs = [(("", "id"), id); (("", "domain"), domain)] in let* () = output_element_start writer "email" attrs in output_element_end writer | None -> (* Invalid email format, skip *) Ok ()) | None -> Ok () in let* () = match Link.person_link person with | Some link -> output_link writer link | None -> Ok () in output_element_end writer (** Write copyright element *) let output_copyright writer copyright = let = Link.copyright_author copyright in let attrs = [(("", "author"), author)] in let* () = output_element_start writer "copyright" attrs in let* () = output_optional_int_element writer "year" (Link.copyright_year copyright) in let* () = output_optional_text_element writer "license" (Link.copyright_license copyright) in output_element_end writer (** Write bounds element *) let output_bounds writer bounds = let (minlat, minlon, maxlat, maxlon) = Metadata.Bounds.bounds bounds in let attrs = [ (("", "minlat"), Printf.sprintf "%.6f" (Coordinate.latitude_to_float minlat)); (("", "minlon"), Printf.sprintf "%.6f" (Coordinate.longitude_to_float minlon)); (("", "maxlat"), Printf.sprintf "%.6f" (Coordinate.latitude_to_float maxlat)); (("", "maxlon"), Printf.sprintf "%.6f" (Coordinate.longitude_to_float maxlon)); ] in let* () = output_element_start writer "bounds" attrs in output_element_end writer (** Write extensions element *) let output_extensions writer extensions = if extensions = [] then Ok () else let* () = output_element_start writer "extensions" [] in (* For now, skip writing extension content - would need full extension serialization *) output_element_end writer (** Write metadata element *) let output_metadata writer metadata = let* () = output_element_start writer "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* () = match Metadata.author metadata with | Some -> output_person writer author | None -> Ok () in let* () = match Metadata.copyright metadata with | Some copyright -> output_copyright writer copyright | None -> Ok () in let* () = output_links writer (Metadata.links metadata) in let* () = output_optional_time_element writer "time" (Metadata.time metadata) in let* () = output_optional_text_element writer "keywords" (Metadata.keywords metadata) in let* () = match Metadata.bounds_opt metadata with | Some bounds -> output_bounds writer bounds | None -> Ok () in let* () = output_extensions writer (Metadata.extensions metadata) in output_element_end writer (** Write waypoint elements (used for wpt, rtept, trkpt) *) let output_waypoint_data writer waypoint = let* () = output_optional_float_element writer "ele" (Waypoint.elevation waypoint) in let* () = output_optional_time_element writer "time" (Waypoint.time waypoint) in let* () = output_optional_degrees_element writer "magvar" (Waypoint.magvar waypoint) in let* () = output_optional_float_element writer "geoidheight" (Waypoint.geoidheight waypoint) in let* () = output_optional_text_element writer "name" (Waypoint.name waypoint) in let* () = output_optional_text_element writer "cmt" (Waypoint.comment waypoint) in let* () = output_optional_text_element writer "desc" (Waypoint.description waypoint) in let* () = output_optional_text_element writer "src" (Waypoint.source waypoint) in let* () = output_links writer (Waypoint.links waypoint) in let* () = output_optional_text_element writer "sym" (Waypoint.symbol waypoint) in let* () = output_optional_text_element writer "type" (Waypoint.type_ waypoint) in let* () = output_optional_fix_element writer "fix" (Waypoint.fix waypoint) in let* () = output_optional_int_element writer "sat" (Waypoint.sat waypoint) in let* () = output_optional_float_element writer "hdop" (Waypoint.hdop waypoint) in let* () = output_optional_float_element writer "vdop" (Waypoint.vdop waypoint) in let* () = output_optional_float_element writer "pdop" (Waypoint.pdop waypoint) in let* () = output_optional_float_element writer "ageofdgpsdata" (Waypoint.ageofdgpsdata waypoint) in let* () = output_optional_int_element writer "dgpsid" (Waypoint.dgpsid waypoint) in output_extensions writer (Waypoint.extensions waypoint) (** Write waypoints *) let output_waypoints writer waypoints = let rec write_waypoints = function | [] -> Ok () | wpt :: rest -> 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_waypoint_data writer wpt in let* () = output_element_end writer in write_waypoints rest in write_waypoints waypoints (** Write route points *) let output_route_points writer points element_name = let rec write_points = function | [] -> Ok () | pt :: rest -> let lat = Coordinate.latitude_to_float (Waypoint.lat pt) in let lon = Coordinate.longitude_to_float (Waypoint.lon pt) in let attrs = [ (("", "lat"), Printf.sprintf "%.6f" lat); (("", "lon"), Printf.sprintf "%.6f" lon); ] in let* () = output_element_start writer element_name attrs in let* () = output_waypoint_data writer pt in let* () = output_element_end writer in write_points rest in write_points points (** Write routes *) let output_routes writer routes = let rec write_routes = function | [] -> Ok () | route :: rest -> let* () = output_element_start writer "rte" [] in let* () = output_optional_text_element writer "name" (Route.name route) in let* () = output_optional_text_element writer "cmt" (Route.comment route) in let* () = output_optional_text_element writer "desc" (Route.description route) in let* () = output_optional_text_element writer "src" (Route.source route) in let* () = output_links writer (Route.links route) in let* () = output_optional_int_element writer "number" (Route.number route) in let* () = output_optional_text_element writer "type" (Route.type_ route) in let* () = output_extensions writer (Route.extensions route) in let* () = output_route_points writer (Route.points route) "rtept" in let* () = output_element_end writer in write_routes rest in write_routes routes (** Write track segments *) let output_track_segments writer segments = let rec write_segments = function | [] -> Ok () | seg :: rest -> let* () = output_element_start writer "trkseg" [] in let* () = output_route_points writer (Track.Segment.points seg) "trkpt" in let* () = output_extensions writer (Track.Segment.extensions seg) in let* () = output_element_end writer in write_segments rest in write_segments segments (** Write tracks *) let output_tracks writer tracks = let rec write_tracks = function | [] -> Ok () | track :: rest -> let* () = output_element_start writer "trk" [] in let* () = output_optional_text_element writer "name" (Track.name track) in let* () = output_optional_text_element writer "cmt" (Track.comment track) in let* () = output_optional_text_element writer "desc" (Track.description track) in let* () = output_optional_text_element writer "src" (Track.source track) in let* () = output_links writer (Track.links track) in let* () = output_optional_int_element writer "number" (Track.number track) in let* () = output_optional_text_element writer "type" (Track.type_ track) in let* () = output_extensions writer (Track.extensions track) in let* () = output_track_segments writer (Track.segments track) in let* () = output_element_end writer in write_tracks rest in write_tracks tracks (** Write a complete GPX document *) let write ?(validate=false) output gpx = let writer = Xmlm.make_output output in let result = try (* Write XML declaration and GPX root element *) let version = Doc.version gpx in let creator = Doc.creator gpx in let attrs = [ (("", "version"), version); (("", "creator"), creator); (("", "xsi:schemaLocation"), "http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"); (("xmlns", "xsi"), "http://www.w3.org/2001/XMLSchema-instance"); (("", "xmlns"), "http://www.topografix.com/GPX/1/1") ] in let* () = output_element_start writer "gpx" attrs in (* Write metadata if present *) let* () = match Doc.metadata gpx with | Some metadata -> output_metadata writer metadata | None -> Ok () in (* Write waypoints *) let* () = output_waypoints writer (Doc.waypoints gpx) in (* Write routes *) let* () = output_routes writer (Doc.routes gpx) in (* Write tracks *) let* () = output_tracks writer (Doc.tracks gpx) in (* Write root-level extensions *) let* () = output_extensions writer (Doc.extensions gpx) in output_element_end writer with | Xmlm.Error ((line, col), error) -> Error (Error.xml_error (Printf.sprintf "XML error at line %d, column %d: %s" line col (Xmlm.error_message error))) | exn -> Error (Error.xml_error (Printexc.to_string exn)) in match result, validate with | Ok (), true -> let validation = Validate.validate_gpx gpx in if validation.is_valid then Ok () else let error_msgs = List.filter (fun issue -> issue.Validate.level = `Error) validation.issues |> List.map (fun issue -> issue.Validate.message) |> String.concat "; " in Error (Error.validation_error error_msgs) | result, false -> result | Error _ as result, true -> result (* Pass through write errors even when validating *) (** Write GPX to string *) let write_string ?(validate=false) gpx = let buffer = Buffer.create 1024 in let dest = `Buffer buffer in let* () = write ~validate dest gpx in Ok (Buffer.contents buffer)
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>