use std::collections::{BTreeMap, HashMap};
use martin_tile_utils::{Encoding, Format, TileInfo};
use serde::{Deserialize, Serialize};
use tilejson::{Bounds, TileJSON, VectorLayer};
use tracing::{info, warn};
use super::PostgresInfo;
use crate::config::file::postgres::utils::{normalize_key, patch_json};
use crate::config::file::{CachePolicy, UnrecognizedValues};
pub type TableInfoSources = BTreeMap<String, TableInfo>;
#[cfg(feature = "unstable-schemas")]
pub(crate) fn bounds_world_example() -> [f64; 4] {
[-180.0, -90.0, 180.0, 90.0]
}
#[serde_with::skip_serializing_none]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
#[cfg_attr(feature = "unstable-schemas", derive(schemars::JsonSchema))]
pub struct TableInfo {
#[cfg_attr(feature = "unstable-schemas", schemars(example = &"table_source"))]
pub layer_id: Option<String>,
#[cfg_attr(feature = "unstable-schemas", schemars(example = &"public"))]
pub schema: String,
#[cfg_attr(feature = "unstable-schemas", schemars(example = &"table_source"))]
pub table: String,
#[cfg_attr(feature = "unstable-schemas", schemars(example = &4326i32))]
pub srid: i32,
#[cfg_attr(feature = "unstable-schemas", schemars(example = &"geom"))]
pub geometry_column: String,
#[serde(skip)]
pub geometry_index: Option<bool>,
#[serde(skip)]
pub relkind: Option<char>,
pub id_column: Option<String>,
#[cfg_attr(feature = "unstable-schemas", schemars(example = &0u8))]
pub minzoom: Option<u8>,
#[cfg_attr(feature = "unstable-schemas", schemars(example = &30u8))]
pub maxzoom: Option<u8>,
#[cfg_attr(feature = "unstable-schemas", schemars(with = "Option<[f64; 4]>"))]
#[cfg_attr(
feature = "unstable-schemas",
schemars(example = bounds_world_example())
)]
pub bounds: Option<Bounds>,
#[cfg_attr(feature = "unstable-schemas", schemars(example = &4096u32))]
pub extent: Option<u32>,
#[cfg_attr(feature = "unstable-schemas", schemars(example = &64u32))]
pub buffer: Option<u32>,
#[cfg_attr(feature = "unstable-schemas", schemars(example = &true))]
pub clip_geom: Option<bool>,
#[cfg_attr(feature = "unstable-schemas", schemars(example = &"GEOMETRY"))]
pub geometry_type: Option<String>,
#[cfg_attr(
feature = "unstable-schemas",
schemars(with = "Option<crate::config::file::CachePolicyShape>")
)]
pub cache: Option<CachePolicy>,
pub properties: Option<BTreeMap<String, String>>,
#[serde(skip)]
#[cfg_attr(feature = "unstable-schemas", schemars(skip))]
pub prop_mapping: HashMap<String, String>,
#[serde(flatten, skip_serializing)]
#[cfg_attr(feature = "unstable-schemas", schemars(skip))]
pub unrecognized: UnrecognizedValues,
#[serde(skip)]
#[cfg_attr(feature = "unstable-schemas", schemars(skip))]
pub tilejson: Option<serde_json::Value>,
}
impl PostgresInfo for TableInfo {
fn format_id(&self) -> String {
format!("{}.{}.{}", self.schema, self.table, self.geometry_column)
}
fn to_tilejson(&self, source_id: String) -> TileJSON {
let mut tilejson = tilejson::tilejson! {
tiles: vec![], name: source_id.clone(),
description: self.format_id(),
};
tilejson.minzoom = self.minzoom;
tilejson.maxzoom = self.maxzoom;
tilejson.bounds = self.bounds;
let id = if let Some(id) = &self.layer_id {
id.clone()
} else {
source_id
};
let layer = VectorLayer {
id,
fields: self.properties.clone().unwrap_or_default(),
description: None,
maxzoom: None,
minzoom: None,
other: BTreeMap::default(),
};
tilejson.vector_layers = Some(vec![layer]);
patch_json(tilejson, self.tilejson.as_ref())
}
fn tile_info(&self) -> TileInfo {
TileInfo::new(Format::Mvt, Encoding::Uncompressed)
}
}
impl TableInfo {
#[must_use]
pub fn append_cfg_info(
&self,
cfg_inf: &Self,
new_id: &String,
default_srid: Option<i32>,
) -> Option<Self> {
let mut inf = Self {
schema: self.schema.clone(),
table: self.table.clone(),
geometry_column: self.geometry_column.clone(),
geometry_index: self.geometry_index,
relkind: self.relkind,
tilejson: self.tilejson.clone(),
srid: self.calc_srid(new_id, cfg_inf.srid, default_srid)?,
prop_mapping: HashMap::new(),
..cfg_inf.clone()
};
match (&self.geometry_type, &cfg_inf.geometry_type) {
(Some(src), Some(cfg)) if src != cfg => {
warn!(
r"Table {} has geometry type={src}, but source {new_id} has {cfg}",
self.format_id()
);
}
_ => {}
}
let empty = BTreeMap::new();
let props = self.properties.as_ref().unwrap_or(&empty);
if let Some(id_column) = &cfg_inf.id_column {
let prop = normalize_key(props, id_column.as_str(), "id_column", new_id)?;
inf.prop_mapping.insert(id_column.clone(), prop);
}
if let Some(p) = &cfg_inf.properties {
for key in p.keys() {
let prop = normalize_key(props, key.as_str(), "property", new_id)?;
inf.prop_mapping.insert(key.clone(), prop);
}
}
Some(inf)
}
#[must_use]
pub fn calc_srid(&self, new_id: &str, cfg_srid: i32, default_srid: Option<i32>) -> Option<i32> {
match (self.srid, cfg_srid, default_srid) {
(0, 0, Some(default_srid)) => {
info!(
"Table {} has SRID=0, using provided default SRID={default_srid}",
self.format_id()
);
Some(default_srid)
}
(0, 0, None) => {
let info = "To use this table source, set default or specify this table SRID in the config file, or set the default SRID with --default-srid=...";
warn!("Table {} has SRID=0, skipping. {info}", self.format_id());
None
}
(0, cfg, _) => Some(cfg), (src, 0, _) => Some(src), (src, cfg, _) if src != cfg => {
warn!(
"Table {} has SRID={src}, but source {new_id} has SRID={cfg}",
self.format_id()
);
None
}
(_, cfg, _) => Some(cfg),
}
}
}