use crate::finder::Item;
use crate::tzb::{ExpandedPolygon, TileId};
use serde::{Deserialize, Serialize};
pub type PolygonCoordinates = Vec<Vec<[f64; 2]>>;
pub type MultiPolygonCoordinates = Vec<PolygonCoordinates>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GeometryDefine {
#[serde(rename = "type")]
pub geometry_type: String,
pub coordinates: MultiPolygonCoordinates,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PropertiesDefine {
pub tzid: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeatureItem {
#[serde(rename = "type")]
pub feature_type: String,
pub properties: PropertiesDefine,
pub geometry: GeometryDefine,
}
impl FeatureItem {
#[allow(clippy::inherent_to_string)]
#[must_use]
pub fn to_string(&self) -> String {
serde_json::to_string(self).unwrap_or_default()
}
#[must_use]
pub fn to_string_pretty(&self) -> String {
serde_json::to_string_pretty(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BoundaryFile {
#[serde(rename = "type")]
pub collection_type: String,
pub features: Vec<FeatureItem>,
}
impl BoundaryFile {
#[allow(clippy::inherent_to_string)]
#[must_use]
pub fn to_string(&self) -> String {
serde_json::to_string(self).unwrap_or_default()
}
#[must_use]
pub fn to_string_pretty(&self) -> String {
serde_json::to_string_pretty(self).unwrap_or_default()
}
}
pub(crate) fn collection(features: Vec<FeatureItem>) -> BoundaryFile {
BoundaryFile {
collection_type: "FeatureCollection".to_string(),
features,
}
}
fn scaled_coord(v: i32) -> f64 {
#[allow(clippy::cast_possible_truncation)]
let narrowed = (f64::from(v) / 1e5) as f32;
f64::from(narrowed)
}
fn ring_coords<'a>(points: impl Iterator<Item = &'a geometry_rs::Point<i32>>) -> Vec<[f64; 2]> {
points
.map(|p| [scaled_coord(p.x), scaled_coord(p.y)])
.collect()
}
fn ring_coords_closed<'a>(
points: impl Iterator<Item = &'a geometry_rs::Point<i32>>,
) -> Vec<[f64; 2]> {
let mut coords = ring_coords(points);
if let Some(&first) = coords.first() {
coords.push(first);
}
coords
}
pub(crate) fn feature_from_item(item: &Item) -> FeatureItem {
let coordinates = item
.polys
.iter()
.map(|poly| {
let mut rings = PolygonCoordinates::new();
rings.push(ring_coords(poly.exterior().iter()));
for hole in poly.holes() {
rings.push(ring_coords(hole.iter()));
}
rings
})
.collect();
feature(item.name.clone(), coordinates)
}
pub(crate) fn feature_from_expanded(name: String, polys: &[ExpandedPolygon]) -> FeatureItem {
let coordinates = polys
.iter()
.map(|poly| {
let mut rings = PolygonCoordinates::new();
rings.push(ring_coords_closed(poly.exterior.iter()));
for hole in &poly.holes {
rings.push(ring_coords_closed(hole.iter()));
}
rings
})
.collect();
feature(name, coordinates)
}
pub(crate) fn feature_from_tile_keys(name: String, keys: &[u64]) -> FeatureItem {
let coordinates = keys
.iter()
.map(|&key| vec![TileId(key).polygon()])
.collect();
feature(name, coordinates)
}
fn feature(name: String, coordinates: MultiPolygonCoordinates) -> FeatureItem {
FeatureItem {
feature_type: "Feature".to_string(),
properties: PropertiesDefine { tzid: name },
geometry: GeometryDefine {
geometry_type: "MultiPolygon".to_string(),
coordinates,
},
}
}