ifc_lite_landxml/plan/numeric/
references.rs1use std::{
6 cell::Ref,
7 collections::{HashMap, HashSet},
8};
9
10use super::{LandXmlPlanDocument, LandXmlPlanPoint, LandXmlPlanPointLocation, ParcelProbeWork};
11use crate::plan::model::LandXmlPlanReferenceIndex;
12use crate::LandXmlMonument;
13
14pub struct LandXmlPlanResolver<'a> {
18 document: &'a LandXmlPlanDocument,
19 cache: HashMap<(Option<crate::LandXmlSourceId>, String), Option<LandXmlPlanPoint>>,
20 work: usize,
21 max_work: usize,
22}
23impl<'a> LandXmlPlanResolver<'a> {
24 pub fn new(document: &'a LandXmlPlanDocument, max_work: usize) -> Self {
25 Self {
26 document,
27 cache: HashMap::new(),
28 work: 0,
29 max_work,
30 }
31 }
32 pub fn resolve(
33 &mut self,
34 scope: Option<&crate::LandXmlSourceId>,
35 location: &LandXmlPlanPointLocation,
36 ) -> std::result::Result<Option<LandXmlPlanPoint>, crate::LandXmlError> {
37 let LandXmlPlanPointLocation::PointReference { pnt_ref } = location else {
38 return Ok(match location {
39 LandXmlPlanPointLocation::Coordinates { point, .. } => Some(*point),
40 _ => None,
41 });
42 };
43 let first = (scope.cloned(), pnt_ref.clone());
44 if let Some(value) = self.cache.get(&first) {
45 return Ok(*value);
46 }
47 let index = self
48 .document
49 .reference_index_with(|| Ok::<(), crate::LandXmlError>(()))?;
50 let mut key = first.clone();
51 let mut path = Vec::new();
52 let mut seen = HashSet::new();
53 let result = loop {
54 self.check_work()?;
55 if let Some(value) = self.cache.get(&key) {
56 break *value;
57 }
58 let Some(position) = self
59 .document
60 .lookup_reference(&index, key.0.as_ref(), &key.1)
61 else {
62 break None;
63 };
64 if !seen.insert(position) {
65 break None;
66 }
67 path.push(key.clone());
68 let Some(point) = self.document.cogo_points().get(position) else {
69 break None;
70 };
71 if let Some(value) = point.point {
72 break Some(value);
73 }
74 let Some(reference) = point.pnt_ref.clone() else {
75 break None;
76 };
77 key = (Some(point.scope_id.clone()), reference);
78 };
79 for item in path {
80 self.cache.insert(item, result);
81 }
82 self.cache.insert(first, result);
83 Ok(result)
84 }
85
86 pub fn work_used(&self) -> usize {
90 self.work
91 }
92
93 pub(super) fn check_work(&mut self) -> std::result::Result<(), crate::LandXmlError> {
94 self.work = self.work.checked_add(1).ok_or_else(|| {
95 crate::LandXmlError::new(
96 crate::LandXmlDiagnosticCode::LimitExceeded,
97 "LandXML bulk plan work limit exceeded",
98 )
99 })?;
100 if self.work > self.max_work {
101 return Err(crate::LandXmlError::new(
102 crate::LandXmlDiagnosticCode::LimitExceeded,
103 "LandXML bulk plan work limit exceeded",
104 ));
105 }
106 Ok(())
107 }
108}
109
110impl LandXmlPlanDocument {
111 pub(crate) fn ensure_reference_index(&self) {
115 let _ = self.reference_index_with(|| Ok::<(), ()>(()));
116 }
117
118 pub fn resolve_point(
121 &self,
122 scope_id: Option<&crate::LandXmlSourceId>,
123 location: &LandXmlPlanPointLocation,
124 ) -> Option<LandXmlPlanPoint> {
125 match location {
126 LandXmlPlanPointLocation::Coordinates { point, .. } => Some(*point),
127 LandXmlPlanPointLocation::PointReference { pnt_ref } => self
128 .resolve_reference(scope_id, pnt_ref, self.cogo_points().len(), None)
129 .ok()
130 .flatten(),
131 }
132 }
133
134 pub fn resolve_point_with_cancel(
136 &self,
137 scope_id: Option<&crate::LandXmlSourceId>,
138 location: &LandXmlPlanPointLocation,
139 cancelled: Option<&dyn crate::LandXmlCancellation>,
140 ) -> std::result::Result<Option<LandXmlPlanPoint>, crate::LandXmlError> {
141 match location {
142 LandXmlPlanPointLocation::Coordinates { point, .. } => Ok(Some(*point)),
143 LandXmlPlanPointLocation::PointReference { pnt_ref } => {
144 self.resolve_reference(scope_id, pnt_ref, self.cogo_points().len(), cancelled)
145 }
146 }
147 }
148
149 fn resolve_reference(
150 &self,
151 scope_id: Option<&crate::LandXmlSourceId>,
152 reference: &str,
153 budget: usize,
154 cancelled: Option<&dyn crate::LandXmlCancellation>,
155 ) -> std::result::Result<Option<LandXmlPlanPoint>, crate::LandXmlError> {
156 let index = self.reference_index_with(|| {
157 if cancelled.is_some_and(crate::LandXmlCancellation::is_cancelled) {
158 return Err(crate::LandXmlError::new(
159 crate::LandXmlDiagnosticCode::Cancelled,
160 "COGO reference index rebuild cancelled",
161 ));
162 }
163 Ok(())
164 })?;
165 let mut next_scope = scope_id.cloned();
166 let mut next_reference = reference;
167 let mut remaining = budget;
168 let mut seen = HashSet::new();
169 while remaining > 0 {
170 if cancelled.is_some_and(crate::LandXmlCancellation::is_cancelled) {
171 return Err(crate::LandXmlError::new(
172 crate::LandXmlDiagnosticCode::Cancelled,
173 "COGO reference resolution cancelled",
174 ));
175 }
176 let Some(index) = self.lookup_reference(&index, next_scope.as_ref(), next_reference)
177 else {
178 return Ok(None);
179 };
180 if !seen.insert(index) {
181 return Ok(None);
182 }
183 let Some(point) = self.cogo_points().get(index) else {
184 return Ok(None);
185 };
186 if let Some(value) = point.point {
187 return Ok(Some(value));
188 }
189 next_scope = Some(point.scope_id.clone());
190 let Some(next) = point.pnt_ref.as_deref() else {
191 return Ok(None);
192 };
193 next_reference = next;
194 remaining -= 1;
195 }
196 Ok(None)
197 }
198
199 pub(super) fn resolve_point_with_work<W: ParcelProbeWork>(
200 &self,
201 scope_id: Option<&crate::LandXmlSourceId>,
202 location: &LandXmlPlanPointLocation,
203 work: &mut W,
204 ) -> std::result::Result<Option<LandXmlPlanPoint>, crate::LandXmlError> {
205 match location {
206 LandXmlPlanPointLocation::Coordinates { point, .. } => Ok(Some(*point)),
207 LandXmlPlanPointLocation::PointReference { pnt_ref } => {
208 let index = self.reference_index_with(|| work.check())?;
209 let mut next_scope = scope_id.cloned();
210 let mut next_reference = pnt_ref.as_str();
211 let mut remaining = self.cogo_points().len();
212 let mut seen = HashSet::new();
213 while remaining > 0 {
214 work.check()?;
215 let Some(index) =
216 self.lookup_reference(&index, next_scope.as_ref(), next_reference)
217 else {
218 return Ok(None);
219 };
220 if !seen.insert(index) {
221 return Ok(None);
222 }
223 let Some(point) = self.cogo_points().get(index) else {
224 return Ok(None);
225 };
226 if point.point.is_some() {
227 return Ok(point.point);
228 }
229 next_scope = Some(point.scope_id.clone());
230 let Some(next) = point.pnt_ref.as_deref() else {
231 return Ok(None);
232 };
233 next_reference = next;
234 remaining -= 1;
235 }
236 Ok(None)
237 }
238 }
239 }
240
241 fn reference_index_with<E>(
246 &self,
247 check: impl FnMut() -> std::result::Result<(), E>,
248 ) -> std::result::Result<Ref<'_, LandXmlPlanReferenceIndex>, E> {
249 if self.reference_index.borrow().is_none() {
250 let rebuilt =
251 LandXmlPlanReferenceIndex::from_points_checked(self.cogo_points(), check)?;
252 *self.reference_index.borrow_mut() = Some(rebuilt);
253 }
254 Ok(Ref::map(self.reference_index.borrow(), |index| {
255 index
256 .as_ref()
257 .expect("reference index is initialized before it is borrowed")
258 }))
259 }
260
261 fn lookup_reference(
262 &self,
263 index: &LandXmlPlanReferenceIndex,
264 scope_id: Option<&crate::LandXmlSourceId>,
265 reference: &str,
266 ) -> Option<usize> {
267 let scoped_target =
268 scope_id.and_then(|scope| index.scoped.get(&(scope.clone(), reference.to_owned())));
269 let target = scoped_target.or_else(|| index.global.get(reference));
270 if let Some(Some(target)) = target {
271 if self
272 .cogo_points()
273 .get(target.index)
274 .is_some_and(|point| point.source_id == target.source_id)
275 {
276 return Some(target.index);
277 }
278 }
279 None
283 }
284
285 pub fn resolve_monument_point(&self, monument: &LandXmlMonument) -> Option<LandXmlPlanPoint> {
287 monument.point.or_else(|| {
288 monument.pnt_ref.as_ref().and_then(|pnt_ref| {
289 self.resolve_point(
290 monument.point_scope_id.as_ref(),
291 &LandXmlPlanPointLocation::PointReference {
292 pnt_ref: pnt_ref.clone(),
293 },
294 )
295 })
296 })
297 }
298}