Skip to main content

fcb_core/
cj_utils.rs

1use crate::cjerror::CjError as Error;
2use cjseq::{CityJSON, CityJSONFeature};
3use std::io::{BufRead, BufReader, Read};
4
5pub struct CityJSONSeq {
6    pub cj: CityJSON,
7    pub features: Vec<CityJSONFeature>,
8}
9
10pub enum CJType {
11    Normal(CityJSON),
12    Seq(CityJSONSeq),
13}
14
15#[derive(Debug)]
16pub enum CJTypeKind {
17    Normal,
18    Seq,
19}
20
21pub trait CityJSONReader {
22    fn read_lines(&mut self) -> Box<dyn Iterator<Item = Result<String, Error>> + '_>;
23}
24
25impl<R: Read> CityJSONReader for BufReader<R> {
26    fn read_lines(&mut self) -> Box<dyn Iterator<Item = Result<String, Error>> + '_> {
27        Box::new(self.lines().map(|line| line.map_err(Error::Io)))
28    }
29}
30
31impl CityJSONReader for &str {
32    fn read_lines(&mut self) -> Box<dyn Iterator<Item = Result<String, Error>> + '_> {
33        match std::fs::File::open(self) {
34            Ok(file) => Box::new(
35                BufReader::new(file)
36                    .lines()
37                    .map(|line| line.map_err(Error::Io)),
38            ),
39            Err(e) => Box::new(std::iter::once(Err(Error::Io(e)))),
40        }
41    }
42}
43
44fn parse_cityjson<T: CityJSONReader>(mut source: T, cj_type: CJTypeKind) -> Result<CJType, Error> {
45    let mut lines = source.read_lines();
46
47    match cj_type {
48        CJTypeKind::Normal => {
49            let content = lines.collect::<Result<Vec<_>, Error>>()?.join("\n");
50
51            let cj: CityJSON = serde_json::from_str(&content)?;
52            Ok(CJType::Normal(cj))
53        }
54
55        CJTypeKind::Seq => {
56            // Read first line as CityJSON metadata
57            let first_line = lines
58                .next()
59                .ok_or(Error::Io(std::io::Error::other("Empty input")))?;
60            let cj: CityJSON = serde_json::from_str(&first_line?)?;
61
62            // Read remaining lines as CityJSONFeatures
63            let features: Result<Vec<_>, Error> = lines
64                .map(|line| -> Result<_, Error> {
65                    let line = line?;
66                    Ok(serde_json::from_str(&line)?)
67                })
68                .collect();
69
70            Ok(CJType::Seq(CityJSONSeq {
71                cj,
72                features: features?,
73            }))
74        }
75    }
76}
77
78/// Read CityJSON from a file path
79pub fn read_cityjson(file: &str, cj_type: CJTypeKind) -> Result<CJType, Error> {
80    parse_cityjson(file, cj_type)
81}
82
83/// Read CityJSON from any reader (file or stdin)
84pub fn read_cityjson_from_reader<R: Read>(
85    reader: BufReader<R>,
86    cj_type: CJTypeKind,
87) -> Result<CJType, Error> {
88    parse_cityjson(reader, cj_type)
89}
90
91/// Tests reading CityJSON data from a memory string
92///
93/// # Arguments
94/// None
95///
96/// # Returns
97/// * `Result<()>` - Ok if test passes, Error otherwise
98///
99/// # Example
100/// ```
101/// let test_data = include_str!("../tests/data/small.city.jsonl");
102/// let result = read_cityjson(test_data, CJTypeKind::Seq)?;
103/// ```
104#[cfg(test)]
105mod tests {
106    use std::{fs::File, path::PathBuf};
107
108    use super::*;
109
110    #[test]
111    fn test_read_from_memory() -> Result<(), Error> {
112        let input_file = BufReader::new(File::open(
113            PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/data/small.city.jsonl"),
114        )?);
115        let result = read_cityjson_from_reader(input_file, CJTypeKind::Seq)?;
116
117        if let CJType::Seq(seq) = result {
118            assert_eq!(seq.features.len(), 3);
119        } else {
120            panic!("Expected Seq type");
121        }
122
123        Ok(())
124    }
125}