1use pyo3::{exceptions::PyOSError, prelude::*, types::PyDict};
2use thrust::data::eurocontrol::database::{AirwayDatabase, ResolvedPoint, ResolvedRouteSegment};
3use thrust::data::field15::Field15Parser;
4
5#[pyclass]
6pub struct AiracDatabase {
7 database: AirwayDatabase,
8}
9
10#[pyclass(get_all)]
11#[derive(Debug, Clone)]
12pub struct Point {
13 latitude: f64,
14 longitude: f64,
15 name: Option<String>,
16}
17
18#[pymethods]
19impl Point {
20 fn __repr__(&self) -> String {
21 format!(
22 "Point(latitude={:.6}, longitude={:.6}{})",
23 self.latitude,
24 self.longitude,
25 match &self.name {
26 Some(name) => format!(", name='{}'", name),
27 None => String::new(),
28 }
29 )
30 }
31
32 fn to_dict(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
33 let d = PyDict::new(py);
34 d.set_item("latitude", self.latitude)?;
35 d.set_item("longitude", self.longitude)?;
36 if let Some(name) = &self.name {
37 d.set_item("name", name)?;
38 }
39 Ok(d.into())
40 }
41}
42
43impl From<ResolvedPoint> for Point {
44 fn from(point: ResolvedPoint) -> Self {
45 match point {
46 ResolvedPoint::AirportHeliport(airport) => Self {
47 latitude: airport.latitude,
48 longitude: airport.longitude,
49 name: Some(airport.icao),
50 },
51 ResolvedPoint::Navaid(navaid) => Self {
52 latitude: navaid.latitude,
53 longitude: navaid.longitude,
54 name: navaid.name,
55 },
56 ResolvedPoint::DesignatedPoint(designated_point) => Self {
57 latitude: designated_point.latitude,
58 longitude: designated_point.longitude,
59 name: Some(designated_point.designator),
60 },
61 ResolvedPoint::Coordinates { latitude, longitude } => Self {
62 latitude,
63 longitude,
64 name: None,
65 },
66 ResolvedPoint::None => Self {
67 latitude: 0.0,
68 longitude: 0.0,
69 name: None,
70 },
71 }
72 }
73}
74
75#[pyclass(get_all)]
76#[derive(Debug, Clone)]
77pub struct Segment {
78 start: Point,
79 end: Point,
80 name: Option<String>,
81}
82
83#[pymethods]
84impl Segment {
85 fn __repr__(&self) -> String {
86 format!(
87 "Segment(start={}, end={}{})",
88 self.start.__repr__(),
89 self.end.__repr__(),
90 match &self.name {
91 Some(name) => format!(", name='{}'", name),
92 None => String::new(),
93 }
94 )
95 }
96
97 fn to_dict(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
98 let d = PyDict::new(py);
99 d.set_item("start", self.start.to_dict(py)?)?;
100 d.set_item("end", self.end.to_dict(py)?)?;
101 if let Some(name) = &self.name {
102 d.set_item("name", name)?;
103 }
104 Ok(d.into())
105 }
106}
107
108impl From<ResolvedRouteSegment> for Segment {
109 fn from(segment: ResolvedRouteSegment) -> Self {
110 Self {
111 start: Point::from(segment.start),
112 end: Point::from(segment.end),
113 name: segment.name,
114 }
115 }
116}
117
118#[pymethods]
119impl AiracDatabase {
120 #[new]
121 fn new(path: String) -> PyResult<Self> {
122 let database =
123 AirwayDatabase::new(std::path::Path::new(&path)).map_err(|e| PyOSError::new_err(e.to_string()))?;
124 Ok(Self { database })
125 }
126
127 fn enrich_route(&self, route: String) -> Vec<Segment> {
128 let elements = Field15Parser::parse(&route);
129 let enriched = self.database.enrich_route(elements);
130 enriched.into_iter().map(Segment::from).collect()
131 }
132}
133
134pub fn init(py: Python<'_>) -> PyResult<Bound<'_, PyModule>> {
135 let m = PyModule::new(py, "field15")?;
136 m.add_class::<AiracDatabase>()?;
137 m.add_class::<Point>()?;
138 m.add_class::<Segment>()?;
139 Ok(m)
140}