1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
//! This module provides Python bindings for importing and interacting with railway graphs.
//!
use pyo3::prelude::*;
use pyo3::wrap_pyfunction;
use pythonize::pythonize;

use crate::importer::overpass_importer::OverpassImporter;
use crate::importer::RailwayGraphImporter;
use crate::railway_model::railway_graph::RailwayGraphExt;
use crate::railway_model::RailwayGraph;
use crate::types::{EdgeId, NodeId};

mod overpass_api_client;

/// A Python wrapper for the OverpassImporter struct.
#[pyclass]
pub struct PyOverpassImporter {}

#[pymethods]
impl PyOverpassImporter {
    /// Create a new PyOverpassImporter instance.
    #[new]
    fn new() -> Self {
        PyOverpassImporter {}
    }

    /// Import railway graph data from a JSON string.
    ///
    /// # Arguments
    ///
    /// * `input` - A JSON string containing railway graph data.
    ///
    /// # Returns
    ///
    /// * A PyRailwayGraph instance containing the imported railway graph data.
    fn import_graph(&self, input: &str) -> PyResult<PyRailwayGraph> {
        let json_value: serde_json::Value = serde_json::from_str(input)
            .map_err(|err| PyErr::new::<pyo3::exceptions::PyValueError, _>(format!("{}", err)))?;

        let railway_graph = OverpassImporter::import(&json_value)
            .map_err(|err| PyErr::new::<pyo3::exceptions::PyValueError, _>(format!("{}", err)))?;

        Ok(PyRailwayGraph {
            inner: railway_graph,
        })
    }
}

/// Export a PyRailwayGraph to an SVG string.
///
/// This function generates an SVG representation of the given PyRailwayGraph.
///
/// # Arguments
///
/// * `graph` - A reference to the PyRailwayGraph to be exported.
///
/// # Returns
///
/// * A PyResult containing a String of the SVG representation of the graph, or an error if the
///   conversion failed.
#[pyfunction]
pub fn export_svg(graph: &PyRailwayGraph) -> PyResult<String> {
    Ok(crate::export::generate_svg_string(&graph.inner).unwrap())
}

/// A Python wrapper for the RailwayGraph struct.
#[pyclass]
pub struct PyRailwayGraph {
    inner: RailwayGraph,
}

#[pymethods]
impl PyRailwayGraph {
    /// Get the number of nodes in the railway graph.
    ///
    /// # Returns
    ///
    /// * The number of nodes in the railway graph.
    fn node_count(&self) -> usize {
        self.inner.physical_graph.graph.node_count()
    }

    /// Get the number of edges in the railway graph.
    ///
    /// # Returns
    ///
    /// * The number of edges in the railway graph.
    fn edge_count(&self) -> usize {
        self.inner.physical_graph.graph.edge_count()
    }

    /// Get a node by its ID from the railway graph.
    ///
    /// # Arguments
    ///
    /// * `node_id` - The ID of the node to retrieve.
    ///
    /// # Returns
    ///
    /// * An optional `RailwayNode` instance if the node with the specified ID is found.
    fn get_node_by_id(&self, node_id: NodeId) -> Option<PyObject> {
        Some(Python::with_gil(|py| {
            pythonize(py, &self.inner.get_node_by_id(node_id).unwrap())
                .unwrap()
                .to_object(py)
        }))
    }

    /// Get an edge by its ID from the railway graph.
    ///
    /// # Arguments
    ///
    /// * `edge_id` - The ID of the edge to retrieve.
    ///
    /// # Returns
    ///
    /// * An optional `RailwayEdge` instance if the edge with the specified ID is found.
    fn get_edge_by_id(&self, edge_id: EdgeId) -> Option<PyObject> {
        Some(Python::with_gil(|py| {
            pythonize(py, &self.inner.get_edge_by_id(edge_id))
                .unwrap()
                .to_object(py)
        }))
    }
}

/// Initialize the openrailwaymap_exporter Python module.
///
/// # Arguments
///
/// * `_py` - The Python interpreter state.
/// * `m` - The Python module object.
///
/// # Returns
///
/// * A PyResult indicating the success or failure of the module initialization.
#[pymodule]
fn openrailwaymap_exporter(py: Python<'_>, m: &PyModule) -> PyResult<()> {
    m.add_class::<PyOverpassImporter>()?;
    m.add_class::<PyRailwayGraph>()?;
    m.add_function(wrap_pyfunction!(export_svg, m)?)?;
    overpass_api_client::init_overpass_api_client(py, m)?;
    Ok(())
}