Skip to content

Commit

Permalink
editoast: move BoundingBox and Zone to editoast_schemas
Browse files Browse the repository at this point in the history
  • Loading branch information
hamz2a committed Apr 19, 2024
1 parent b3228a3 commit 6bd535e
Show file tree
Hide file tree
Showing 13 changed files with 190 additions and 153 deletions.
2 changes: 2 additions & 0 deletions editoast/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions editoast/editoast_schemas/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ license = "LGPL-3.0"
derivative.workspace = true
editoast_common.workspace = true
enum-map.workspace = true
geojson = "0.24.1"
serde.workspace = true
serde_derive.workspace = true
serde_json.workspace = true
strum.workspace = true
thiserror.workspace = true
utoipa.workspace = true
7 changes: 7 additions & 0 deletions editoast/editoast_schemas/src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use thiserror::Error;

#[derive(Debug, Error)]
pub enum GeometryError {
#[error("expected geometry {expected} but got {actual}")]
UnexpectedGeometry { expected: String, actual: String },
}
1 change: 1 addition & 0 deletions editoast/editoast_schemas/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod errors;
pub mod infra;
pub mod primitives;
pub mod rolling_stock;
Expand Down
148 changes: 148 additions & 0 deletions editoast/editoast_schemas/src/primitives.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
use derivative::Derivative;
use enum_map::Enum;
use geojson;
use geojson::Geometry;
use geojson::Value::LineString;
use serde::Deserialize;
use serde::Serialize;
use std::iter::FromIterator;
use strum::Display;
use strum::EnumIter;
use utoipa::ToSchema;

use crate::errors::GeometryError;

editoast_common::schemas! {
ObjectType,
Zone,
BoundingBox,
}

/// This trait should be implemented by all struct that represents an OSRD type.
Expand Down Expand Up @@ -81,3 +89,143 @@ impl ObjectRef {
ObjectRef { obj_type, obj_id }
}
}

/// A bounding box
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ToSchema)]
pub struct BoundingBox(pub (f64, f64), pub (f64, f64));

impl FromIterator<(f64, f64)> for BoundingBox {
fn from_iter<I: IntoIterator<Item = (f64, f64)>>(iter: I) -> Self {
let mut min: (f64, f64) = (f64::MAX, f64::MAX);
let mut max: (f64, f64) = (f64::MIN, f64::MIN);

for (x, y) in iter {
min.0 = min.0.min(x);
max.0 = max.0.max(x);
min.1 = min.1.min(y);
max.1 = max.1.max(y);
}

BoundingBox(min, max)
}
}

impl BoundingBox {
pub fn union(&mut self, b: &Self) -> &mut Self {
self.0 = (self.0 .0.min(b.0 .0), self.0 .1.min(b.0 .1));
self.1 = (self.1 .0.max(b.1 .0), self.1 .1.max(b.1 .1));
self
}

pub fn is_valid(&self) -> bool {
self.0 .0 <= self.1 .0 && self.0 .1 <= self.1 .1
}

pub fn from_geojson(value: geojson::Value) -> Result<Self, GeometryError> {
match value {
LineString(segments) => Ok(Self::from_iter(segments.into_iter().map(|points| {
(
*points.first().expect("invalid point"),
*points.get(1).expect("invalid point"),
)
}))),
value => Err(GeometryError::UnexpectedGeometry {
expected: "LineString".to_owned(),
actual: value.to_string(),
}),
}
}

pub fn from_geometry(value: Geometry) -> Result<Self, GeometryError> {
Self::from_geojson(value.value)
}

/// Calculates the diagonal length of the bounding box using the Haversine formula.
///
/// # Returns
///
/// * `f64` - The diagonal length of the bounding box in meters.
///
/// # Examples
///
/// ```
/// use editoast_schemas::primitives::BoundingBox;
///
/// let bbox = BoundingBox((40.0, -75.0), (42.0, -73.0));
/// let diagonal_length = bbox.diagonal_length();
/// assert_eq!(diagonal_length, 230908.62753622115);
/// ```
pub fn diagonal_length(&self) -> f64 {
// Earth's mean radius in meters
let r: f64 = 6_378_100.0;

// Calculate differences in longitude and latitude in radians
let d_lon: f64 = (self.1 .0 - self.0 .0).to_radians();
let d_lat: f64 = (self.1 .1 - self.0 .1).to_radians();

// Convert latitude to radians
let lat1: f64 = (self.0 .1).to_radians();
let lat2: f64 = (self.1 .1).to_radians();

// Haversine formula
let a: f64 = ((d_lat / 2.0).sin()) * ((d_lat / 2.0).sin())
+ ((d_lon / 2.0).sin()) * ((d_lon / 2.0).sin()) * (lat1.cos()) * (lat2.cos());
let c: f64 = 2.0 * ((a.sqrt()).atan2((1.0 - a).sqrt()));

// Calculate diagonal length
r * c
}
}

impl Default for BoundingBox {
fn default() -> Self {
Self(
(f64::INFINITY, f64::INFINITY),
(f64::NEG_INFINITY, f64::NEG_INFINITY),
)
}
}

/// Geographic and Schematic bounding box zone impacted by a list of operations.
/// Zones use the coordinate system [epsg:4326](https://epsg.io/4326).
#[derive(Debug, Clone, Default, Serialize, ToSchema)]
pub struct Zone {
pub geo: BoundingBox,
pub sch: BoundingBox,
}

impl Zone {
pub fn union(&mut self, other: &Self) {
self.geo.union(&other.geo);
self.sch.union(&other.sch);
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_bounding_box_union() {
let mut a = BoundingBox((0., 0.), (1., 1.));
let b = BoundingBox((2., 2.), (3., 3.));
a.union(&b);
assert_eq!(a, BoundingBox((0., 0.), (3., 3.)));
}

#[test]
fn test_bounding_box_min() {
let mut min = BoundingBox::default();
let a = BoundingBox((0., 0.), (1., 1.));
min.union(&a);
assert_eq!(min, a);
}

#[test]
fn test_validity() {
assert!(BoundingBox((0., 0.), (1., 1.)).is_valid());
assert!(!BoundingBox((1., 0.), (0., 1.)).is_valid());
assert!(!BoundingBox((0., 1.), (1., 0.)).is_valid());
assert!(!BoundingBox::default().is_valid());
}
}
24 changes: 24 additions & 0 deletions editoast/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,30 @@ impl EditoastError for json_patch::PatchError {
}
}

inventory::submit! {
crate::error::ErrorDefinition::new("editoast:geometry:UnexpectedGeometry", "UnexpectedGeometry", "GeometryError", 404u16, "{\"expected\":\"String\",\"actual\":\"String\"}")
}
impl EditoastError for editoast_schemas::errors::GeometryError {
fn get_status(&self) -> StatusCode {
StatusCode::INTERNAL_SERVER_ERROR
}

fn get_type(&self) -> &str {
"editoast:geometry:UnexpectedGeometry"
}

fn context(&self) -> HashMap<String, Value> {
match self {
Self::UnexpectedGeometry { expected, actual } => {
let mut context = HashMap::new();
context.insert("expected".to_string(), json!(expected));
context.insert("actual".to_string(), json!(actual));
context
}
}
}
}

// error definition : uses by the macro EditoastError to generate
// the list of error and share it with the openAPI generator
#[derive(Debug)]
Expand Down
4 changes: 2 additions & 2 deletions editoast/src/infra_cache/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ use crate::infra_cache::object_cache::SwitchCache;
use crate::infra_cache::object_cache::TrackSectionCache;
use crate::infra_cache::operation::CacheOperation;
use crate::infra_cache::operation::RailjsonObject;
use crate::map::BoundingBox;
use crate::modelsv2::railjson::find_all_schemas;
use crate::modelsv2::Infra;
use editoast_schemas::primitives::BoundingBox;

/// Contains infra cached data used to generate layers and errors
#[derive(Debug, Default, Clone)]
Expand Down Expand Up @@ -861,7 +861,6 @@ pub mod tests {
use crate::infra_cache::operation::create::tests::create_track;
use crate::infra_cache::InfraCache;
use crate::infra_cache::SwitchCache;
use crate::map::BoundingBox;
use crate::modelsv2::infra::tests::test_infra_transaction;
use editoast_common::Identifier;
use editoast_common::NonBlankString;
Expand All @@ -877,6 +876,7 @@ pub mod tests {
use editoast_schemas::infra::SwitchPortConnection;
use editoast_schemas::infra::SwitchType;
use editoast_schemas::infra::TrackEndpoint;
use editoast_schemas::primitives::BoundingBox;
use editoast_schemas::primitives::OSRDIdentified;

#[actix_test]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ use editoast_schemas::primitives::ObjectType;

use crate::infra_cache::Cache;
use crate::infra_cache::ObjectCache;
use crate::map::BoundingBox;
use crate::schema::track_section::Curve;
use crate::schema::track_section::Slope;
use crate::schema::TrackSection;
use editoast_schemas::primitives::BoundingBox;

#[derive(Debug, Clone, Derivative)]
#[derivative(Hash, PartialEq, Default)]
Expand Down
Loading

0 comments on commit 6bd535e

Please sign in to comment.