use serde::{Deserialize, Serialize};
use std::sync::Arc;
use crate::plot::types::{validate_parameter, ParamDefinition, Parameters};
use crate::plot::Layer;
use crate::reader::SqlDialect;
use crate::DataFrame;
mod cartesian;
pub mod map;
pub mod map_projections;
mod polar;
pub use cartesian::Cartesian;
pub use map_projections::MapProjectionTrait;
pub use polar::Polar;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum CoordKind {
Cartesian,
Polar,
Map,
}
pub trait CoordTrait: std::fmt::Debug + Send + Sync {
fn coord_kind(&self) -> CoordKind;
fn name(&self) -> &'static str;
fn position_aesthetic_names(&self) -> &'static [&'static str];
fn default_properties(&self) -> &'static [ParamDefinition] {
&[]
}
fn resolve_properties(
&self,
_coord_type_name: Option<&str>,
properties: &Parameters,
) -> Result<Parameters, String> {
let defaults = self.default_properties();
for (key, value) in properties.iter() {
if let Some(param) = defaults.iter().find(|p| p.name == key) {
validate_parameter(key, value, ¶m.constraint)?;
} else {
let allowed: Vec<&str> = defaults.iter().map(|p| p.name).collect();
return Err(if allowed.is_empty() {
format!(
"{} projection does not accept any properties, but got '{}'",
self.name(),
key
)
} else {
format!(
"{} projection property should be {}, not '{}'",
self.name(),
crate::or_list_quoted(&allowed, '\''),
key
)
});
}
}
let mut resolved = properties.clone();
for param in defaults {
if !resolved.contains_key(param.name) {
if let Some(default) = param.to_parameter_value() {
resolved.insert(param.name.to_string(), default);
}
}
}
Ok(resolved)
}
fn as_map_projection(&self) -> Option<&dyn map_projections::MapProjectionTrait> {
None
}
fn apply_projection_transforms(
&self,
layers: &mut [Layer],
layer_queries: &mut [String],
projection: &mut super::Projection,
dialect: &dyn SqlDialect,
_execute_query: &dyn Fn(&str) -> crate::Result<DataFrame>,
) -> crate::Result<()> {
for (idx, layer) in layers.iter_mut().enumerate() {
layer_queries[idx] = layer.geom.apply_projection(
&layer_queries[idx],
projection,
dialect,
&mut layer.mappings,
&mut layer.partition_by,
&mut layer.parameters,
)?;
}
Ok(())
}
}
#[derive(Clone)]
pub struct Coord(Arc<dyn CoordTrait>);
impl Coord {
pub fn cartesian() -> Self {
Self(Arc::new(Cartesian))
}
pub fn polar() -> Self {
Self(Arc::new(Polar))
}
pub fn map(name: &str, properties: &Parameters) -> Self {
Self(
map_projections::build_map_projection(Some(name), properties)
.expect("map coord name must be a known projection or 'map'"),
)
}
pub fn from_kind(kind: CoordKind) -> Self {
match kind {
CoordKind::Cartesian => Self::cartesian(),
CoordKind::Polar => Self::polar(),
CoordKind::Map => Self::map("crs", &Parameters::new()),
}
}
pub fn coord_kind(&self) -> CoordKind {
self.0.coord_kind()
}
pub fn name(&self) -> &'static str {
self.0.name()
}
pub fn position_aesthetic_names(&self) -> &'static [&'static str] {
self.0.position_aesthetic_names()
}
pub fn default_properties(&self) -> &'static [ParamDefinition] {
self.0.default_properties()
}
pub fn resolve_properties(
&self,
coord_type_name: Option<&str>,
properties: &Parameters,
) -> Result<Parameters, String> {
self.0.resolve_properties(coord_type_name, properties)
}
pub fn as_map_projection(&self) -> Option<&dyn map_projections::MapProjectionTrait> {
self.0.as_map_projection()
}
pub fn apply_projection_transforms(
&self,
layers: &mut [Layer],
layer_queries: &mut [String],
projection: &mut super::Projection,
dialect: &dyn SqlDialect,
execute_query: &dyn Fn(&str) -> crate::Result<DataFrame>,
) -> crate::Result<()> {
self.0.apply_projection_transforms(
layers,
layer_queries,
projection,
dialect,
execute_query,
)
}
}
impl std::fmt::Debug for Coord {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Coord({})", self.name())
}
}
impl std::fmt::Display for Coord {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name())
}
}
impl PartialEq for Coord {
fn eq(&self, other: &Self) -> bool {
self.coord_kind() == other.coord_kind()
}
}
impl Eq for Coord {}
impl std::hash::Hash for Coord {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.coord_kind().hash(state);
}
}
impl Serialize for Coord {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.coord_kind().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Coord {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let kind = CoordKind::deserialize(deserializer)?;
Ok(Coord::from_kind(kind))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_coord_factory_methods() {
let cartesian = Coord::cartesian();
assert_eq!(cartesian.coord_kind(), CoordKind::Cartesian);
assert_eq!(cartesian.name(), "cartesian");
let polar = Coord::polar();
assert_eq!(polar.coord_kind(), CoordKind::Polar);
assert_eq!(polar.name(), "polar");
}
#[test]
fn test_coord_from_kind() {
assert_eq!(
Coord::from_kind(CoordKind::Cartesian).coord_kind(),
CoordKind::Cartesian
);
assert_eq!(
Coord::from_kind(CoordKind::Polar).coord_kind(),
CoordKind::Polar
);
}
#[test]
fn test_coord_equality() {
assert_eq!(Coord::cartesian(), Coord::cartesian());
assert_eq!(Coord::polar(), Coord::polar());
assert_ne!(Coord::cartesian(), Coord::polar());
}
#[test]
fn test_coord_serialization() {
let cartesian = Coord::cartesian();
let json = serde_json::to_string(&cartesian).unwrap();
assert_eq!(json, "\"cartesian\"");
let polar = Coord::polar();
let json = serde_json::to_string(&polar).unwrap();
assert_eq!(json, "\"polar\"");
}
#[test]
fn test_coord_deserialization() {
let cartesian: Coord = serde_json::from_str("\"cartesian\"").unwrap();
assert_eq!(cartesian.coord_kind(), CoordKind::Cartesian);
let polar: Coord = serde_json::from_str("\"polar\"").unwrap();
assert_eq!(polar.coord_kind(), CoordKind::Polar);
}
#[test]
fn test_position_aesthetic_names() {
let cartesian = Coord::cartesian();
assert_eq!(cartesian.position_aesthetic_names(), &["x", "y"]);
let polar = Coord::polar();
assert_eq!(polar.position_aesthetic_names(), &["radius", "angle"]);
}
}