1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
use chrono::{DateTime, Utc};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::{
date_time::utc_now,
geometry::zero_origin,
validate::{Validate, Validator},
Element, Vector3,
};
/// Root object of an OMF file.
///
/// This is the root element of an OMF file, holding global metadata and a list of
/// [Elements](crate::Element) that describe the objects or shapes within the file.
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]
pub struct Project {
/// Project name.
#[serde(default, skip_serializing_if = "String::is_empty")]
pub name: String,
/// Optional project description.
#[serde(default, skip_serializing_if = "String::is_empty")]
pub description: String,
/// Optional [EPSG](https://epsg.io/) or [PROJ](https://proj.org/) local transformation
/// string, default empty.
///
/// Exactly what is supported depends on the application reading the file.
#[serde(default, skip_serializing_if = "String::is_empty")]
pub coordinate_reference_system: String,
/// Optional unit for distances and locations within the file.
///
/// Typically "meters", "metres", "feet", or empty because the coordinate reference system
/// defines it. If both are empty then applications may assume meters.
#[serde(default, skip_serializing_if = "String::is_empty")]
pub units: String,
/// Optional project origin, default [0, 0, 0].
///
/// Most geometries also have their own origin field. To get the real location add this
/// origin and the geometry origin to all locations within each element.
#[serde(default, skip_serializing_if = "zero_origin")]
pub origin: Vector3,
/// Optional name or email address of the person that created the file, default empty.
#[serde(default, skip_serializing_if = "String::is_empty")]
pub author: String,
/// Optional name and version of the application that created the file, default empty.
#[serde(default, skip_serializing_if = "String::is_empty")]
pub application: String,
/// Optional file or data creation date, default empty.
pub date: DateTime<Utc>,
/// Arbitrary metadata.
///
/// This is the place to put anything that doesn't fit in the other fields.
/// Application-specific data should use a prefix that identifies the application, like
/// `"lf-something"` for Leapfrog.
#[serde(default, skip_serializing_if = "serde_json::Map::is_empty")]
pub metadata: serde_json::Map<String, Value>,
/// List of elements.
#[serde(default)]
pub elements: Vec<Element>,
}
impl Project {
/// Create a new project with just the name set.
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
..Default::default()
}
}
}
impl Default for Project {
fn default() -> Self {
Self {
name: Default::default(),
description: Default::default(),
coordinate_reference_system: Default::default(),
units: Default::default(),
origin: Default::default(),
author: Default::default(),
application: Default::default(),
date: utc_now(),
metadata: Default::default(),
elements: Default::default(),
}
}
}
impl Validate for Project {
fn validate_inner(&mut self, val: &mut Validator) {
val.enter("Project")
.name(&self.name)
.finite_seq(self.origin, "origin")
.objs(&mut self.elements)
.unique(
self.elements.iter().map(|e| &e.name),
"elements[..]::name",
false,
);
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use serde_json::Value;
use super::*;
#[test]
fn serde_empty_project() {
let mut p = Project::new("Test");
p.name = "Foo".to_owned();
p.units = "meters".to_owned();
p.origin = [1e6, 0.0, 0.0];
p.date = chrono::DateTime::from_str("2022-10-31T09:00:00.594Z").unwrap();
p.metadata.insert("other".to_owned(), Value::Bool(true));
let s = serde_json::to_string(&p).unwrap();
let q = serde_json::from_str(&s).unwrap();
assert_eq!(p, q);
assert_eq!(
s,
concat!(
r#"{"name":"Foo","units":"meters","origin":[1000000.0,0.0,0.0],"#,
r#""date":"2022-10-31T09:00:00.594Z","metadata":{"other":true},"#,
r#""elements":[]}"#
)
);
}
}