use std::any::Any;
use std::collections::HashMap;
use nalgebra::Vector3;
use serde::{Deserialize, Serialize};
use serde_json::{Value as JsonValue, json};
use uuid::Uuid;
use crate::constants::AngleFormat;
use crate::coordinates::position_geodetic_to_ecef;
use crate::utils::errors::BraheError;
use crate::utils::identifiable::Identifiable;
pub trait AccessibleLocation: Identifiable + Send + Sync {
fn center_geodetic(&self) -> Vector3<f64>;
fn center_ecef(&self) -> Vector3<f64>;
fn properties(&self) -> &HashMap<String, JsonValue>;
fn properties_mut(&mut self) -> &mut HashMap<String, JsonValue>;
fn to_geojson(&self) -> JsonValue;
fn as_any(&self) -> &dyn Any;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PointLocation {
center: Vector3<f64>,
#[serde(skip)]
center_ecef: Option<Vector3<f64>>,
#[serde(default)]
properties: HashMap<String, JsonValue>,
name: Option<String>,
id: Option<u64>,
uuid: Option<Uuid>,
}
impl PointLocation {
pub fn new(lon: f64, lat: f64, alt: f64) -> Self {
let center = Vector3::new(lon, lat, alt);
let center_ecef = position_geodetic_to_ecef(center, AngleFormat::Degrees)
.expect("Invalid geodetic coordinates");
Self {
center,
center_ecef: Some(center_ecef),
properties: HashMap::new(),
name: None,
id: None,
uuid: Some(Uuid::now_v7()),
}
}
pub fn from_geojson(geojson: &JsonValue) -> Result<Self, BraheError> {
if geojson.get("type").and_then(|t| t.as_str()) != Some("Feature") {
return Err(BraheError::ParseError(
"GeoJSON must be a Feature object".to_string(),
));
}
let geometry = geojson.get("geometry").ok_or_else(|| {
BraheError::ParseError("GeoJSON Feature missing geometry".to_string())
})?;
if geometry.get("type").and_then(|t| t.as_str()) != Some("Point") {
return Err(BraheError::ParseError(
"GeoJSON geometry must be Point type".to_string(),
));
}
let coords = geometry
.get("coordinates")
.and_then(|c| c.as_array())
.ok_or_else(|| BraheError::ParseError("Invalid Point coordinates".to_string()))?;
if coords.len() < 2 {
return Err(BraheError::ParseError(
"Point must have at least [lon, lat]".to_string(),
));
}
let lon = coords[0]
.as_f64()
.ok_or_else(|| BraheError::ParseError("Longitude must be a number".to_string()))?;
let lat = coords[1]
.as_f64()
.ok_or_else(|| BraheError::ParseError("Latitude must be a number".to_string()))?;
let alt = coords.get(2).and_then(|a| a.as_f64()).unwrap_or(0.0);
let mut location = Self::new(lon, lat, alt);
if let Some(props) = geojson.get("properties").and_then(|p| p.as_object()) {
for (key, value) in props.iter() {
match key.as_str() {
"name" => {
if let Some(name_str) = value.as_str() {
location.name = Some(name_str.to_string());
}
}
"id" => {
if let Some(id_num) = value.as_u64() {
location.id = Some(id_num);
}
}
"uuid" => {
if let Some(uuid_str) = value.as_str()
&& let Ok(parsed_uuid) = Uuid::parse_str(uuid_str)
{
location.uuid = Some(parsed_uuid);
}
}
_ => {
location.properties.insert(key.clone(), value.clone());
}
}
}
}
Ok(location)
}
pub fn add_property(mut self, key: &str, value: JsonValue) -> Self {
self.properties.insert(key.to_string(), value);
self
}
pub fn lon(&self) -> f64 {
self.center.x
}
pub fn lat(&self) -> f64 {
self.center.y
}
pub fn alt(&self) -> f64 {
self.center.z
}
pub fn longitude(&self, angle_format: AngleFormat) -> f64 {
match angle_format {
AngleFormat::Degrees => self.center.x,
AngleFormat::Radians => self.center.x.to_radians(),
}
}
pub fn latitude(&self, angle_format: AngleFormat) -> f64 {
match angle_format {
AngleFormat::Degrees => self.center.y,
AngleFormat::Radians => self.center.y.to_radians(),
}
}
pub fn altitude(&self) -> f64 {
self.center.z
}
}
impl AccessibleLocation for PointLocation {
fn center_geodetic(&self) -> Vector3<f64> {
self.center
}
fn center_ecef(&self) -> Vector3<f64> {
self.center_ecef.unwrap_or_else(|| {
position_geodetic_to_ecef(self.center, AngleFormat::Degrees)
.expect("Invalid geodetic coordinates")
})
}
fn properties(&self) -> &HashMap<String, JsonValue> {
&self.properties
}
fn properties_mut(&mut self) -> &mut HashMap<String, JsonValue> {
&mut self.properties
}
fn to_geojson(&self) -> JsonValue {
let mut props = self.properties.clone();
if let Some(name) = &self.name {
props.insert("name".to_string(), json!(name));
}
if let Some(id) = self.id {
props.insert("id".to_string(), json!(id));
}
if let Some(uuid) = self.uuid {
props.insert("uuid".to_string(), json!(uuid.to_string()));
}
json!({
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [self.center.x, self.center.y, self.center.z]
},
"properties": props
})
}
fn as_any(&self) -> &dyn Any {
self
}
}
impl Identifiable for PointLocation {
fn with_name(mut self, name: &str) -> Self {
self.name = Some(name.to_string());
self
}
fn with_uuid(mut self, uuid: Uuid) -> Self {
self.uuid = Some(uuid);
self
}
fn with_new_uuid(mut self) -> Self {
self.uuid = Some(Uuid::now_v7());
self
}
fn with_id(mut self, id: u64) -> Self {
self.id = Some(id);
self
}
fn with_identity(mut self, name: Option<&str>, uuid: Option<Uuid>, id: Option<u64>) -> Self {
self.name = name.map(|s| s.to_string());
self.uuid = uuid;
self.id = id;
self
}
fn set_identity(&mut self, name: Option<&str>, uuid: Option<Uuid>, id: Option<u64>) {
self.name = name.map(|s| s.to_string());
self.uuid = uuid;
self.id = id;
}
fn set_id(&mut self, id: Option<u64>) {
self.id = id;
}
fn set_name(&mut self, name: Option<&str>) {
self.name = name.map(|s| s.to_string());
}
fn generate_uuid(&mut self) {
self.uuid = Some(Uuid::now_v7());
}
fn get_id(&self) -> Option<u64> {
self.id
}
fn get_name(&self) -> Option<&str> {
self.name.as_deref()
}
fn get_uuid(&self) -> Option<Uuid> {
self.uuid
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PolygonLocation {
center: Vector3<f64>,
#[serde(skip)]
center_ecef: Option<Vector3<f64>>,
vertices: Vec<Vector3<f64>>,
#[serde(default)]
properties: HashMap<String, JsonValue>,
name: Option<String>,
id: Option<u64>,
uuid: Option<Uuid>,
}
impl PolygonLocation {
pub fn new(mut vertices: Vec<Vector3<f64>>) -> Result<Self, BraheError> {
if vertices.len() < 4 {
return Err(BraheError::ParseError(
"Polygon must have at least 4 vertices (3 unique + closure)".to_string(),
));
}
for i in 0..vertices.len() - 2 {
if vertices[i] == vertices[i + 1] {
return Err(BraheError::ParseError(format!(
"Duplicate consecutive vertices at index {}",
i
)));
}
}
let first = vertices[0];
let last = vertices[vertices.len() - 1];
if first != last {
vertices.push(first);
}
let num_unique = vertices.len() - 1; let mut sum_lon = 0.0;
let mut sum_lat = 0.0;
let mut sum_alt = 0.0;
for vertex in vertices.iter().take(num_unique) {
sum_lon += vertex.x;
sum_lat += vertex.y;
sum_alt += vertex.z;
}
let center = Vector3::new(
sum_lon / num_unique as f64,
sum_lat / num_unique as f64,
sum_alt / num_unique as f64,
);
let center_ecef = position_geodetic_to_ecef(center, AngleFormat::Degrees)
.expect("Invalid geodetic coordinates");
Ok(Self {
center,
center_ecef: Some(center_ecef),
vertices,
properties: HashMap::new(),
name: None,
id: None,
uuid: Some(Uuid::now_v7()),
})
}
pub fn from_geojson(geojson: &JsonValue) -> Result<Self, BraheError> {
if geojson.get("type").and_then(|t| t.as_str()) != Some("Feature") {
return Err(BraheError::ParseError(
"GeoJSON must be a Feature object".to_string(),
));
}
let geometry = geojson.get("geometry").ok_or_else(|| {
BraheError::ParseError("GeoJSON Feature missing geometry".to_string())
})?;
if geometry.get("type").and_then(|t| t.as_str()) != Some("Polygon") {
return Err(BraheError::ParseError(
"GeoJSON geometry must be Polygon type".to_string(),
));
}
let coords = geometry
.get("coordinates")
.and_then(|c| c.as_array())
.ok_or_else(|| BraheError::ParseError("Invalid Polygon coordinates".to_string()))?;
if coords.is_empty() {
return Err(BraheError::ParseError(
"Polygon must have at least one ring".to_string(),
));
}
let outer_ring = coords[0]
.as_array()
.ok_or_else(|| BraheError::ParseError("Invalid outer ring".to_string()))?;
let mut vertices = Vec::new();
for vertex_json in outer_ring {
let vertex_array = vertex_json
.as_array()
.ok_or_else(|| BraheError::ParseError("Invalid vertex format".to_string()))?;
if vertex_array.len() < 2 {
return Err(BraheError::ParseError(
"Vertex must have [lon, lat] or [lon, lat, alt]".to_string(),
));
}
let lon = vertex_array[0]
.as_f64()
.ok_or_else(|| BraheError::ParseError("Longitude must be a number".to_string()))?;
let lat = vertex_array[1]
.as_f64()
.ok_or_else(|| BraheError::ParseError("Latitude must be a number".to_string()))?;
let alt = vertex_array.get(2).and_then(|a| a.as_f64()).unwrap_or(0.0);
vertices.push(Vector3::new(lon, lat, alt));
}
let mut polygon = Self::new(vertices)?;
if let Some(props) = geojson.get("properties").and_then(|p| p.as_object()) {
for (key, value) in props.iter() {
match key.as_str() {
"name" => {
if let Some(name_str) = value.as_str() {
polygon.name = Some(name_str.to_string());
}
}
"id" => {
if let Some(id_num) = value.as_u64() {
polygon.id = Some(id_num);
}
}
"uuid" => {
if let Some(uuid_str) = value.as_str()
&& let Ok(parsed_uuid) = Uuid::parse_str(uuid_str)
{
polygon.uuid = Some(parsed_uuid);
}
}
_ => {
polygon.properties.insert(key.clone(), value.clone());
}
}
}
}
Ok(polygon)
}
pub fn vertices(&self) -> &[Vector3<f64>] {
&self.vertices
}
pub fn num_vertices(&self) -> usize {
self.vertices.len() - 1
}
pub fn add_property(mut self, key: &str, value: JsonValue) -> Self {
self.properties.insert(key.to_string(), value);
self
}
pub fn lon(&self) -> f64 {
self.center.x
}
pub fn lat(&self) -> f64 {
self.center.y
}
pub fn alt(&self) -> f64 {
self.center.z
}
pub fn longitude(&self, angle_format: AngleFormat) -> f64 {
match angle_format {
AngleFormat::Degrees => self.center.x,
AngleFormat::Radians => self.center.x.to_radians(),
}
}
pub fn latitude(&self, angle_format: AngleFormat) -> f64 {
match angle_format {
AngleFormat::Degrees => self.center.y,
AngleFormat::Radians => self.center.y.to_radians(),
}
}
pub fn altitude(&self) -> f64 {
self.center.z
}
}
impl AccessibleLocation for PolygonLocation {
fn center_geodetic(&self) -> Vector3<f64> {
self.center
}
fn center_ecef(&self) -> Vector3<f64> {
self.center_ecef.unwrap_or_else(|| {
position_geodetic_to_ecef(self.center, AngleFormat::Degrees)
.expect("Invalid geodetic coordinates")
})
}
fn properties(&self) -> &HashMap<String, JsonValue> {
&self.properties
}
fn properties_mut(&mut self) -> &mut HashMap<String, JsonValue> {
&mut self.properties
}
fn to_geojson(&self) -> JsonValue {
let mut props = self.properties.clone();
if let Some(name) = &self.name {
props.insert("name".to_string(), json!(name));
}
if let Some(id) = self.id {
props.insert("id".to_string(), json!(id));
}
if let Some(uuid) = self.uuid {
props.insert("uuid".to_string(), json!(uuid.to_string()));
}
let coords: Vec<Vec<f64>> = self.vertices.iter().map(|v| vec![v.x, v.y, v.z]).collect();
json!({
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [coords] },
"properties": props
})
}
fn as_any(&self) -> &dyn Any {
self
}
}
impl Identifiable for PolygonLocation {
fn with_name(mut self, name: &str) -> Self {
self.name = Some(name.to_string());
self
}
fn with_uuid(mut self, uuid: Uuid) -> Self {
self.uuid = Some(uuid);
self
}
fn with_new_uuid(mut self) -> Self {
self.uuid = Some(Uuid::now_v7());
self
}
fn with_id(mut self, id: u64) -> Self {
self.id = Some(id);
self
}
fn with_identity(mut self, name: Option<&str>, uuid: Option<Uuid>, id: Option<u64>) -> Self {
self.name = name.map(|s| s.to_string());
self.uuid = uuid;
self.id = id;
self
}
fn set_identity(&mut self, name: Option<&str>, uuid: Option<Uuid>, id: Option<u64>) {
self.name = name.map(|s| s.to_string());
self.uuid = uuid;
self.id = id;
}
fn set_id(&mut self, id: Option<u64>) {
self.id = id;
}
fn set_name(&mut self, name: Option<&str>) {
self.name = name.map(|s| s.to_string());
}
fn generate_uuid(&mut self) {
self.uuid = Some(Uuid::now_v7());
}
fn get_id(&self) -> Option<u64> {
self.id
}
fn get_name(&self) -> Option<&str> {
self.name.as_deref()
}
fn get_uuid(&self) -> Option<Uuid> {
self.uuid
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
#[test]
fn test_point_location_new() {
let loc = PointLocation::new(15.4, 78.2, 0.0);
assert_eq!(loc.lon(), 15.4);
assert_eq!(loc.lat(), 78.2);
assert_eq!(loc.alt(), 0.0);
let ecef = loc.center_ecef();
assert!(ecef.norm() > 0.0);
}
#[test]
fn test_point_location_identifiable() {
let loc = PointLocation::new(15.4, 78.2, 0.0)
.with_name("Svalbard")
.with_id(42)
.with_new_uuid();
assert_eq!(loc.get_name(), Some("Svalbard"));
assert_eq!(loc.get_id(), Some(42));
assert!(loc.get_uuid().is_some());
}
#[test]
fn test_point_location_properties() {
let loc = PointLocation::new(15.4, 78.2, 0.0)
.add_property("country", json!("Norway"))
.add_property("population", json!(2500));
assert_eq!(loc.properties().get("country").unwrap(), "Norway");
assert_eq!(loc.properties().get("population").unwrap(), 2500);
}
#[test]
fn test_point_location_geojson_roundtrip() {
let original = PointLocation::new(15.4, 78.2, 100.0)
.with_name("Svalbard")
.add_property("country", json!("Norway"));
let geojson = original.to_geojson();
let reconstructed = PointLocation::from_geojson(&geojson).unwrap();
assert_eq!(reconstructed.lon(), 15.4);
assert_eq!(reconstructed.lat(), 78.2);
assert_eq!(reconstructed.alt(), 100.0);
assert_eq!(reconstructed.get_name(), Some("Svalbard"));
assert_eq!(reconstructed.properties().get("country").unwrap(), "Norway");
}
#[test]
fn test_polygon_location_new() {
let vertices = vec![
Vector3::new(10.0, 50.0, 0.0),
Vector3::new(11.0, 50.0, 0.0),
Vector3::new(11.0, 51.0, 0.0),
Vector3::new(10.0, 51.0, 0.0),
Vector3::new(10.0, 50.0, 0.0),
];
let poly = PolygonLocation::new(vertices.clone()).unwrap();
assert_eq!(poly.num_vertices(), 4);
assert_eq!(poly.vertices().len(), 5);
let center = poly.center_geodetic();
assert_abs_diff_eq!(center.x, 10.5, epsilon = 0.01); assert_abs_diff_eq!(center.y, 50.5, epsilon = 0.01); }
#[test]
fn test_polygon_location_auto_close() {
let vertices = vec![
Vector3::new(10.0, 50.0, 0.0),
Vector3::new(11.0, 50.0, 0.0),
Vector3::new(11.0, 51.0, 0.0),
Vector3::new(10.0, 51.0, 0.0),
];
let poly = PolygonLocation::new(vertices).unwrap();
assert_eq!(poly.vertices().len(), 5);
assert_eq!(poly.vertices()[0], poly.vertices()[4]);
}
#[test]
fn test_polygon_location_validation() {
let result = PolygonLocation::new(vec![
Vector3::new(10.0, 50.0, 0.0),
Vector3::new(11.0, 50.0, 0.0),
]);
assert!(result.is_err());
let result = PolygonLocation::new(vec![
Vector3::new(10.0, 50.0, 0.0),
Vector3::new(10.0, 50.0, 0.0), Vector3::new(11.0, 50.0, 0.0),
Vector3::new(10.0, 50.0, 0.0),
]);
assert!(result.is_err());
}
#[test]
fn test_polygon_location_geojson_roundtrip() {
let vertices = vec![
Vector3::new(10.0, 50.0, 0.0),
Vector3::new(11.0, 50.0, 0.0),
Vector3::new(11.0, 51.0, 0.0),
Vector3::new(10.0, 51.0, 0.0),
Vector3::new(10.0, 50.0, 0.0),
];
let original = PolygonLocation::new(vertices.clone())
.unwrap()
.with_name("AOI-1")
.add_property("region", json!("Europe"));
let geojson = original.to_geojson();
let reconstructed = PolygonLocation::from_geojson(&geojson).unwrap();
assert_eq!(reconstructed.num_vertices(), 4);
assert_eq!(reconstructed.get_name(), Some("AOI-1"));
assert_eq!(reconstructed.properties().get("region").unwrap(), "Europe");
}
#[test]
fn test_point_location_coordinate_accessors() {
let loc = PointLocation::new(15.4, 78.2, 500.0);
assert_eq!(loc.lon(), 15.4);
assert_eq!(loc.lat(), 78.2);
assert_eq!(loc.alt(), 500.0);
assert_eq!(loc.altitude(), 500.0);
assert_eq!(loc.longitude(AngleFormat::Degrees), 15.4);
assert_eq!(loc.latitude(AngleFormat::Degrees), 78.2);
let lon_rad = loc.longitude(AngleFormat::Radians);
let lat_rad = loc.latitude(AngleFormat::Radians);
assert_abs_diff_eq!(lon_rad, 15.4_f64.to_radians(), epsilon = 1e-10);
assert_abs_diff_eq!(lat_rad, 78.2_f64.to_radians(), epsilon = 1e-10);
}
#[test]
fn test_point_location_center_geodetic() {
let loc = PointLocation::new(15.4, 78.2, 500.0);
let center = loc.center_geodetic();
assert_eq!(center.x, 15.4);
assert_eq!(center.y, 78.2);
assert_eq!(center.z, 500.0);
}
#[test]
fn test_point_location_center_ecef() {
let loc = PointLocation::new(0.0, 0.0, 0.0);
let ecef = loc.center_ecef();
assert_abs_diff_eq!(ecef.x, 6378137.0, epsilon = 1.0);
assert_abs_diff_eq!(ecef.y, 0.0, epsilon = 1.0);
assert_abs_diff_eq!(ecef.z, 0.0, epsilon = 1.0);
}
#[test]
fn test_point_location_properties_accessor() {
let mut loc = PointLocation::new(15.4, 78.2, 0.0);
assert!(loc.properties().is_empty());
loc.properties_mut()
.insert("test_key".to_string(), json!("test_value"));
assert_eq!(loc.properties().get("test_key").unwrap(), "test_value");
}
#[test]
fn test_point_location_add_property_builder() {
let loc = PointLocation::new(15.4, 78.2, 0.0)
.add_property("key1", json!("value1"))
.add_property("key2", json!(42))
.add_property("key3", json!(true));
assert_eq!(loc.properties().get("key1").unwrap(), "value1");
assert_eq!(loc.properties().get("key2").unwrap(), 42);
assert_eq!(loc.properties().get("key3").unwrap(), true);
}
#[test]
fn test_point_location_to_geojson_required_fields() {
let loc = PointLocation::new(15.4, 78.2, 100.0)
.with_name("TestLocation")
.with_id(42)
.with_new_uuid()
.add_property("custom_prop", json!("custom_value"));
let geojson = loc.to_geojson();
assert_eq!(geojson.get("type").unwrap(), "Feature");
let geometry = geojson.get("geometry").unwrap();
assert_eq!(geometry.get("type").unwrap(), "Point");
let coords = geometry.get("coordinates").unwrap().as_array().unwrap();
assert_eq!(coords.len(), 3);
assert_eq!(coords[0].as_f64().unwrap(), 15.4);
assert_eq!(coords[1].as_f64().unwrap(), 78.2);
assert_eq!(coords[2].as_f64().unwrap(), 100.0);
let properties = geojson.get("properties").unwrap();
assert_eq!(properties.get("name").unwrap(), "TestLocation");
assert_eq!(properties.get("id").unwrap(), 42);
assert!(properties.get("uuid").is_some());
assert_eq!(properties.get("custom_prop").unwrap(), "custom_value");
}
#[test]
fn test_point_location_from_geojson_minimal() {
let geojson = json!({
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [15.4, 78.2]
},
"properties": {}
});
let loc = PointLocation::from_geojson(&geojson).unwrap();
assert_eq!(loc.lon(), 15.4);
assert_eq!(loc.lat(), 78.2);
assert_eq!(loc.alt(), 0.0); }
#[test]
fn test_point_location_from_geojson_with_altitude() {
let geojson = json!({
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [15.4, 78.2, 500.0]
},
"properties": {}
});
let loc = PointLocation::from_geojson(&geojson).unwrap();
assert_eq!(loc.alt(), 500.0);
}
#[test]
fn test_point_location_from_geojson_invalid_type() {
let geojson = json!({
"type": "FeatureCollection",
"features": []
});
let result = PointLocation::from_geojson(&geojson);
assert!(result.is_err());
}
#[test]
fn test_point_location_from_geojson_wrong_geometry() {
let geojson = json!({
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 0.0]]]
},
"properties": {}
});
let result = PointLocation::from_geojson(&geojson);
assert!(result.is_err());
}
#[test]
fn test_point_location_identifiable_methods() {
let uuid = Uuid::now_v7();
let loc = PointLocation::new(0.0, 0.0, 0.0).with_name("Test");
assert_eq!(loc.get_name(), Some("Test"));
let loc = PointLocation::new(0.0, 0.0, 0.0).with_id(123);
assert_eq!(loc.get_id(), Some(123));
let loc = PointLocation::new(0.0, 0.0, 0.0).with_uuid(uuid);
assert_eq!(loc.get_uuid(), Some(uuid));
let loc = PointLocation::new(0.0, 0.0, 0.0).with_new_uuid();
assert!(loc.get_uuid().is_some());
let loc = PointLocation::new(0.0, 0.0, 0.0).with_identity(
Some("Combined"),
Some(uuid),
Some(456),
);
assert_eq!(loc.get_name(), Some("Combined"));
assert_eq!(loc.get_uuid(), Some(uuid));
assert_eq!(loc.get_id(), Some(456));
let mut loc = PointLocation::new(0.0, 0.0, 0.0);
loc.set_identity(Some("NewName"), Some(uuid), Some(789));
assert_eq!(loc.get_name(), Some("NewName"));
assert_eq!(loc.get_uuid(), Some(uuid));
assert_eq!(loc.get_id(), Some(789));
let mut loc = PointLocation::new(0.0, 0.0, 0.0);
loc.set_id(Some(999));
assert_eq!(loc.get_id(), Some(999));
let mut loc = PointLocation::new(0.0, 0.0, 0.0);
loc.set_name(Some("SetName"));
assert_eq!(loc.get_name(), Some("SetName"));
let mut loc = PointLocation::new(0.0, 0.0, 0.0);
loc.generate_uuid();
assert!(loc.get_uuid().is_some());
}
#[test]
fn test_polygon_location_coordinate_accessors() {
let vertices = vec![
Vector3::new(10.0, 50.0, 100.0),
Vector3::new(11.0, 50.0, 100.0),
Vector3::new(11.0, 51.0, 100.0),
Vector3::new(10.0, 51.0, 100.0),
Vector3::new(10.0, 50.0, 100.0),
];
let poly = PolygonLocation::new(vertices).unwrap();
assert_abs_diff_eq!(poly.lon(), 10.5, epsilon = 0.01);
assert_abs_diff_eq!(poly.lat(), 50.5, epsilon = 0.01);
assert_abs_diff_eq!(poly.alt(), 100.0, epsilon = 0.01);
assert_abs_diff_eq!(poly.altitude(), 100.0, epsilon = 0.01);
assert_abs_diff_eq!(poly.longitude(AngleFormat::Degrees), 10.5, epsilon = 0.01);
assert_abs_diff_eq!(poly.latitude(AngleFormat::Degrees), 50.5, epsilon = 0.01);
let lon_rad = poly.longitude(AngleFormat::Radians);
let lat_rad = poly.latitude(AngleFormat::Radians);
assert_abs_diff_eq!(lon_rad, 10.5_f64.to_radians(), epsilon = 1e-10);
assert_abs_diff_eq!(lat_rad, 50.5_f64.to_radians(), epsilon = 1e-10);
}
#[test]
fn test_polygon_location_vertices_accessor() {
let vertices = vec![
Vector3::new(10.0, 50.0, 0.0),
Vector3::new(11.0, 50.0, 0.0),
Vector3::new(11.0, 51.0, 0.0),
Vector3::new(10.0, 51.0, 0.0),
Vector3::new(10.0, 50.0, 0.0),
];
let poly = PolygonLocation::new(vertices.clone()).unwrap();
let verts = poly.vertices();
assert_eq!(verts.len(), 5);
assert_eq!(verts[0], vertices[0]);
assert_eq!(verts[4], vertices[4]);
}
#[test]
fn test_polygon_location_num_vertices() {
let vertices = vec![
Vector3::new(10.0, 50.0, 0.0),
Vector3::new(11.0, 50.0, 0.0),
Vector3::new(11.0, 51.0, 0.0),
Vector3::new(10.0, 51.0, 0.0),
Vector3::new(10.0, 50.0, 0.0),
];
let poly = PolygonLocation::new(vertices).unwrap();
assert_eq!(poly.num_vertices(), 4);
}
#[test]
fn test_polygon_location_center_geodetic() {
let vertices = vec![
Vector3::new(10.0, 50.0, 100.0),
Vector3::new(11.0, 50.0, 100.0),
Vector3::new(11.0, 51.0, 100.0),
Vector3::new(10.0, 51.0, 100.0),
Vector3::new(10.0, 50.0, 100.0),
];
let poly = PolygonLocation::new(vertices).unwrap();
let center = poly.center_geodetic();
assert_abs_diff_eq!(center.x, 10.5, epsilon = 0.01);
assert_abs_diff_eq!(center.y, 50.5, epsilon = 0.01);
assert_abs_diff_eq!(center.z, 100.0, epsilon = 0.01);
}
#[test]
fn test_polygon_location_center_ecef() {
let vertices = vec![
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(1.0, 0.0, 0.0),
Vector3::new(1.0, 1.0, 0.0),
Vector3::new(0.0, 1.0, 0.0),
Vector3::new(0.0, 0.0, 0.0),
];
let poly = PolygonLocation::new(vertices).unwrap();
let ecef = poly.center_ecef();
assert!(ecef.norm() > 6000000.0); }
#[test]
fn test_polygon_location_properties_accessor() {
let vertices = vec![
Vector3::new(10.0, 50.0, 0.0),
Vector3::new(11.0, 50.0, 0.0),
Vector3::new(11.0, 51.0, 0.0),
Vector3::new(10.0, 51.0, 0.0),
Vector3::new(10.0, 50.0, 0.0),
];
let mut poly = PolygonLocation::new(vertices).unwrap();
assert!(poly.properties().is_empty());
poly.properties_mut()
.insert("test_key".to_string(), json!("test_value"));
assert_eq!(poly.properties().get("test_key").unwrap(), "test_value");
}
#[test]
fn test_polygon_location_add_property_builder() {
let vertices = vec![
Vector3::new(10.0, 50.0, 0.0),
Vector3::new(11.0, 50.0, 0.0),
Vector3::new(11.0, 51.0, 0.0),
Vector3::new(10.0, 51.0, 0.0),
Vector3::new(10.0, 50.0, 0.0),
];
let poly = PolygonLocation::new(vertices)
.unwrap()
.add_property("key1", json!("value1"))
.add_property("key2", json!(42))
.add_property("key3", json!(true));
assert_eq!(poly.properties().get("key1").unwrap(), "value1");
assert_eq!(poly.properties().get("key2").unwrap(), 42);
assert_eq!(poly.properties().get("key3").unwrap(), true);
}
#[test]
fn test_polygon_location_to_geojson_required_fields() {
let vertices = vec![
Vector3::new(10.0, 50.0, 0.0),
Vector3::new(11.0, 50.0, 0.0),
Vector3::new(11.0, 51.0, 0.0),
Vector3::new(10.0, 51.0, 0.0),
Vector3::new(10.0, 50.0, 0.0),
];
let poly = PolygonLocation::new(vertices)
.unwrap()
.with_name("TestPolygon")
.with_id(99)
.with_new_uuid()
.add_property("custom_prop", json!("custom_value"));
let geojson = poly.to_geojson();
assert_eq!(geojson.get("type").unwrap(), "Feature");
let geometry = geojson.get("geometry").unwrap();
assert_eq!(geometry.get("type").unwrap(), "Polygon");
let coords = geometry.get("coordinates").unwrap().as_array().unwrap();
assert_eq!(coords.len(), 1);
let outer_ring = coords[0].as_array().unwrap();
assert_eq!(outer_ring.len(), 5);
let first_vertex = outer_ring[0].as_array().unwrap();
assert_eq!(first_vertex[0].as_f64().unwrap(), 10.0);
assert_eq!(first_vertex[1].as_f64().unwrap(), 50.0);
assert_eq!(first_vertex[2].as_f64().unwrap(), 0.0);
let properties = geojson.get("properties").unwrap();
assert_eq!(properties.get("name").unwrap(), "TestPolygon");
assert_eq!(properties.get("id").unwrap(), 99);
assert!(properties.get("uuid").is_some());
assert_eq!(properties.get("custom_prop").unwrap(), "custom_value");
}
#[test]
fn test_polygon_location_from_geojson_minimal() {
let geojson = json!({
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [[
[10.0, 50.0],
[11.0, 50.0],
[11.0, 51.0],
[10.0, 51.0],
[10.0, 50.0]
]]
},
"properties": {}
});
let poly = PolygonLocation::from_geojson(&geojson).unwrap();
assert_eq!(poly.num_vertices(), 4);
}
#[test]
fn test_polygon_location_from_geojson_with_altitude() {
let geojson = json!({
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [[
[10.0, 50.0, 100.0],
[11.0, 50.0, 100.0],
[11.0, 51.0, 100.0],
[10.0, 51.0, 100.0],
[10.0, 50.0, 100.0]
]]
},
"properties": {}
});
let poly = PolygonLocation::from_geojson(&geojson).unwrap();
assert_abs_diff_eq!(poly.alt(), 100.0, epsilon = 0.01);
}
#[test]
fn test_polygon_location_from_geojson_invalid_type() {
let geojson = json!({
"type": "FeatureCollection",
"features": []
});
let result = PolygonLocation::from_geojson(&geojson);
assert!(result.is_err());
}
#[test]
fn test_polygon_location_from_geojson_wrong_geometry() {
let geojson = json!({
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [0.0, 0.0]
},
"properties": {}
});
let result = PolygonLocation::from_geojson(&geojson);
assert!(result.is_err());
}
#[test]
fn test_polygon_location_identifiable_methods() {
let vertices = vec![
Vector3::new(10.0, 50.0, 0.0),
Vector3::new(11.0, 50.0, 0.0),
Vector3::new(11.0, 51.0, 0.0),
Vector3::new(10.0, 51.0, 0.0),
Vector3::new(10.0, 50.0, 0.0),
];
let uuid = Uuid::now_v7();
let poly = PolygonLocation::new(vertices.clone())
.unwrap()
.with_name("Test");
assert_eq!(poly.get_name(), Some("Test"));
let poly = PolygonLocation::new(vertices.clone()).unwrap().with_id(123);
assert_eq!(poly.get_id(), Some(123));
let poly = PolygonLocation::new(vertices.clone())
.unwrap()
.with_uuid(uuid);
assert_eq!(poly.get_uuid(), Some(uuid));
let poly = PolygonLocation::new(vertices.clone())
.unwrap()
.with_new_uuid();
assert!(poly.get_uuid().is_some());
let poly = PolygonLocation::new(vertices.clone())
.unwrap()
.with_identity(Some("Combined"), Some(uuid), Some(456));
assert_eq!(poly.get_name(), Some("Combined"));
assert_eq!(poly.get_uuid(), Some(uuid));
assert_eq!(poly.get_id(), Some(456));
let mut poly = PolygonLocation::new(vertices.clone()).unwrap();
poly.set_identity(Some("NewName"), Some(uuid), Some(789));
assert_eq!(poly.get_name(), Some("NewName"));
assert_eq!(poly.get_uuid(), Some(uuid));
assert_eq!(poly.get_id(), Some(789));
let mut poly = PolygonLocation::new(vertices.clone()).unwrap();
poly.set_id(Some(999));
assert_eq!(poly.get_id(), Some(999));
let mut poly = PolygonLocation::new(vertices.clone()).unwrap();
poly.set_name(Some("SetName"));
assert_eq!(poly.get_name(), Some("SetName"));
let mut poly = PolygonLocation::new(vertices.clone()).unwrap();
poly.generate_uuid();
assert!(poly.get_uuid().is_some());
}
}