cimengine-build-tools/src/types.rs
2024-05-30 12:22:27 +03:00

355 lines
9.1 KiB
Rust

use clap::{Parser, Subcommand};
use geo::{Geometry, MultiPolygon};
use geo::{Point, Polygon};
use geojson::{FeatureCollection, Value};
use serde::{Deserialize, Serialize};
use serde_json::json;
#[derive(Debug, Parser)]
#[command(name = "cimengine", bin_name = "cimengine")]
#[command(about = "CIMEngine build tools")]
pub struct Cli {
#[command(subcommand)]
pub cmd: Commands,
}
#[derive(Debug, Subcommand)]
#[clap(author, version, about)]
pub enum Commands {
/// Build project
Build,
/// Initialize a new project
Init {
#[clap(default_value = "map")]
name: String,
},
/// Fix geospatial file
Fix {
/// Path to geospatial file supported by cimengine
#[clap(short, long)]
path: String,
},
/// Utility for creating countries, roads, etc.
New {
#[command(subcommand)]
cmd: NewCommands,
},
}
#[derive(Debug, Subcommand)]
pub enum NewCommands {
/// Create new country
Country {
id: String,
#[clap(long)]
name: Option<String>,
#[clap(long)]
description: Option<String>,
#[clap(long)]
foundation_date: Option<String>,
#[clap(long)]
flag: Option<String>,
#[clap(long)]
about: Option<String>,
#[clap(long)]
fill: Option<String>,
#[clap(long)]
stroke: Option<String>,
},
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Config {
pub main: MainConfig,
pub processing: Vec<ProcessingConfig>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct MainConfig {
pub layers: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ProcessingConfig {
pub show_markers: Option<bool>,
pub output_folder: String,
pub tags: Option<Vec<String>>,
pub countries_rewrite: Option<Vec<CountryRewriteConfig>>,
pub public: Option<PublicConfig>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct CountryRewriteConfig {
pub tags: Option<Vec<String>>,
pub properties: CountryRewriteConfigProps,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct CountryRewriteConfigProps {
pub name: Option<String>,
pub description: Option<String>,
pub foundation_date: Option<String>,
pub flag: Option<String>,
pub fill: Option<String>,
pub stroke: Option<String>,
pub about: Option<String>,
pub tags: Option<Vec<String>>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct PublicConfig {
pub name: String,
pub description: String,
pub geo: String,
pub countries: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct CountryConfig {
pub name: String,
pub description: String,
pub foundation_date: String,
pub flag: String,
pub fill: String,
pub stroke: String,
pub about: Option<String>,
pub tags: Option<Vec<String>>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct CountryData {
pub id: String,
pub config: CountryConfig,
pub land: MultiPolygon,
pub markers: Vec<Marker>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct CountryPolyProps {
id: String,
r#type: String,
fill: String,
stroke: String,
tags: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Marker {
pub coordinates: Point,
pub title: String,
pub description: String,
pub ty: MarkerType,
}
impl ToFeature for Marker {
fn to_feature(&self) -> geojson::Feature {
geojson::Feature {
geometry: Some(geojson::Geometry::new(Value::Point(vec![
self.coordinates.x(),
self.coordinates.y(),
]))),
properties: Some(
serde_json::Map::from_iter([
("title".to_owned(), json!(self.title)),
("description".to_owned(), json!(self.description)),
("type".to_owned(), json!(self.ty.to_str())),
])
.into(),
),
bbox: None,
id: None,
foreign_members: None,
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum MarkerType {
Capital,
City,
Landmark,
}
impl MarkerType {
pub fn to_str(&self) -> &'static str {
match self {
MarkerType::Capital => "capital",
MarkerType::City => "city",
MarkerType::Landmark => "landmark-0",
}
}
}
pub enum Territory {
Polygon(Polygon),
MultiPolygon(MultiPolygon),
}
impl ToTerritory for MultiPolygon {
fn to_territory(&self) -> Territory {
Territory::MultiPolygon(self.clone())
}
}
impl ToFeature for MultiPolygon {
fn to_feature(&self) -> geojson::Feature {
geojson::Feature {
geometry: Some(geojson::Geometry::new((self).into())),
properties: None,
bbox: None,
id: None,
foreign_members: None,
}
}
}
impl ToFeatures for Vec<Marker> {
fn to_features(&self) -> Vec<geojson::Feature> {
self.iter().map(|m| m.to_feature()).collect()
}
}
impl ToFeatures for CountryData {
fn to_features(&self) -> Vec<geojson::Feature> {
let land = geojson::Feature {
geometry: Some(geojson::Geometry::from(&self.land)),
properties: Some(
serde_json::Map::from_iter([
("id".to_owned(), json!(self.id)),
("type".to_owned(), json!("country")),
("fill".to_owned(), json!(self.config.fill)),
("stroke".to_owned(), json!(self.config.stroke)),
("tags".to_owned(), json!(self.config.tags)),
])
.into(),
),
bbox: None,
id: None,
foreign_members: None,
};
let mut features = vec![land];
features.extend(self.markers.to_features());
features
}
}
impl ToCollection for Vec<CountryData> {
fn to_collection(self) -> geojson::FeatureCollection {
geojson::FeatureCollection {
features: self.to_features(),
bbox: None,
foreign_members: None,
}
}
}
impl ToFeatures for Vec<CountryData> {
fn to_features(&self) -> Vec<geojson::Feature> {
self.iter().flat_map(|c| c.to_features()).collect()
}
}
impl ToCollection for Vec<geojson::Feature> {
fn to_collection(self) -> geojson::FeatureCollection {
geojson::FeatureCollection {
features: self,
bbox: None,
foreign_members: None,
}
}
}
impl ToSplitGeo for FeatureCollection {
fn split_geo(&self) -> (Vec<Marker>, Vec<Territory>) {
let mut markers: Vec<Marker> = vec![];
let mut territories: Vec<Territory> = vec![];
self.features.iter().for_each(|f| {
let properties = f.properties.clone().unwrap();
let geometry: Geometry = f.geometry.clone().unwrap().try_into().unwrap();
match geometry {
Geometry::Point(p) => {
let ty = match properties
.get("type")
.expect("Missing marker type")
.to_string()
.trim_matches('"')
{
"capital" | "capital-city" => MarkerType::Capital,
"city" => MarkerType::City,
"landmark" => MarkerType::Landmark,
t => panic!("Invalid marker type: {}", t),
};
markers.push(Marker {
coordinates: p,
title: properties
.get("title")
.expect("Missing marker title")
.to_string(),
description: properties
.get("description")
.unwrap_or(&json!(""))
.to_string(),
ty,
})
}
Geometry::MultiPolygon(mp) => territories.push(Territory::MultiPolygon(mp)),
Geometry::Polygon(p) => territories.push(Territory::Polygon(p)),
_ => panic!("Unexpected geometry type"),
}
});
(markers, territories)
}
}
impl ToMultiPolygon for Polygon {
fn to_mp(&self) -> MultiPolygon {
MultiPolygon::new(vec![self.clone()])
}
}
pub trait ToFeature {
fn to_feature(&self) -> geojson::Feature;
}
pub trait ToFeatures {
fn to_features(&self) -> Vec<geojson::Feature>;
}
pub trait ToCountryFeature {
fn to_country_feature(&self, id: &String, config: &CountryConfig) -> geojson::Feature;
}
pub trait ToCollection {
fn to_collection(self) -> geojson::FeatureCollection;
}
pub trait ToSplitGeo {
fn split_geo(&self) -> (Vec<Marker>, Vec<Territory>);
}
pub trait ToTerritory {
fn to_territory(&self) -> Territory;
}
pub trait UnsplitGeo {
fn unsplit_geo(self) -> FeatureCollection;
}
pub trait ToMultiPolygon {
fn to_mp(&self) -> MultiPolygon;
}