tp_lib_core/detections/
anchor.rs1use std::collections::HashMap;
16
17use geo::{LineString, Point};
18
19use crate::models::{Netelement, ResolvedAnchor};
20use crate::path::candidate::CandidateNetElement;
21
22use super::error::DetectionError;
23
24pub fn apply_anchors(
35 anchors: &[ResolvedAnchor],
36 position_candidates: &mut [Vec<CandidateNetElement>],
37 emission_probs: &mut [Vec<f64>],
38 netelements: &[Netelement],
39 netelement_index: &HashMap<String, usize>,
40 gnss_index_map: Option<&[usize]>,
41) -> Result<(), DetectionError> {
42 debug_assert_eq!(position_candidates.len(), emission_probs.len());
43
44 for anchor in anchors {
45 match anchor {
46 ResolvedAnchor::Punctual {
47 netelement_id,
48 intrinsic,
49 gnss_index,
50 } => {
51 let Some(working_idx) = remap_index(*gnss_index, gnss_index_map) else {
52 continue;
53 };
54 if working_idx >= position_candidates.len() {
55 continue;
56 }
57 let Some(&ne_idx) = netelement_index.get(netelement_id) else {
58 return Err(DetectionError::UnknownNetelement {
59 source_file: String::new(),
60 source_row: 0,
61 netelement_id: netelement_id.clone(),
62 });
63 };
64 let ne = &netelements[ne_idx];
65 let pt = point_at_intrinsic(&ne.geometry, *intrinsic);
66 let forced = CandidateNetElement {
67 netelement_id: netelement_id.clone(),
68 distance_meters: 0.0,
69 intrinsic_coordinate: intrinsic.clamp(0.0, 1.0),
70 projected_point: pt,
71 };
72 position_candidates[working_idx] = vec![forced];
73 emission_probs[working_idx] = vec![1.0];
74 }
75 ResolvedAnchor::Linear {
76 netelement_id,
77 start_intrinsic,
78 end_intrinsic,
79 gnss_range,
80 } => {
81 let Some(&ne_idx) = netelement_index.get(netelement_id) else {
82 return Err(DetectionError::UnknownNetelement {
83 source_file: String::new(),
84 source_row: 0,
85 netelement_id: netelement_id.clone(),
86 });
87 };
88 let ne = &netelements[ne_idx];
89
90 let lo = *gnss_range.start();
91 let hi = *gnss_range.end();
92 let span = hi.saturating_sub(lo) as f64;
93
94 for original_idx in lo..=hi {
95 let Some(working_idx) = remap_index(original_idx, gnss_index_map) else {
96 continue;
97 };
98 if working_idx >= position_candidates.len() {
99 continue;
100 }
101
102 let frac = if span > 0.0 {
103 (original_idx - lo) as f64 / span
104 } else {
105 0.0
106 };
107 let intrinsic = start_intrinsic + frac * (end_intrinsic - start_intrinsic);
108 let intrinsic = intrinsic.clamp(0.0, 1.0);
109 let pt = point_at_intrinsic(&ne.geometry, intrinsic);
110
111 let forced = CandidateNetElement {
112 netelement_id: netelement_id.clone(),
113 distance_meters: 0.0,
114 intrinsic_coordinate: intrinsic,
115 projected_point: pt,
116 };
117 position_candidates[working_idx] = vec![forced];
118 emission_probs[working_idx] = vec![1.0];
119 }
120 }
121 }
122 }
123
124 Ok(())
125}
126
127fn remap_index(original: usize, map: Option<&[usize]>) -> Option<usize> {
128 match map {
129 None => Some(original),
130 Some(m) => m.get(original).copied(),
131 }
132}
133
134pub(crate) fn point_at_intrinsic(line: &LineString<f64>, intrinsic: f64) -> Point<f64> {
138 use geo::algorithm::haversine_distance::HaversineDistance;
139
140 let coords = &line.0;
141 if coords.is_empty() {
142 return Point::new(0.0, 0.0);
143 }
144 if coords.len() == 1 {
145 return Point::new(coords[0].x, coords[0].y);
146 }
147
148 let t = intrinsic.clamp(0.0, 1.0);
149
150 let mut seg_lens: Vec<f64> = Vec::with_capacity(coords.len() - 1);
152 let mut total = 0.0;
153 for i in 0..coords.len() - 1 {
154 let a = Point::new(coords[i].x, coords[i].y);
155 let b = Point::new(coords[i + 1].x, coords[i + 1].y);
156 let d = a.haversine_distance(&b);
157 seg_lens.push(d);
158 total += d;
159 }
160 if total <= 0.0 {
161 return Point::new(coords[0].x, coords[0].y);
162 }
163
164 let target = t * total;
165 let mut acc = 0.0;
166 for (i, &seg_len) in seg_lens.iter().enumerate() {
167 if acc + seg_len >= target || i == seg_lens.len() - 1 {
168 let local_t = if seg_len > 0.0 {
169 ((target - acc) / seg_len).clamp(0.0, 1.0)
170 } else {
171 0.0
172 };
173 let p1 = &coords[i];
174 let p2 = &coords[i + 1];
175 return Point::new(
176 p1.x + local_t * (p2.x - p1.x),
177 p1.y + local_t * (p2.y - p1.y),
178 );
179 }
180 acc += seg_len;
181 }
182
183 Point::new(coords[0].x, coords[0].y)
184}