///|
/// moongpx 可运行示例。
///
/// 跑法:moon run cmd/main
///
/// 演示三件事:
/// 1. 坐标解析与范围校验(含那个不对称的经纬度边界)
/// 2. 严格 xsd:decimal —— 指数记法必须被拒
/// 3. 序列化时的指数陷阱 —— 输出必须永远是合法 GPX
/// 4. 解析真实文档、诊断行列号
/// 5. 序列化 + 往返一致性(模型 → 文本 → 模型,必须完全相等)
///|
fn show_lat(text : String) -> Unit {
match @moongpx.Latitude::from_string(text) {
Ok(lat) =>
println(
" lat \"\{text}\" → 合法,回写为 \"\{lat.to_gpx_string()}\"",
)
Err(e) => println(" lat \"\{text}\" → 拒绝:\{e.message()}")
}
}
///|
fn show_lon(text : String) -> Unit {
match @moongpx.Longitude::from_string(text) {
Ok(lon) =>
println(
" lon \"\{text}\" → 合法,回写为 \"\{lon.to_gpx_string()}\"",
)
Err(e) => println(" lon \"\{text}\" → 拒绝:\{e.message()}")
}
}
///|
fn main {
println("=== 1. 纬度是闭区间 [-90, 90] ===")
show_lat("31.2304")
show_lat("90")
show_lat("-90")
show_lat("90.0001")
println("")
println("=== 2. 经度是半开区间 [-180, 180) —— 两端不对称 ===")
show_lon("121.4737")
show_lon("-180")
show_lon("179.9999")
show_lon("180")
println(
" ↑ 注意:纬度 90 合法,但经度 180 非法。这是 GPX 最容易被做错的地方。",
)
println("")
println("=== 3. xsd:decimal 不接受指数记法 ===")
for text in ["1e2", "1e-7", "NaN", "INF", "31.2304"] {
match @moongpx.parse_decimal(text) {
Ok(v) => println(" \"\{text}\" → \{v}")
Err(e) => println(" \"\{text}\" → 拒绝:\{e.message()}")
}
}
println("")
println(
"=== 4. 序列化陷阱:Double 会输出指数,但 GPX 不允许 ===",
)
for v in [1.0e-7, 1.0e21, 2.5e-3, 31.2304] {
println(
" \{v} → 原始 \{v.to_string()} → 合规 \{@moongpx.format_decimal(v)}",
)
}
println(
" ↑ 若直接用 Double::to_string,lat=\"0.0000001\" 会被回写成 \"1e-7\",产出非法文档。",
)
println("")
println("=== 5. 解析一个真实 GPX 文档 ===")
let doc = "" +
"晨跑" +
"" +
"早跑" +
"4" +
"6" +
"9" +
""
match @moongpx.parse_gpx(doc) {
Ok(gpx) => {
println(" 解析成功")
println(" version = \{gpx.version} creator = \{gpx.creator}")
match gpx.metadata {
Some(m) =>
match m.name {
Some(n) => println(" metadata.name = \{n}")
None => println(" metadata.name = 无")
}
None => println(" metadata = 无")
}
println(" 轨迹数 = \{gpx.tracks.length()}")
if gpx.tracks.length() > 0 {
let segs = gpx.tracks[0].segments
println(
" 轨迹段数 = \{segs.length()} 点数 = \{segs[0].points.length()}",
)
let mut total = 0.0
for i = 1; i < segs[0].points.length(); i = i + 1 {
let a = segs[0].points[i - 1]
let b = segs[0].points[i]
total = total + distance_m(a, b)
}
println(" 轨迹总长 = \{total} m")
}
}
Err(diags) => println(@moongpx.format_diagnostics(diags))
}
println("")
println("=== 6. 序列化:模型 → 合规 GPX 文本 ===")
let doc2 = "" +
"晨跑 & 夜跑" +
"" +
"0.0000001a<b" +
"早跑" +
"4" +
"" +
"私有数据" +
""
match @moongpx.parse_gpx(doc2) {
Ok(g) => {
let out = @moongpx.to_gpx(g)
println(out)
println(
" ↑ 注意三处:① 输出 0.0000001 而不是 1e-7;" +
"② 文本里的 & 被转义成 &;③ extensions 原样保留未被转义。",
)
println("")
match @moongpx.parse_gpx(out) {
Ok(g2) =>
println(
" 往返一致性 parse(to_gpx(g)) == g :\{if g == g2 { "成立" } else { "失败" }}",
)
Err(d) =>
println(" 往返后解析失败:\{@moongpx.format_diagnostics(d)}")
}
}
Err(d) => println(@moongpx.format_diagnostics(d))
}
println("")
println("=== 7. 一份有问题的文档 —— 诊断带行列号 ===")
let bad = "\n" +
" \n" +
" \n" +
" \n" +
""
match @moongpx.parse_gpx(bad) {
Ok(_) => println(" 不该成功")
Err(diags) => {
println(" 共 \{diags.length()} 条诊断:")
for d in diags {
println(" \{d.to_string()}")
}
}
}
println("")
println(
"=== 8. 独立校验:warning 不阻塞解析,但会被记下来 ===",
)
let suspicious = "" +
"99999" +
"空的"
match @moongpx.parse_gpx(suspicious) {
Ok(_) => println(" parse_gpx → Ok(文档合法,可以正常用)")
Err(d) =>
println(" parse_gpx → Err: \{@moongpx.format_diagnostics(d)}")
}
println(" is_valid_gpx = \{@moongpx.is_valid_gpx(suspicious)}")
println(" has_schema_errors = \{@moongpx.has_schema_errors(suspicious)}")
println(
" validate_gpx = \{@moongpx.validate_gpx(suspicious).length()} 条:",
)
for d in @moongpx.validate_gpx(suspicious) {
println(" \{d.to_string()}")
}
println("")
println(
"=== 9. 一次报出全部问题(error 与 warning 混合,不中断) ===",
)
let mixed = "" +
"99999bogus" +
"空的"
println(@moongpx.validate_gpx_report(mixed))
println("")
println("=== 10. GPX 1.0 解析并升级为 1.1 ===")
let g10 = "" +
"周末徒步" +
"张三zhangsan@example.com" +
"http://example.com示例" +
"" +
"10" +
"45.51.25" +
"40" +
""
match @moongpx.parse_gpx_1_0(g10) {
Ok(g) => {
println(" version = \{g.version}(1.0 文档已转成 1.1 模型)")
match g.metadata {
Some(m) => {
match m.name {
Some(n) => println(" 根级 → metadata.name = \{n}")
None => ()
}
match m.author {
Some(p) =>
match p.email {
Some(e) =>
println(" 整串 → id=\{e.id} domain=\{e.domain}")
None => ()
}
None => ()
}
if m.links.length() > 0 {
println(" → link.href = \{m.links[0].href}")
}
}
None => ()
}
let pt = g.tracks[0].segments[0].points[0]
match pt.extensions {
Some(x) => println(" / → extensions = \{x}")
None => ()
}
}
Err(d) => println(@moongpx.format_diagnostics(d))
}
println("")
println("=== 11. 轨迹统计 ===")
match @moongpx.parse_gpx_1_0(g10) {
Ok(g) =>
if g.tracks.length() > 0 {
let s = @moongpx.track_stats(g.tracks[0])
println(" 点数 = \{s.point_count}")
println(" 距离 = \{@moongpx.format_decimal(s.distance_m)} m")
println(
" 爬升 = \{@moongpx.format_decimal(s.ascend_m)} m 下降 = \{@moongpx.format_decimal(s.descend_m)} m",
)
match (s.min_ele, s.max_ele) {
(Some(lo), Some(hi)) =>
println(
" 高程 = \{@moongpx.format_decimal(lo)} ~ \{@moongpx.format_decimal(hi)} m",
)
_ => println(" 高程 = 无")
}
}
Err(_) => ()
}
println("")
println("示例结束。")
}
///|
fn distance_m(a : @moongpx.Waypoint, b : @moongpx.Waypoint) -> Double {
let r = 6371008.8
let d2r = 0.017453292519943295
let lat1 = a.lat.to_double()
let lon1 = a.lon.to_double()
let lat2 = b.lat.to_double()
let lon2 = b.lon.to_double()
let dlat = (lat2 - lat1) * d2r
let dlon = (lon2 - lon1) * d2r
let sdlat = @math.sin(dlat / 2.0)
let sdlon = @math.sin(dlon / 2.0)
let h = sdlat * sdlat +
@math.cos(lat1 * d2r) * @math.cos(lat2 * d2r) * sdlon * sdlon
2.0 * r * @math.asin(h.sqrt())
}