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}