1use std::{
6 cell::RefCell,
7 collections::{BTreeMap, HashMap},
8};
9
10use serde::{Deserialize, Serialize};
11
12use crate::{LandXmlSourceId, LandXmlUnits};
13
14pub type LandXmlProperties = BTreeMap<String, String>;
15
16#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
18pub struct LandXmlPlanPoint {
19 pub northing: f64,
20 pub easting: f64,
21 pub elevation: Option<f64>,
22}
23
24#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
26#[serde(tag = "kind", rename_all = "snake_case")]
27pub enum LandXmlPlanPointLocation {
28 Coordinates {
29 point: LandXmlPlanPoint,
30 pnt_ref: Option<String>,
33 },
34 PointReference {
35 pnt_ref: String,
36 },
37}
38
39#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
41pub struct LandXmlCgPoint {
42 pub source_id: LandXmlSourceId,
43 pub scope_id: LandXmlSourceId,
44 pub ordinal: usize,
45 pub name: Option<String>,
46 pub code: Option<String>,
47 pub description: Option<String>,
48 pub point: Option<LandXmlPlanPoint>,
49 pub pnt_ref: Option<String>,
50 pub properties: LandXmlProperties,
51}
52
53#[derive(Clone, Debug, Default, PartialEq)]
56pub(crate) struct LandXmlPlanReferenceIndex {
57 pub(crate) scoped: HashMap<(LandXmlSourceId, String), Option<ReferenceTarget>>,
58 pub(crate) global: HashMap<String, Option<ReferenceTarget>>,
59}
60
61#[derive(Clone, Debug, PartialEq)]
62pub(crate) struct ReferenceTarget {
63 pub(crate) index: usize,
64 pub(crate) source_id: LandXmlSourceId,
65}
66
67impl LandXmlPlanReferenceIndex {
68 pub(crate) fn insert(&mut self, point: &LandXmlCgPoint) {
69 self.insert_at(point, point.ordinal.saturating_sub(1));
70 }
71
72 fn insert_at(&mut self, point: &LandXmlCgPoint, index: usize) {
73 let mut keys = Vec::new();
77 if let Some(name) = &point.name {
78 keys.push(name.clone());
79 }
80 if let Some(oid) = point.properties.get("oID") {
81 keys.push(oid.clone());
82 }
83 keys.sort();
84 keys.dedup();
85 let target = ReferenceTarget {
86 index,
87 source_id: point.source_id.clone(),
88 };
89 for key in keys {
90 self.scoped
91 .entry((point.scope_id.clone(), key.clone()))
92 .and_modify(|slot| *slot = None)
93 .or_insert(Some(target.clone()));
94 self.global
95 .entry(key)
96 .and_modify(|slot| *slot = None)
97 .or_insert(Some(target.clone()));
98 }
99 }
100
101 pub(crate) fn from_points_checked<E>(
102 points: &[LandXmlCgPoint],
103 mut check: impl FnMut() -> std::result::Result<(), E>,
104 ) -> std::result::Result<Self, E> {
105 let mut index = Self::default();
106 for (position, point) in points.iter().enumerate() {
107 check()?;
108 index.insert_at(point, position);
109 }
110 Ok(index)
111 }
112}
113
114#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
116pub struct LandXmlMonument {
117 pub source_id: LandXmlSourceId,
118 pub point_scope_id: Option<LandXmlSourceId>,
120 pub ordinal: usize,
121 pub name: Option<String>,
122 pub code: Option<String>,
123 pub description: Option<String>,
124 pub pnt_ref: Option<String>,
125 pub point: Option<LandXmlPlanPoint>,
126 pub properties: LandXmlProperties,
127}
128
129#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
130#[serde(rename_all = "snake_case")]
131pub enum LandXmlGeometryKind {
132 Line,
133 Curve,
134 IrregularLine,
135}
136
137#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
139pub struct LandXmlPlanGeometry {
140 pub source_id: LandXmlSourceId,
141 pub ordinal: usize,
142 pub kind: LandXmlGeometryKind,
143 pub point_scope_id: Option<LandXmlSourceId>,
144 pub start: LandXmlPlanPointLocation,
145 pub end: LandXmlPlanPointLocation,
146 pub center: Option<LandXmlPlanPointLocation>,
147 pub pi: Option<LandXmlPlanPointLocation>,
148 pub intermediate_points: Vec<LandXmlPlanPoint>,
149 pub rotation: Option<String>,
150 pub radius: Option<f64>,
151 pub declared_length: Option<f64>,
152 pub properties: LandXmlProperties,
153}
154
155#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
156pub struct LandXmlPlanFeature {
157 pub source_id: LandXmlSourceId,
158 pub ordinal: usize,
159 pub name: Option<String>,
160 pub code: Option<String>,
161 pub description: Option<String>,
162 pub properties: LandXmlProperties,
163 pub locations: Vec<LandXmlPlanPointLocation>,
164 pub geometry: Vec<LandXmlPlanGeometry>,
165}
166
167#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
168pub struct LandXmlParcel {
169 pub source_id: LandXmlSourceId,
170 pub ordinal: usize,
171 pub name: Option<String>,
172 pub code: Option<String>,
173 pub description: Option<String>,
174 pub title: Option<String>,
175 pub declared_area: Option<f64>,
176 pub declared_perimeter: Option<f64>,
177 pub declared_area_unit: Option<String>,
178 pub properties: LandXmlProperties,
179 pub loops: Vec<Vec<LandXmlPlanGeometry>>,
181 pub preservation_reason: Option<String>,
184}
185
186#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
188#[serde(tag = "kind", rename_all = "snake_case")]
189pub enum LandXmlParcelState {
190 Analytic,
191 PreservedOnly { reason: String },
192}
193
194#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
196pub struct LandXmlParcelProbe {
197 pub state: LandXmlParcelState,
198 pub perimeter_in_declared_linear_units: Option<f64>,
199 pub area_in_declared_square_units: Option<f64>,
200 pub declared_area: Option<f64>,
201 pub declared_perimeter: Option<f64>,
202 pub perimeter_in_meters: Option<f64>,
203 pub area_in_square_meters: Option<f64>,
204}
205
206#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
208pub struct LandXmlPlanSourceBatch {
209 pub source_ids: Vec<LandXmlSourceId>,
210}
211
212#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
214pub struct LandXmlPlanDocument {
215 pub schema: String,
218 pub version: String,
219 pub capability_diagnostics: Vec<crate::LandXmlCapabilityDiagnostic>,
221 pub units: Option<LandXmlUnits>,
222 pub area_unit: Option<String>,
223 pub area_scale_to_square_meters: Option<f64>,
224 pub(crate) cogo_points: Vec<LandXmlCgPoint>,
225 pub monuments: Vec<LandXmlMonument>,
226 pub plan_features: Vec<LandXmlPlanFeature>,
227 pub parcels: Vec<LandXmlParcel>,
228 pub warnings: Vec<String>,
229 #[serde(skip)]
232 pub(crate) reference_index: RefCell<Option<LandXmlPlanReferenceIndex>>,
233}
234
235impl LandXmlPlanDocument {
236 pub fn cogo_points(&self) -> &[LandXmlCgPoint] {
239 &self.cogo_points
240 }
241
242 pub fn replace_cogo_points(&mut self, cogo_points: Vec<LandXmlCgPoint>) {
244 self.cogo_points = cogo_points;
245 *self.reference_index.get_mut() = None;
246 }
247}