Skip to main content

stac_io/
ndjson.rs

1use crate::{Error, FromJsonPath, Result};
2use stac::{Catalog, Collection, FromNdjson, Item, ItemCollection, SelfHref, ToNdjson, Value};
3use std::{
4    fs::File,
5    io::{BufRead, BufReader, BufWriter},
6    path::Path,
7};
8
9/// Create a STAC object from newline-delimited JSON.
10pub trait FromNdjsonPath: FromNdjson + FromJsonPath + SelfHref {
11    /// Reads newline-delimited JSON data from a file.
12    ///
13    /// # Examples
14    ///
15    /// ```
16    /// use stac::ItemCollection;
17    /// use stac_io::FromNdjsonPath;
18    ///
19    /// let item_collection = ItemCollection::from_ndjson_path("data/items.ndjson").unwrap();
20    /// ```
21    fn from_ndjson_path(path: impl AsRef<Path>) -> Result<Self> {
22        Self::from_json_path(path)
23    }
24}
25
26/// Write a STAC object to newline-delimited JSON.
27pub trait ToNdjsonPath: ToNdjson {
28    /// Writes a value to a path as newline-delimited JSON.
29    ///
30    /// # Examples
31    ///
32    /// ```no_run
33    /// use stac::{ItemCollection, Item};
34    /// use stac_io::ToNdjsonPath;
35    ///
36    /// let item_collection: ItemCollection = vec![Item::new("a"), Item::new("b")].into();
37    /// item_collection.to_ndjson_path("items.ndjson").unwrap();
38    /// ```
39    fn to_ndjson_path(&self, path: impl AsRef<Path>) -> Result<()> {
40        let file = File::create(path)?;
41        self.to_ndjson_writer(file)?;
42        Ok(())
43    }
44}
45
46impl FromNdjsonPath for Item {}
47impl FromNdjsonPath for Catalog {}
48impl FromNdjsonPath for Collection {}
49impl FromNdjsonPath for ItemCollection {
50    fn from_ndjson_path(path: impl AsRef<Path>) -> Result<Self> {
51        let path = path.as_ref();
52        let reader = BufReader::new(File::open(path)?);
53        let mut items = Vec::new();
54        for line in reader.lines() {
55            items.push(serde_json::from_str(&line?)?);
56        }
57        let mut item_collection = ItemCollection::from(items);
58        item_collection.set_self_href(path.to_string_lossy());
59        Ok(item_collection)
60    }
61}
62impl FromNdjsonPath for Value {
63    fn from_ndjson_path(path: impl AsRef<Path>) -> Result<Self> {
64        let path = path.as_ref();
65        let reader = BufReader::new(File::open(path)?);
66        let mut values: Vec<Value> = Vec::new();
67        for line in reader.lines() {
68            values.push(serde_json::from_str(&line?)?);
69        }
70        vec_into_value(values)
71    }
72}
73
74fn vec_into_value(mut values: Vec<Value>) -> Result<Value> {
75    if values.len() == 1 {
76        Ok(values.pop().unwrap())
77    } else {
78        Ok(ItemCollection::from(
79            values
80                .into_iter()
81                .map(|v| Item::try_from(v).map_err(Error::from))
82                .collect::<Result<Vec<_>>>()?,
83        )
84        .into())
85    }
86}
87
88impl ToNdjsonPath for Item {}
89impl ToNdjsonPath for Catalog {}
90impl ToNdjsonPath for Collection {}
91
92impl ToNdjsonPath for ItemCollection {
93    fn to_ndjson_path(&self, path: impl AsRef<Path>) -> Result<()> {
94        let file = BufWriter::new(File::create(path)?);
95        self.to_ndjson_writer(file)?;
96        Ok(())
97    }
98}
99
100impl ToNdjsonPath for Value {
101    fn to_ndjson_path(&self, path: impl AsRef<Path>) -> Result<()> {
102        match self {
103            Value::Item(item) => item.to_ndjson_path(path),
104            Value::Catalog(catalog) => catalog.to_ndjson_path(path),
105            Value::Collection(collection) => collection.to_ndjson_path(path),
106            Value::ItemCollection(item_collection) => item_collection.to_ndjson_path(path),
107        }
108    }
109}
110
111/// Returns an iterator that yields one [Item] per line from a reader.
112///
113/// # Examples
114///
115/// ```
116/// use std::io::BufReader;
117/// use std::fs::File;
118///
119/// let reader = BufReader::new(File::open("data/items.ndjson").unwrap());
120/// let items: Vec<_> = stac_io::ndjson_item_reader(reader).collect::<Result<_, _>>().unwrap();
121/// assert_eq!(items.len(), 2);
122/// ```
123pub fn ndjson_item_reader(reader: impl BufRead) -> impl Iterator<Item = Result<Item>> {
124    reader.lines().filter_map(|line| match line {
125        Ok(line) if line.is_empty() => None,
126        Ok(line) => Some(serde_json::from_str(&line).map_err(Error::from)),
127        Err(err) => Some(Err(Error::from(err))),
128    })
129}
130
131impl ToNdjsonPath for serde_json::Value {
132    fn to_ndjson_path(&self, path: impl AsRef<Path>) -> Result<()> {
133        let file = File::create(path)?;
134        self.to_ndjson_writer(file)?;
135        Ok(())
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use super::FromNdjsonPath;
142    use stac::{ItemCollection, SelfHref, Value};
143
144    #[test]
145    fn item_collection_read() {
146        let item_collection = ItemCollection::from_ndjson_path("data/items.ndjson").unwrap();
147        assert_eq!(item_collection.items.len(), 2);
148        assert!(
149            item_collection
150                .self_href()
151                .unwrap()
152                .ends_with("data/items.ndjson")
153        );
154    }
155
156    #[test]
157    fn value_read() {
158        let _ = Value::from_ndjson_path("data/items.ndjson").unwrap();
159    }
160}