mirror of
https://github.com/CIMEngine/cimengine-build-tools.git
synced 2024-11-21 19:56:22 +03:00
feat: dissolve territory
This commit is contained in:
parent
b1f5e42a46
commit
1ab51d33d3
10 changed files with 347 additions and 48 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -116,6 +116,7 @@ dependencies = [
|
||||||
"toml",
|
"toml",
|
||||||
"toml_edit",
|
"toml_edit",
|
||||||
"wax",
|
"wax",
|
||||||
|
"xxhash-rust",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -736,6 +737,12 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xxhash-rust"
|
||||||
|
version = "0.8.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "927da81e25be1e1a2901d59b81b37dd2efd1fc9c9345a55007f09bf5a2d3ee03"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.7.32"
|
version = "0.7.32"
|
||||||
|
|
|
@ -15,6 +15,8 @@ toml = "0.8.12"
|
||||||
toml_edit = "0.22.12"
|
toml_edit = "0.22.12"
|
||||||
wax = "0.6.0"
|
wax = "0.6.0"
|
||||||
|
|
||||||
|
xxhash-rust = { version = "0.8.10", features = ["xxh3"] }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "cimengine"
|
name = "cimengine"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
60
src/build.rs
Normal file
60
src/build.rs
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
use std::{fs, path::Path};
|
||||||
|
|
||||||
|
use geojson::{Feature, FeatureCollection};
|
||||||
|
use serde_json::json;
|
||||||
|
use wax::Glob;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
types::CountryData,
|
||||||
|
utils::{get_country, read_config},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn build() {
|
||||||
|
let config = read_config();
|
||||||
|
|
||||||
|
for processing_item in config.processing {
|
||||||
|
let mut features: Vec<Feature> = vec![];
|
||||||
|
|
||||||
|
let out_folder = Path::new(&processing_item.output_folder);
|
||||||
|
|
||||||
|
let mut countries: Vec<CountryData> = vec![];
|
||||||
|
|
||||||
|
for country_id in &config.main.layers {
|
||||||
|
let mut country = get_country(country_id.to_owned());
|
||||||
|
|
||||||
|
// TODO: Add tags support
|
||||||
|
|
||||||
|
features.append(&mut country.geo.features);
|
||||||
|
countries.push(country);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Add country_rewrite support
|
||||||
|
// TODO: let countries = vec![rewrite_info];
|
||||||
|
|
||||||
|
// TODO: Add nature support
|
||||||
|
|
||||||
|
let feature_collection = FeatureCollection {
|
||||||
|
bbox: None,
|
||||||
|
features,
|
||||||
|
foreign_members: None,
|
||||||
|
}
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let countries = serde_json::to_string(&serde_json::Map::from_iter(
|
||||||
|
countries
|
||||||
|
.iter()
|
||||||
|
.map(|country| (country.id.clone(), json!(country.config))),
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
fs::create_dir_all(out_folder).unwrap();
|
||||||
|
|
||||||
|
fs::write(out_folder.join("geo.geojson"), feature_collection).unwrap();
|
||||||
|
fs::write(out_folder.join("countries.json"), countries).unwrap();
|
||||||
|
|
||||||
|
if let Some(public) = processing_item.public {
|
||||||
|
let public = serde_json::to_string(&public).unwrap();
|
||||||
|
fs::write(out_folder.join("public.json"), public).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +0,0 @@
|
||||||
pub fn build() {}
|
|
|
@ -4,6 +4,7 @@ mod build;
|
||||||
mod init;
|
mod init;
|
||||||
mod new;
|
mod new;
|
||||||
mod types;
|
mod types;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
use types::Commands;
|
use types::Commands;
|
||||||
|
|
||||||
|
|
29
src/new.rs
29
src/new.rs
|
@ -1,7 +1,10 @@
|
||||||
use std::{fs, path::Path};
|
use std::{fs, path::Path};
|
||||||
use toml_edit::{value, DocumentMut, Value};
|
use toml_edit::{value, DocumentMut, Value};
|
||||||
|
|
||||||
use crate::types::{Config, Country, NewCommands};
|
use crate::{
|
||||||
|
types::{CountryConfig, NewCommands},
|
||||||
|
utils::{hash_hex_color, read_config},
|
||||||
|
};
|
||||||
|
|
||||||
pub fn new(cmd: NewCommands) {
|
pub fn new(cmd: NewCommands) {
|
||||||
match cmd {
|
match cmd {
|
||||||
|
@ -12,39 +15,37 @@ pub fn new(cmd: NewCommands) {
|
||||||
foundation_date,
|
foundation_date,
|
||||||
flag,
|
flag,
|
||||||
about,
|
about,
|
||||||
|
fill,
|
||||||
|
stroke,
|
||||||
} => {
|
} => {
|
||||||
let name = name.unwrap_or_default();
|
let name = name.unwrap_or_default();
|
||||||
let description = description.unwrap_or_default();
|
let description = description.unwrap_or_default();
|
||||||
let foundation_date = foundation_date.unwrap_or_default();
|
let foundation_date = foundation_date.unwrap_or_default();
|
||||||
let flag = flag.unwrap_or_default();
|
let flag = flag.unwrap_or_default();
|
||||||
let about = about;
|
let fill = fill.unwrap_or_else(|| hash_hex_color(id.clone() + "_fill"));
|
||||||
|
let stroke = stroke.unwrap_or_else(|| hash_hex_color(id.clone() + "_stroke"));
|
||||||
|
|
||||||
let country = Country {
|
let country = CountryConfig {
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
description,
|
description,
|
||||||
foundation_date,
|
foundation_date,
|
||||||
flag,
|
flag,
|
||||||
about,
|
about,
|
||||||
tags: Some(vec![]),
|
fill,
|
||||||
|
stroke,
|
||||||
|
tags: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let config = fs::read_to_string("config.toml").unwrap();
|
let config = fs::read_to_string("config.toml").unwrap();
|
||||||
|
|
||||||
// Validate config
|
// Validate config
|
||||||
let c = toml::from_str::<Config>(&config);
|
read_config();
|
||||||
|
|
||||||
match c {
|
|
||||||
Ok(c) => c,
|
|
||||||
Err(err) => {
|
|
||||||
panic!("Invalid config: {}", err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get actual config
|
// Get actual config
|
||||||
let mut config = config.parse::<DocumentMut>().unwrap();
|
let mut config = config.parse::<DocumentMut>().unwrap();
|
||||||
|
|
||||||
// Add country to layers
|
// Add country to layers
|
||||||
let layers = config["country"]["layers"].clone().into_value().unwrap();
|
let layers = config["main"]["layers"].clone().into_value().unwrap();
|
||||||
|
|
||||||
let layers = if let Value::Array(mut layers) = layers {
|
let layers = if let Value::Array(mut layers) = layers {
|
||||||
layers.push(&id);
|
layers.push(&id);
|
||||||
|
@ -53,7 +54,7 @@ pub fn new(cmd: NewCommands) {
|
||||||
panic!("layers is not an array");
|
panic!("layers is not an array");
|
||||||
};
|
};
|
||||||
|
|
||||||
config["country"]["layers"] = value(layers);
|
config["main"]["layers"] = value(layers);
|
||||||
|
|
||||||
fs::write("config.toml", config.to_string()).unwrap();
|
fs::write("config.toml", config.to_string()).unwrap();
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,16 @@
|
||||||
[country]
|
[main]
|
||||||
# Order matters when building countries. This affects the processing of area intersections
|
# Order matters when building countries. This affects the processing of area intersections
|
||||||
layers = ["sample_country_id"]
|
layers = ["sample_country_id"]
|
||||||
|
|
||||||
[[processing]]
|
[[processing]]
|
||||||
output_folder = "out"
|
output_folder = "./out/map"
|
||||||
countries_file = "countries.json"
|
|
||||||
geo_file = "geo.geojson"
|
|
||||||
|
|
||||||
# generate_colors = false
|
|
||||||
# show_markers = false
|
# show_markers = false
|
||||||
|
|
||||||
# Information for public repository in cimengine. See: https://github.com/CIMEngine/MapList
|
# Information for public repository in cimengine. See: https://github.com/CIMEngine/MapList
|
||||||
# [processing.public]
|
# [processing.public]
|
||||||
# name = "Sample Map"
|
# name = "Sample Map"
|
||||||
|
# description = "This is a sample map"
|
||||||
# geo = "https://example.com/geo.geojson"
|
# geo = "https://example.com/geo.geojson"
|
||||||
# countries = "https://example.com/countries.json"
|
# countries = "https://example.com/countries.json"
|
||||||
|
|
||||||
|
@ -22,9 +20,14 @@ geo_file = "geo.geojson"
|
||||||
# include = [] # [] = not filtered
|
# include = [] # [] = not filtered
|
||||||
# exclude = ["test", "test2"]
|
# exclude = ["test", "test2"]
|
||||||
|
|
||||||
|
# Rewrite properties of all countries. All fields are optional.
|
||||||
# [processing.countries_rewrite]
|
# [processing.countries_rewrite]
|
||||||
# name = "name"
|
# name = "name"
|
||||||
# color = "#000000"
|
# color = "#000000"
|
||||||
|
# description = "description"
|
||||||
|
# foundation_date = "2024-01-01"
|
||||||
|
# flag = "https://example.com/flag.png"
|
||||||
|
# about = "https://example.com/about.html"
|
||||||
|
|
||||||
# Nature layers
|
# Nature layers
|
||||||
[[nature]]
|
[[nature]]
|
||||||
|
|
|
@ -2,4 +2,8 @@ name = "Sample Country"
|
||||||
description = "This is a sample country"
|
description = "This is a sample country"
|
||||||
foundation_date = "2024-01-01"
|
foundation_date = "2024-01-01"
|
||||||
flag = "https://example.com/flag.png"
|
flag = "https://example.com/flag.png"
|
||||||
about = "https://example.com/about.html"
|
fill = "#000000"
|
||||||
|
stroke = "#000000"
|
||||||
|
|
||||||
|
# about = "https://example.com/about.html"
|
||||||
|
# tags = ["test", "test2"]
|
||||||
|
|
142
src/types.rs
142
src/types.rs
|
@ -1,5 +1,8 @@
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
|
use geo::{BoundingRect, Geometry, MultiPolygon, Point, Polygon};
|
||||||
|
use geojson::{Feature, FeatureCollection, Value};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
#[command(name = "cimengine", bin_name = "cimengine")]
|
#[command(name = "cimengine", bin_name = "cimengine")]
|
||||||
|
@ -37,71 +40,156 @@ pub enum NewCommands {
|
||||||
/// Create new country
|
/// Create new country
|
||||||
Country {
|
Country {
|
||||||
id: String,
|
id: String,
|
||||||
#[clap(short, long)]
|
#[clap(long)]
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
#[clap(short, long)]
|
#[clap(long)]
|
||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
#[clap(short, long)]
|
#[clap(long)]
|
||||||
foundation_date: Option<String>,
|
foundation_date: Option<String>,
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
flag: Option<String>,
|
flag: Option<String>,
|
||||||
#[clap(short, long)]
|
#[clap(long)]
|
||||||
about: Option<String>,
|
about: Option<String>,
|
||||||
|
#[clap(long)]
|
||||||
|
fill: Option<String>,
|
||||||
|
#[clap(long)]
|
||||||
|
stroke: Option<String>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
country: CountryConfig,
|
pub main: MainConfig,
|
||||||
processing: Vec<ProcessingConfig>,
|
pub processing: Vec<ProcessingConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct CountryConfig {
|
pub struct MainConfig {
|
||||||
layers: Vec<String>,
|
pub layers: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct ProcessingConfig {
|
pub struct ProcessingConfig {
|
||||||
generate_colors: Option<bool>,
|
pub show_markers: Option<bool>,
|
||||||
show_markers: Option<bool>,
|
pub output_folder: String,
|
||||||
output_folder: String,
|
|
||||||
countries_file: String,
|
|
||||||
geo_file: String,
|
|
||||||
|
|
||||||
tags: Option<ProcessingTagsConfig>,
|
pub tags: Option<ProcessingTagsConfig>,
|
||||||
countries_rewrite: Option<CountryRewriteConfig>,
|
pub countries_rewrite: Option<CountryRewriteConfig>,
|
||||||
public: Option<PublicConfig>,
|
pub public: Option<PublicConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct ProcessingTagsConfig {
|
pub struct ProcessingTagsConfig {
|
||||||
include: Option<Vec<String>>,
|
pub include: Option<Vec<String>>,
|
||||||
exclude: Option<Vec<String>>,
|
pub exclude: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct CountryRewriteConfig {
|
pub struct CountryRewriteConfig {
|
||||||
name: Option<String>,
|
pub name: Option<String>,
|
||||||
description: Option<String>,
|
pub description: Option<String>,
|
||||||
foundation_date: Option<String>,
|
pub foundation_date: Option<String>,
|
||||||
flag: Option<String>,
|
pub flag: Option<String>,
|
||||||
about: Option<String>,
|
pub about: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct PublicConfig {
|
pub struct PublicConfig {
|
||||||
name: String,
|
pub name: String,
|
||||||
geo: String,
|
pub description: String,
|
||||||
countries: String,
|
pub geo: String,
|
||||||
|
pub countries: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct Country {
|
pub struct CountryConfig {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub foundation_date: String,
|
pub foundation_date: String,
|
||||||
pub flag: String,
|
pub flag: String,
|
||||||
|
pub fill: String,
|
||||||
|
pub stroke: String,
|
||||||
pub about: Option<String>,
|
pub about: Option<String>,
|
||||||
pub tags: Option<Vec<String>>,
|
pub tags: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct CountryData {
|
||||||
|
pub id: String,
|
||||||
|
pub config: CountryConfig,
|
||||||
|
pub geo: FeatureCollection,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Marker {
|
||||||
|
pub coordinates: Point,
|
||||||
|
pub title: String,
|
||||||
|
pub description: String,
|
||||||
|
pub ty: MarkerType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Marker {
|
||||||
|
pub 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)),
|
||||||
|
("marker-type".to_owned(), json!(self.ty.to_str())),
|
||||||
|
])
|
||||||
|
.into(),
|
||||||
|
),
|
||||||
|
|
||||||
|
bbox: None,
|
||||||
|
id: None,
|
||||||
|
foreign_members: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Territory {
|
||||||
|
pub fn to_feature(&self) -> geojson::Feature {
|
||||||
|
match self {
|
||||||
|
Territory::Polygon(p) => geojson::Feature {
|
||||||
|
geometry: Some(geojson::Geometry::new(p.into())),
|
||||||
|
properties: None,
|
||||||
|
|
||||||
|
bbox: None,
|
||||||
|
id: None,
|
||||||
|
foreign_members: None,
|
||||||
|
},
|
||||||
|
Territory::MultiPolygon(mp) => geojson::Feature {
|
||||||
|
geometry: Some(geojson::Geometry::new(mp.into())),
|
||||||
|
properties: None,
|
||||||
|
|
||||||
|
bbox: None,
|
||||||
|
id: None,
|
||||||
|
foreign_members: None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
134
src/utils.rs
Normal file
134
src/utils.rs
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
use std::{collections::HashMap, fs, path::Path};
|
||||||
|
|
||||||
|
use geo::{BooleanOps, Geometry, MultiPolygon};
|
||||||
|
use geojson::{Feature, FeatureCollection, GeoJson};
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
use crate::types::{Config, CountryConfig, CountryData, Marker, MarkerType, Territory};
|
||||||
|
|
||||||
|
pub fn read_config() -> Config {
|
||||||
|
let c = toml::from_str::<Config>(&fs::read_to_string("config.toml").unwrap());
|
||||||
|
|
||||||
|
match c {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(err) => panic!("Invalid config: {}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_country(id: String) -> CountryData {
|
||||||
|
let country_folder = Path::new(".").join("countries").join(&id);
|
||||||
|
|
||||||
|
let config = toml::from_str::<CountryConfig>(
|
||||||
|
&fs::read_to_string(country_folder.join("country.toml")).unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let config = match config {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(err) => panic!("Invalid config: {}", err),
|
||||||
|
};
|
||||||
|
|
||||||
|
let geo_str = fs::read_to_string(country_folder.join("country.geojson")).unwrap();
|
||||||
|
let geo: GeoJson = geo_str.parse().unwrap();
|
||||||
|
|
||||||
|
let geo = match geo {
|
||||||
|
GeoJson::FeatureCollection(coll) => coll,
|
||||||
|
_ => panic!("Invalid geojson, expected FeatureCollection"),
|
||||||
|
};
|
||||||
|
|
||||||
|
CountryData {
|
||||||
|
id: id.clone(),
|
||||||
|
config: config.clone(),
|
||||||
|
geo: dissolve_territory(geo, id, config),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dissolve_territory(
|
||||||
|
geo: FeatureCollection,
|
||||||
|
id: String,
|
||||||
|
config: CountryConfig,
|
||||||
|
) -> FeatureCollection {
|
||||||
|
let mut markers: Vec<Marker> = vec![];
|
||||||
|
let mut territories: Vec<Territory> = vec![];
|
||||||
|
|
||||||
|
geo.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"),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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>())
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue