Current section
Files
Jump to
Current section
Files
src/gpxb.gleam
import gleam/float
import gleam/int
import gleam/list
import gleam/option.{type Option, None, Some}
import gleam/string
import gleam/string_builder.{type StringBuilder}
import xmb.{type Xml, text, x}
pub type Gpx {
Gpx(
metadata: Option(Metadata),
waypoints: List(Waypoint),
routes: List(Route),
tracks: List(Track),
)
}
pub type Coordinate {
Coordinate(
/// The latitude of the point. Decimal degrees, WGS84 datum.
///
/// -90.0 <= value <= 90.0
latitude: Float,
/// The longitude of the point. Decimal degrees, WGS84 datum.
///
/// -180.0 <= value < 180.0
longitude: Float,
)
}
/// A waypoint, point of interest, or named feature on a map.
pub type Waypoint {
Waypoint(
coordinate: Coordinate,
name: Option(String),
description: Option(String),
/// Creation/modification timestamp for element.
time: Option(Time),
)
}
/// An ordered series of waypoints representing a suggested series of turn points
/// leading to a destination.
pub type Route {
Route(
name: Option(String),
description: Option(String),
/// Source of data. Included to give user some idea of reliability and
/// accuracy of data.
source: Option(String),
/// Links to external information about the route.
links: List(Link),
/// The points of the route
waypoints: List(Waypoint),
)
}
/// An ordered series of points describing a path that has been taken.
pub type Track {
Track(
name: Option(String),
description: Option(String),
/// Source of data. Included to give user some idea of reliability and
/// accuracy of data.
source: Option(String),
/// Links to external information about the route.
links: List(Link),
segments: List(TrackSegment),
)
}
/// A Track Segment holds a list of Track Points which are logically connected
/// in order. To represent a single GPS track where GPS reception was lost, or
/// the GPS receiver was turned off, start a new `TrackSegment` for each
/// continuous span of track data.
pub type TrackSegment {
TrackSegment(waypoints: List(Waypoint))
}
/// Date and time in are in Univeral Coordinated Time (UTC), not local time!
///
/// Conforms to ISO 8601 specification for date/time representation. Fractional
/// seconds are allowed for millisecond timing in tracklogs.
///
pub type Time {
Time(
year: Int,
month: Int,
day: Int,
hour: Int,
minute: Int,
second: Int,
millisecond: Int,
)
}
/// Information about the GPX file, author, and copyright restrictions goes in
/// the metadata section. Providing rich, meaningful information about your GPX
/// files allows others to search for and use your GPS data.
pub type Metadata {
Metadata(
name: Option(String),
description: Option(String),
author: Option(Person),
)
}
/// A person or organization.
pub type Person {
Person(name: Option(String), email: Option(String), link: Option(Link))
}
/// A link to an external resource (Web page, digital photo, video clip, etc)
/// with additional information.
pub type Link {
Link(href: String, text: Option(String), type_: Option(String))
}
pub fn render(gpx: Gpx) -> StringBuilder {
let children =
[
list.map(gpx.waypoints, waypoint("wpt", _)),
list.map(gpx.routes, route),
list.map(gpx.tracks, track),
]
|> list.concat
|> map_push(gpx.metadata, fn(metadata) {
[]
|> map_push(metadata.author, person("author", _))
|> map_push(metadata.description, text_elem("desc", _))
|> map_push(metadata.name, text_elem("name", _))
|> x("metadata", [], _)
})
let gpx =
x(
"gpx",
[
#("version", "1.1"),
#("creator", "gleam-gpxb"),
#("xmlns", "http://www.topografix.com/GPX/1/1"),
],
children,
)
xmb.render([gpx])
}
fn text_elem(tag: String, child: String) -> Xml {
x(tag, [], [text(child)])
}
fn map_push(items: List(b), item: Option(a), transform: fn(a) -> b) -> List(b) {
case item {
None -> items
Some(item) -> [transform(item), ..items]
}
}
fn person(tag: String, author: Person) -> Xml {
x(
tag,
[],
[]
|> map_push(author.link, link)
|> map_push(author.email, text_elem("email", _))
|> map_push(author.name, text_elem("name", _)),
)
}
fn link(link: Link) -> Xml {
x(
"link",
[#("href", link.href)],
[]
|> map_push(link.type_, text_elem("type", _))
|> map_push(link.text, text_elem("text", _)),
)
}
fn waypoint(tag: String, waypoint: Waypoint) -> Xml {
x(
tag,
[
#("latitude", float.to_string(waypoint.coordinate.latitude)),
#("longitude", float.to_string(waypoint.coordinate.longitude)),
],
[]
|> map_push(waypoint.time, time)
|> map_push(waypoint.description, text_elem("desc", _))
|> map_push(waypoint.name, text_elem("name", _)),
)
}
fn time(time: Time) -> Xml {
let i = fn(i: Int) { i |> int.to_string |> string.pad_left(2, "0") }
let timestamp =
i(time.year)
<> "-"
<> i(time.month)
<> "-"
<> i(time.day)
<> "T"
<> i(time.hour)
<> ":"
<> i(time.minute)
<> ":"
<> i(time.second)
<> "."
<> string.pad_left(int.to_string(time.millisecond % 1000), 4, "0")
<> "Z"
x("time", [], [text(timestamp)])
}
fn route(route: Route) -> Xml {
x(
"rte",
[],
[
list.map(route.links, link),
list.map(route.waypoints, waypoint("rtept", _)),
]
|> list.concat
|> map_push(route.source, text_elem("src", _))
|> map_push(route.description, text_elem("desc", _))
|> map_push(route.name, text_elem("name", _)),
)
}
fn track(track: Track) -> Xml {
x(
"trk",
[],
[list.map(track.links, link), list.map(track.segments, track_segment)]
|> list.concat
|> map_push(track.source, text_elem("src", _))
|> map_push(track.description, text_elem("desc", _))
|> map_push(track.name, text_elem("name", _)),
)
}
fn track_segment(segment: TrackSegment) -> Xml {
x("trkseg", [], list.map(segment.waypoints, waypoint("trkpt", _)))
}