gistools/writers/json/
mod.rs

1use super::OnFeature;
2use crate::{
3    geometry::{ConvertVectorFeatureWM, convert},
4    parsers::{FeatureReader, Writer},
5};
6use alloc::{collections::BTreeSet, format, vec::Vec};
7use s2json::{BBox3D, JSONCollection, Projection};
8use serde::Serialize;
9
10/// User defined options on how to store the features
11#[derive(Debug)]
12pub struct ToJSONOptions<M: Clone, P: Clone + Default + Serialize, D: Clone + Default + Serialize> {
13    /// Projection can be S2 or WG
14    pub projection: Option<Projection>,
15    /// Build the bounding box
16    pub build_bbox: Option<bool>,
17    /// Set to true to get a GeoJSON object
18    pub geojson: Option<bool>,
19    /// User defined function on how to store the feature
20    pub on_feature: Option<OnFeature<M, P, D>>,
21}
22impl<M: Clone, P: Clone + Default + Serialize, D: Clone + Default + Serialize> Default
23    for ToJSONOptions<M, P, D>
24{
25    fn default() -> Self {
26        ToJSONOptions { projection: None, build_bbox: None, geojson: None, on_feature: None }
27    }
28}
29
30/// # To JSON
31///
32/// ## Description
33/// Given a writer and an array of readers, write the input features to the writer as a JSON object
34///
35/// ## Parameters
36/// - `writer`: the buffer or file to write to. See [`Writer`] for what writers are available
37/// - `readers`: Any reader that implements the [`FeatureReader`] trait can be used
38/// - `opts`: user defined options on how to write the features
39///
40/// ## Usage
41/// ```rust
42/// use gistools::{readers::JSONReader, parsers::{BufferWriter, FileReader}, writers::{to_json, ToJSONOptions}};
43/// use s2json::{MValueCompatible, Projection};
44/// use serde::{Deserialize, Serialize};
45/// use std::path::PathBuf;
46///
47/// #[derive(Debug, Default, Clone, MValueCompatible, PartialEq, Serialize, Deserialize)]
48/// #[serde(default)]
49/// struct Props {
50///     name: String,
51/// }
52///
53/// let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
54/// path = path.join("tests/writers/fixtures/points.geojson");
55///
56/// let reader: JSONReader<FileReader, (), Props, ()> = JSONReader::new(FileReader::from(path));
57/// let mut writer = BufferWriter::default();
58///
59/// // write
60/// to_json(
61///     &mut writer,
62///     vec![&reader],
63///     Some(ToJSONOptions {
64///         projection: Some(Projection::WG),
65///         geojson: Some(true),
66///         ..Default::default()
67///     }),
68/// );
69/// ```
70pub fn to_json<
71    T: Writer,
72    M: Clone + Serialize,
73    P: Clone + Default + Serialize,
74    D: Clone + Default + Serialize,
75    I: FeatureReader<M, P, D>,
76>(
77    writer: &mut T,
78    readers: Vec<&I>,
79    opts: Option<ToJSONOptions<M, P, D>>,
80) {
81    let opts = opts.unwrap_or_default();
82    let projection = opts.projection.unwrap_or(Projection::S2);
83    let on_feature = opts.on_feature.unwrap_or(Some);
84    let build_bbox = opts.build_bbox.unwrap_or(true);
85    let mut bbox = BBox3D::default();
86    let mut faces: BTreeSet<u8> = BTreeSet::new();
87    let r#type =
88        if projection == Projection::S2 { "S2FeatureCollection" } else { "FeatureCollection" };
89
90    writer.append_string("{\n\t\"type\": \"");
91    writer.append_string(r#type);
92    writer.append_string("\",\n");
93    writer.append_string("\t\"features\": [\n");
94
95    let mut first = true;
96    for reader in readers {
97        for feature in reader.iter() {
98            let converted_features = convert(
99                projection,
100                &JSONCollection::VectorFeature(feature),
101                Some(build_bbox),
102                None,
103            );
104            for converted_feature in converted_features {
105                let user_feature = on_feature(converted_feature);
106                if user_feature.is_none() {
107                    continue;
108                }
109                let user_feature = user_feature.unwrap();
110                faces.insert(user_feature.face.into());
111                if build_bbox && let Some(feature_bbox) = user_feature.geometry.bbox() {
112                    bbox.merge_in_place(feature_bbox);
113                }
114                if !first {
115                    writer.append_string(",\n");
116                } else {
117                    first = false;
118                }
119                let feature_str = match opts.geojson.unwrap_or(false) {
120                    true => serde_json::to_string(&user_feature.to_feature(true)).unwrap(),
121                    false => serde_json::to_string(&user_feature).unwrap(),
122                };
123                writer.append_string("\t\t");
124                writer.append_string(&feature_str);
125            }
126        }
127    }
128
129    writer.append_string("\n\t],");
130    let faces_vec: Vec<&u8> = faces.iter().collect();
131    writer.append_string(&format!("\n\t\"faces\": {faces_vec:?}"));
132    if build_bbox {
133        writer
134            .append_string(&format!(",\n\t\"bbox\": {:?}", &serde_json::to_string(&bbox).unwrap()));
135    }
136    writer.append_string("\n}");
137}
138
139/// # To JSON Line Delimited
140///
141/// Given a writer and an array of readers, write the input features to the writer as JSON-LD
142///
143/// ## Parameters
144/// - `writer`: the buffer or file to write to. See [`Writer`] for what writers are available
145/// - `readers`: Any reader that implements the [`FeatureReader`] trait can be used
146/// - `opts`: user defined options on how to write the features
147///
148/// ## Usage
149/// ```rust
150/// use gistools::{readers::JSONReader, parsers::{BufferWriter, FileReader}, writers::{to_jsonld, ToJSONOptions}};
151/// use s2json::{MValueCompatible, Projection};
152/// use serde::{Deserialize, Serialize};
153/// use std::path::PathBuf;
154///
155/// #[derive(Debug, Default, Clone, MValueCompatible, PartialEq, Serialize, Deserialize)]
156/// #[serde(default)]
157/// struct Props {
158///     name: String,
159/// }
160///
161/// let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
162/// path = path.join("tests/writers/fixtures/points.geojson");
163///
164/// let reader: JSONReader<FileReader, (), Props, ()> = JSONReader::new(FileReader::from(path));
165/// let mut writer = BufferWriter::default();
166///
167/// // write
168/// to_jsonld(
169///     &mut writer,
170///     vec![&reader],
171///     Some(ToJSONOptions {
172///         projection: Some(Projection::WG),
173///         geojson: Some(true),
174///         ..Default::default()
175///     }),
176/// );
177/// ```
178pub fn to_jsonld<
179    T: Writer,
180    M: Clone + Serialize,
181    P: Clone + Default + Serialize,
182    D: Clone + Default + Serialize,
183    I: FeatureReader<M, P, D>,
184>(
185    writer: &mut T,
186    readers: Vec<&I>,
187    opts: Option<ToJSONOptions<M, P, D>>,
188) {
189    let opts = opts.unwrap_or_default();
190    let projection = opts.projection.unwrap_or(Projection::S2);
191    let on_feature = opts.on_feature.unwrap_or(Some);
192    let build_bbox = opts.build_bbox.unwrap_or(true);
193
194    for reader in readers {
195        for feature in reader.iter() {
196            let converted_features = convert(
197                projection,
198                &JSONCollection::VectorFeature(feature),
199                Some(build_bbox),
200                None,
201            );
202            for converted_feature in converted_features {
203                let user_feature = on_feature(converted_feature);
204                if let Some(user_feature) = user_feature {
205                    let feature_str = match opts.geojson.unwrap_or(false) {
206                        true => serde_json::to_string(&user_feature.to_feature(true)).unwrap(),
207                        false => serde_json::to_string(&user_feature).unwrap(),
208                    };
209                    writer.append_string(&feature_str);
210                    writer.append_string("\n");
211                }
212            }
213        }
214    }
215}