tp_lib_core/models/result.rs
1//! Projected position result data model
2
3use geo::Point;
4use serde::{Deserialize, Serialize};
5use crate::models::GnssPosition;
6
7/// Represents a GNSS position projected onto a railway netelement
8///
9/// A `ProjectedPosition` is the result of projecting a GNSS measurement onto the
10/// nearest railway track segment. It preserves the original GNSS data and adds:
11///
12/// - Projected coordinates on the track centerline
13/// - Measure (distance along track from netelement start)
14/// - Projection distance (perpendicular distance from original to projected point)
15/// - Netelement assignment
16///
17/// # Use Cases
18///
19/// - Calculate train progress along tracks
20/// - Analyze position accuracy and quality
21/// - Detect track deviations or sensor errors
22/// - Generate linear referencing for asset management
23///
24/// # Examples
25///
26/// ```rust,no_run
27/// use tp_core::{parse_gnss_csv, parse_network_geojson, RailwayNetwork};
28/// use tp_core::{project_gnss, ProjectionConfig};
29///
30/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
31/// // Load and project data
32/// let netelements = parse_network_geojson("network.geojson")?;
33/// let network = RailwayNetwork::new(netelements)?;
34/// let positions = parse_gnss_csv("gnss.csv", "EPSG:4326", "latitude", "longitude", "timestamp")?;
35///
36/// let config = ProjectionConfig::default();
37/// let projected = project_gnss(&positions, &network, &config)?;
38///
39/// // Analyze results
40/// for pos in projected {
41/// println!("Track position: {}m on {}", pos.measure_meters, pos.netelement_id);
42/// println!("Projection accuracy: {:.2}m", pos.projection_distance_meters);
43/// }
44/// # Ok(())
45/// # }
46/// ```
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct ProjectedPosition {
49 /// Original GNSS measurement (preserved)
50 pub original: GnssPosition,
51
52 /// Projected coordinates on the track axis
53 pub projected_coords: Point<f64>,
54
55 /// ID of the netelement this position was projected onto
56 pub netelement_id: String,
57
58 /// Distance along the netelement from start (in meters)
59 pub measure_meters: f64,
60
61 /// Distance between original GNSS position and projected position (in meters)
62 pub projection_distance_meters: f64,
63
64 /// Coordinate Reference System of the projected coordinates
65 pub crs: String,
66}
67
68impl ProjectedPosition {
69 /// Create a new projected position
70 pub fn new(
71 original: GnssPosition,
72 projected_coords: Point<f64>,
73 netelement_id: String,
74 measure_meters: f64,
75 projection_distance_meters: f64,
76 crs: String,
77 ) -> Self {
78 Self {
79 original,
80 projected_coords,
81 netelement_id,
82 measure_meters,
83 projection_distance_meters,
84 crs,
85 }
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92 use chrono::{TimeZone, FixedOffset};
93 use std::collections::HashMap;
94
95 #[test]
96 fn test_projected_position_creation() {
97 let timestamp = FixedOffset::east_opt(3600)
98 .unwrap()
99 .with_ymd_and_hms(2025, 12, 9, 14, 30, 0)
100 .unwrap();
101
102 let original = GnssPosition {
103 latitude: 50.8503,
104 longitude: 4.3517,
105 timestamp,
106 crs: "EPSG:4326".to_string(),
107 metadata: HashMap::new(),
108 };
109
110 let projected = ProjectedPosition::new(
111 original.clone(),
112 Point::new(4.3517, 50.8503),
113 "NE001".to_string(),
114 100.5,
115 2.3,
116 "EPSG:4326".to_string(),
117 );
118
119 assert_eq!(projected.netelement_id, "NE001");
120 assert_eq!(projected.measure_meters, 100.5);
121 assert_eq!(projected.projection_distance_meters, 2.3);
122 }
123}