Skip to main content

oxihuman_export/
landxml_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! LandXML civil engineering export stub.
6
7/// A LandXML surface TIN (Triangulated Irregular Network).
8#[derive(Debug, Clone)]
9pub struct LandXmlSurface {
10    pub name: String,
11    pub verts: Vec<[f64; 3]>,
12    pub tris: Vec<[u32; 3]>,
13}
14
15/// A LandXML alignment stub.
16#[derive(Debug, Clone)]
17pub struct LandXmlAlignment {
18    pub name: String,
19    pub sta_start: f64,
20    pub sta_end: f64,
21    pub points: Vec<[f64; 3]>,
22}
23
24/// LandXML export container.
25#[derive(Debug, Clone, Default)]
26pub struct LandXmlExport {
27    pub version: String,
28    pub surfaces: Vec<LandXmlSurface>,
29    pub alignments: Vec<LandXmlAlignment>,
30}
31
32/// Create a new LandXML export.
33pub fn new_landxml_export(version: &str) -> LandXmlExport {
34    LandXmlExport {
35        version: version.to_string(),
36        surfaces: Vec::new(),
37        alignments: Vec::new(),
38    }
39}
40
41/// Add a TIN surface.
42pub fn add_landxml_surface(
43    export: &mut LandXmlExport,
44    name: &str,
45    verts: Vec<[f64; 3]>,
46    tris: Vec<[u32; 3]>,
47) {
48    export.surfaces.push(LandXmlSurface {
49        name: name.to_string(),
50        verts,
51        tris,
52    });
53}
54
55/// Add an alignment.
56pub fn add_landxml_alignment(
57    export: &mut LandXmlExport,
58    name: &str,
59    sta_start: f64,
60    sta_end: f64,
61    points: Vec<[f64; 3]>,
62) {
63    export.alignments.push(LandXmlAlignment {
64        name: name.to_string(),
65        sta_start,
66        sta_end,
67        points,
68    });
69}
70
71/// Return surface count.
72pub fn landxml_surface_count(export: &LandXmlExport) -> usize {
73    export.surfaces.len()
74}
75
76/// Return alignment count.
77pub fn landxml_alignment_count(export: &LandXmlExport) -> usize {
78    export.alignments.len()
79}
80
81/// Render a stub LandXML header.
82pub fn landxml_xml_header(export: &LandXmlExport) -> String {
83    format!(
84        "<LandXML version=\"{}\" xmlns=\"http://www.landxml.org/schema/LandXML-{}\">",
85        export.version, export.version
86    )
87}
88
89/// Validate all TIN triangle indices.
90pub fn validate_landxml(export: &LandXmlExport) -> bool {
91    export.surfaces.iter().all(|s| {
92        let n = s.verts.len() as u32;
93        s.tris.iter().all(|t| t[0] < n && t[1] < n && t[2] < n)
94    })
95}
96
97/// Return total triangle count across all surfaces.
98pub fn landxml_total_tris(export: &LandXmlExport) -> usize {
99    export.surfaces.iter().map(|s| s.tris.len()).sum()
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    fn simple_tin() -> (Vec<[f64; 3]>, Vec<[u32; 3]>) {
107        let v = vec![
108            [0.0, 0.0, 0.0],
109            [1.0, 0.0, 1.0],
110            [0.0, 1.0, 0.5],
111            [1.0, 1.0, 1.5],
112        ];
113        let t = vec![[0, 1, 2], [1, 3, 2]];
114        (v, t)
115    }
116
117    #[test]
118    fn test_new_export_empty() {
119        let exp = new_landxml_export("1.2");
120        assert_eq!(landxml_surface_count(&exp), 0);
121    }
122
123    #[test]
124    fn test_add_surface() {
125        let mut exp = new_landxml_export("1.2");
126        let (v, t) = simple_tin();
127        add_landxml_surface(&mut exp, "Ground", v, t);
128        assert_eq!(landxml_surface_count(&exp), 1);
129    }
130
131    #[test]
132    fn test_add_alignment() {
133        let mut exp = new_landxml_export("1.2");
134        add_landxml_alignment(
135            &mut exp,
136            "Road1",
137            0.0,
138            100.0,
139            vec![[0.0, 0.0, 0.0], [100.0, 0.0, 0.0]],
140        );
141        assert_eq!(landxml_alignment_count(&exp), 1);
142    }
143
144    #[test]
145    fn test_header_contains_version() {
146        let exp = new_landxml_export("1.2");
147        assert!(landxml_xml_header(&exp).contains("1.2"));
148    }
149
150    #[test]
151    fn test_validate_valid() {
152        let mut exp = new_landxml_export("1.2");
153        let (v, t) = simple_tin();
154        add_landxml_surface(&mut exp, "G", v, t);
155        assert!(validate_landxml(&exp));
156    }
157
158    #[test]
159    fn test_total_tris() {
160        let mut exp = new_landxml_export("1.2");
161        let (v, t) = simple_tin();
162        add_landxml_surface(&mut exp, "G", v, t);
163        assert_eq!(landxml_total_tris(&exp), 2);
164    }
165
166    #[test]
167    fn test_validate_empty() {
168        assert!(validate_landxml(&new_landxml_export("1.2")));
169    }
170
171    #[test]
172    fn test_version_stored() {
173        let exp = new_landxml_export("2.0");
174        assert_eq!(exp.version, "2.0");
175    }
176
177    #[test]
178    fn test_alignment_stations() {
179        let mut exp = new_landxml_export("1.2");
180        add_landxml_alignment(&mut exp, "R", 100.0, 500.0, vec![]);
181        assert!((exp.alignments[0].sta_start - 100.0).abs() < 1e-9);
182        assert!((exp.alignments[0].sta_end - 500.0).abs() < 1e-9);
183    }
184}