refactor: traits

This commit is contained in:
Artemy 2024-05-23 12:47:22 +03:00
parent 1ab51d33d3
commit 2da4604a67
2 changed files with 159 additions and 102 deletions

View file

@ -1,5 +1,6 @@
use clap::{Parser, Subcommand};
use geo::{BoundingRect, Geometry, MultiPolygon, Point, Polygon};
use geo::{Geometry, MultiPolygon};
use geo::{Point, Polygon};
use geojson::{Feature, FeatureCollection, Value};
use serde::{Deserialize, Serialize};
use serde_json::json;
@ -127,8 +128,8 @@ pub struct Marker {
pub ty: MarkerType,
}
impl Marker {
pub fn to_feature(&self) -> geojson::Feature {
impl ToFeature for Marker {
fn to_feature(&self) -> geojson::Feature {
geojson::Feature {
geometry: Some(geojson::Geometry::new(Value::Point(vec![
self.coordinates.x(),
@ -171,25 +172,146 @@ pub enum Territory {
MultiPolygon(MultiPolygon),
}
impl Territory {
pub fn to_feature(&self) -> geojson::Feature {
match self {
Territory::Polygon(p) => geojson::Feature {
geometry: Some(geojson::Geometry::new(p.into())),
properties: None,
impl ToTerritory for MultiPolygon {
fn to_territory(&self) -> Territory {
Territory::MultiPolygon(self.clone())
}
}
bbox: None,
id: None,
foreign_members: None,
},
Territory::MultiPolygon(mp) => geojson::Feature {
geometry: Some(geojson::Geometry::new(mp.into())),
properties: None,
impl ToCountryFeature for MultiPolygon {
fn to_country_feature(&self, id: &String, config: &CountryConfig) -> geojson::Feature {
geojson::Feature {
geometry: Some(geojson::Geometry::new((self).into())),
properties: Some(
serde_json::Map::from_iter([
("id".to_owned(), json!(id)),
("type".to_owned(), json!("country")),
("fill".to_owned(), json!(config.fill)),
("stroke".to_owned(), json!(config.stroke)),
("tags".to_owned(), json!(config.tags)),
])
.into(),
),
bbox: None,
id: None,
foreign_members: 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 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 UnsplitGeo for (Vec<Marker>, Feature) {
fn unsplit_geo(self) -> FeatureCollection {
let (markers, territories) = self;
let mut features: Vec<geojson::Feature> = markers.to_features();
features.push(territories);
features.to_collection()
}
}
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;
}

View file

@ -1,10 +1,12 @@
use std::{collections::HashMap, fs, path::Path};
use std::{fs, path::Path};
use geo::{BooleanOps, Geometry, MultiPolygon};
use geojson::{Feature, FeatureCollection, GeoJson};
use serde_json::json;
use geo::{BooleanOps, MultiPolygon};
use geojson::{FeatureCollection, GeoJson};
use crate::types::{Config, CountryConfig, CountryData, Marker, MarkerType, Territory};
use crate::types::{
Config, CountryConfig, CountryData, Territory, ToCountryFeature, ToMultiPolygon, ToSplitGeo,
UnsplitGeo,
};
pub fn read_config() -> Config {
let c = toml::from_str::<Config>(&fs::read_to_string("config.toml").unwrap());
@ -47,88 +49,21 @@ pub fn dissolve_territory(
id: String,
config: CountryConfig,
) -> FeatureCollection {
let mut markers: Vec<Marker> = vec![];
let mut territories: Vec<Territory> = vec![];
let (markers, territories) = geo.split_geo();
geo.features.iter().for_each(|f| {
let properties = f.properties.clone().unwrap();
let dissolved = territories
.iter()
.fold(MultiPolygon::new(vec![]), |a, b| match b {
Territory::Polygon(p) => a.union(&p.to_mp()),
Territory::MultiPolygon(mp) => a.union(mp),
})
.to_country_feature(&id, &config);
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"),
}
});
let territories = territories.iter();
let dissolved = territories.fold(MultiPolygon::new(vec![]), |a, b| match b {
Territory::Polygon(p) => a.union(&MultiPolygon::new(vec![p.clone()])),
Territory::MultiPolygon(mp) => a.union(mp),
});
// combine markers and dissolved
let mut features: Vec<Feature> = markers.iter().map(|m| m.to_feature()).collect();
features.push(geojson::Feature {
geometry: Some(geojson::Geometry::new((&dissolved).into())),
properties: Some(
serde_json::Map::from_iter([
("id".to_owned(), json!(id)),
("type".to_owned(), json!("country")),
("fill".to_owned(), json!(config.fill)),
("stroke".to_owned(), json!(config.stroke)),
("tags".to_owned(), json!(config.tags)),
])
.into(),
),
bbox: None,
id: None,
foreign_members: None,
});
FeatureCollection {
features,
bbox: None,
foreign_members: None,
}
(markers, dissolved).unsplit_geo()
}
pub fn hash_hex_color(s: String) -> String {
let hex_str = format!("{:x}", xxhash_rust::xxh3::xxh3_64(s.as_bytes()));
format!("#{:6}", hex_str.chars().take(6).collect::<String>())
format!("#{}", hex_str.chars().take(6).collect::<String>())
}