1use crate::finder::Item;
8use crate::tzb::{ExpandedPolygon, TileId};
9use serde::{Deserialize, Serialize};
10
11pub type PolygonCoordinates = Vec<Vec<[f64; 2]>>;
12pub type MultiPolygonCoordinates = Vec<PolygonCoordinates>;
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct GeometryDefine {
16 #[serde(rename = "type")]
17 pub geometry_type: String,
18 pub coordinates: MultiPolygonCoordinates,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct PropertiesDefine {
23 pub tzid: String,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct FeatureItem {
28 #[serde(rename = "type")]
29 pub feature_type: String,
30 pub properties: PropertiesDefine,
31 pub geometry: GeometryDefine,
32}
33
34impl FeatureItem {
35 #[allow(clippy::inherent_to_string)]
38 #[must_use]
39 pub fn to_string(&self) -> String {
40 serde_json::to_string(self).unwrap_or_default()
41 }
42
43 #[must_use]
44 pub fn to_string_pretty(&self) -> String {
45 serde_json::to_string_pretty(self).unwrap_or_default()
46 }
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct BoundaryFile {
51 #[serde(rename = "type")]
52 pub collection_type: String,
53 pub features: Vec<FeatureItem>,
54}
55
56impl BoundaryFile {
57 #[allow(clippy::inherent_to_string)]
60 #[must_use]
61 pub fn to_string(&self) -> String {
62 serde_json::to_string(self).unwrap_or_default()
63 }
64
65 #[must_use]
66 pub fn to_string_pretty(&self) -> String {
67 serde_json::to_string_pretty(self).unwrap_or_default()
68 }
69}
70
71pub(crate) fn collection(features: Vec<FeatureItem>) -> BoundaryFile {
72 BoundaryFile {
73 collection_type: "FeatureCollection".to_string(),
74 features,
75 }
76}
77
78fn scaled_coord(v: i32) -> f64 {
79 #[allow(clippy::cast_possible_truncation)]
80 let narrowed = (f64::from(v) / 1e5) as f32;
81 f64::from(narrowed)
82}
83
84fn ring_coords<'a>(points: impl Iterator<Item = &'a geometry_rs::Point<i32>>) -> Vec<[f64; 2]> {
85 points
86 .map(|p| [scaled_coord(p.x), scaled_coord(p.y)])
87 .collect()
88}
89
90fn ring_coords_closed<'a>(
94 points: impl Iterator<Item = &'a geometry_rs::Point<i32>>,
95) -> Vec<[f64; 2]> {
96 let mut coords = ring_coords(points);
97 if let Some(&first) = coords.first() {
98 coords.push(first);
99 }
100 coords
101}
102
103pub(crate) fn feature_from_item(item: &Item) -> FeatureItem {
105 let coordinates = item
106 .polys
107 .iter()
108 .map(|poly| {
109 let mut rings = PolygonCoordinates::new();
110 rings.push(ring_coords(poly.exterior().iter()));
111 for hole in poly.holes() {
112 rings.push(ring_coords(hole.iter()));
113 }
114 rings
115 })
116 .collect();
117 feature(item.name.clone(), coordinates)
118}
119
120pub(crate) fn feature_from_expanded(name: String, polys: &[ExpandedPolygon]) -> FeatureItem {
122 let coordinates = polys
123 .iter()
124 .map(|poly| {
125 let mut rings = PolygonCoordinates::new();
126 rings.push(ring_coords_closed(poly.exterior.iter()));
127 for hole in &poly.holes {
128 rings.push(ring_coords_closed(hole.iter()));
129 }
130 rings
131 })
132 .collect();
133 feature(name, coordinates)
134}
135
136pub(crate) fn feature_from_tile_keys(name: String, keys: &[u64]) -> FeatureItem {
139 let coordinates = keys
140 .iter()
141 .map(|&key| vec![TileId(key).polygon()])
142 .collect();
143 feature(name, coordinates)
144}
145
146fn feature(name: String, coordinates: MultiPolygonCoordinates) -> FeatureItem {
147 FeatureItem {
148 feature_type: "Feature".to_string(),
149 properties: PropertiesDefine { tzid: name },
150 geometry: GeometryDefine {
151 geometry_type: "MultiPolygon".to_string(),
152 coordinates,
153 },
154 }
155}