diff --git a/src/database/db_core.rs b/src/database/db_core.rs index 3b5f4ba..e800946 100644 --- a/src/database/db_core.rs +++ b/src/database/db_core.rs @@ -15,6 +15,20 @@ pub struct CoreQueryParameters<'a> { pub resolution: Option>, } +impl CoreQueryParameters<'_> { + pub fn view_port(&self, space: &Space) -> Option { + if let Some((low, high)) = self.view_port { + let view_port = Shape::BoundingBox(low.into(), high.into()); + match view_port.rebase(Space::universe(), space) { + Err(_) => None, + Ok(view) => Some(view), + } + } else { + None + } + } +} + #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] pub enum Properties { Feature(String), @@ -194,27 +208,32 @@ impl Core { from: &str, ) -> ResultSet { let CoreQueryParameters { - db, - output_space, - threshold_volume, - resolution, - .. + db, output_space, .. } = parameters; let mut results = vec![]; let count = positions.len(); let from = db.space(from)?; + // Filter positions based on the view port, if present + let filtered = match parameters.view_port(from) { + None => positions.iter().map(|p| p).collect::>(), + Some(view_port) => positions + .iter() + .filter(|&p| view_port.contains(p)) + .collect::>(), + }; + for s in &self.space_db { let to = db.space(s.name())?; let mut p = Vec::with_capacity(count); - for position in positions { + for position in filtered.as_slice() { let position: Vec = Space::change_base(position, from, to)?.into(); p.push(to.encode(&position)?); } - let r = s.get_by_positions(&p, threshold_volume, resolution)?; + let r = s.get_by_positions(&p, parameters)?; let mut r = self.to_space_object(s.name(), r); Self::decode_positions(&mut r, to, db, output_space)?; @@ -238,11 +257,7 @@ impl Core { space_id: &str, ) -> ResultSet { let CoreQueryParameters { - db, - output_space, - threshold_volume, - resolution, - .. + db, output_space, .. } = parameters; let mut results = vec![]; @@ -256,7 +271,7 @@ impl Core { // let current_shape = shape.encode(current_space)?; // println!("current shape Encoded: {:?}", current_shape); - let r = s.get_by_shape(¤t_shape, threshold_volume, resolution)?; + let r = s.get_by_shape(¤t_shape, parameters)?; let mut r = self.to_space_object(s.name(), r); Self::decode_positions(&mut r, current_space, db, output_space)?; @@ -273,11 +288,7 @@ impl Core { S: Into, { let CoreQueryParameters { - db, - output_space, - threshold_volume, - resolution, - .. + db, output_space, .. } = parameters; let id: String = id.into(); @@ -293,7 +304,7 @@ impl Core { for s in &self.space_db { let current_space = db.space(s.name())?; - let r = s.get_by_id(offset, threshold_volume, resolution)?; + let r = s.get_by_id(offset, parameters)?; let mut r = self.to_space_object(s.name(), r); Self::decode_positions(&mut r, current_space, db, output_space)?; @@ -312,64 +323,68 @@ impl Core { S: Into, { let CoreQueryParameters { - db, - output_space, - threshold_volume, - resolution, - .. + db, output_space, .. } = parameters; let id: String = id.into(); let mut results = vec![]; + // Convert the view port to the encoded space coordinates + let view_port = parameters.view_port(Space::universe()); + if let Ok(offset) = self .properties .binary_search_by_key(&&id, |properties| properties.id()) { // Generate the search volume. Iterate over all reference spaces, to // retrieve a list of SpaceSetObjects linked to `id`, then iterate - // over the result to generate a list of positions. + // over the result to generate a list of positions in Universe. let search_volume = self .space_db .iter() - .filter_map( - |s| match s.get_by_id(offset, threshold_volume, resolution) { - Ok(v) => Some(v), + .filter_map(|s| { + match db.space(s.name()) { Err(_) => None, - }, - ) - .flat_map(|v| v) - .map(|o| o.position().clone()) - .collect::>(); + Ok(from) => match s.get_by_id(offset, parameters) { + Err(_) => None, + Ok(v) => { + // Convert the search Volume into Universe. + let mut p = vec![]; + for o in v { + if let Ok(position) = + Space::change_base(o.position(), from, Space::universe()) + { + p.push(position) + } + } - /* - let search_volume = self - .space_db - .iter() - .filter_map(|s| match s.get_by_id(offset, threshold_volume) { - Err(_) => None, - Ok(v) => Some(( - s.name(), - v.into_iter().map(|o| o.position()).collect::>(), - )), + Some(p) + } + }, + } }) - .filter_map(|(space_id, list)| match db.space(space_id) { - Err(_) => None, - Ok(space) => Some(( - space_id, - list.into_iter() - .map(|o| space.decode(o).into()) - .collect::>(), - )), - }).filter_map(|(space_id, list)|) - .collect::>(); - */ + .flat_map(|v| v); + + let search_volume = if let Some(view) = view_port { + search_volume + .filter(|p| view.contains(p)) + .collect::>() + } else { + search_volume.collect::>() + }; // Select based on the volume, and filter out the label position themselves. for s in &self.space_db { let to = db.space(s.name())?; + let mut p = vec![]; - let r = s.get_by_positions(&search_volume, threshold_volume, resolution)?; + // Convert the search Volume into the target space. + for position in &search_volume { + let position = Space::change_base(position, Space::universe(), to)?; + p.push(position); + } + + let r = s.get_by_positions(&p, parameters)?; let mut r = self.to_space_object(s.name(), r); Self::decode_positions(&mut r, to, db, output_space)?; diff --git a/src/database/space/position.rs b/src/database/space/position.rs index cde57bd..44081d5 100644 --- a/src/database/space/position.rs +++ b/src/database/space/position.rs @@ -1,3 +1,5 @@ +use std::cmp::Ordering; +use std::collections::HashSet; use std::fmt; use std::fmt::Display; use std::fmt::Formatter; @@ -13,7 +15,7 @@ use std::ops::SubAssign; use super::coordinate::Coordinate; -#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, Serialize)] pub enum Position { Position1(Coordinate), Position2([Coordinate; 2]), @@ -101,6 +103,50 @@ impl Display for Position { } } +impl PartialOrd for Position { + fn partial_cmp(&self, other: &Self) -> Option { + // Let's restrict for now to same-length vectors. + if self.dimensions() != other.dimensions() { + return None; + } + + let mut ordering = HashSet::with_capacity(self.dimensions()); + for k in 0..self.dimensions() { + ordering.insert(self[k].partial_cmp(&other[k])); + } + + if ordering.contains(&None) { + return None; + } + + let ordering = ordering.drain().filter_map(|v| v).collect::>(); + match ordering.len() { + 3 => None, + 2 => { + // The two values are, by construction different, which means we + // have the following possibilities as there are only GREATER, + // EQUAL and LESS in the enum: + // - LESS, GREATER + // - LESS, EQUAL + // - GREATER, EQUAL + // If one of the values is EQUAL, then the ordering will be the + // other value. + if ordering[0] == Ordering::Equal { + Some(ordering[1]) + } else if ordering[1] == Ordering::Equal { + Some(ordering[0]) + } else { + None + } + } + 1 => Some(ordering[0]), + // We can not have more than 3 elements, and if we have 0, it means + // we had only None in the list + _ => None, + } + } +} + impl Index for Position { type Output = Coordinate; @@ -164,6 +210,22 @@ impl Sub for Position { } } +impl Sub for &Position { + type Output = Position; + + fn sub(self, rhs: Self) -> Self::Output { + let dimensions = self.dimensions(); + assert_eq!(dimensions, rhs.dimensions()); + let mut v = Vec::with_capacity(dimensions); + + for k in 0..dimensions { + v.push(self[k] - rhs[k]); + } + + v.into() + } +} + impl SubAssign for Position { fn sub_assign(&mut self, rhs: Self) { let dimensions = self.dimensions(); @@ -271,6 +333,16 @@ impl From> for Position { } } +impl From<&Vec> for Position { + fn from(coordinates: &Vec) -> Self { + coordinates + .iter() + .map(|c| (*c).into()) + .collect::>() + .into() + } +} + impl From> for Position { fn from(coordinates: Vec) -> Self { coordinates diff --git a/src/database/space/shape.rs b/src/database/space/shape.rs index fe10d43..7f85616 100644 --- a/src/database/space/shape.rs +++ b/src/database/space/shape.rs @@ -85,7 +85,13 @@ impl Shape { } } - //pub fn inside(&self) {} + pub fn contains(&self, position: &Position) -> bool { + match self { + Shape::Point(reference) => reference == position, + Shape::HyperSphere(center, radius) => (position - center).norm() <= radius.f64(), + Shape::BoundingBox(lower, higher) => lower <= position && position <= higher, + } + } /* Original version proposed by Charles François Rey - 2019 ```perl diff --git a/src/database/space_db.rs b/src/database/space_db.rs index 3e21669..9595cfd 100644 --- a/src/database/space_db.rs +++ b/src/database/space_db.rs @@ -15,6 +15,7 @@ use super::space_index::SpaceFields; use super::space_index::SpaceIndex; use super::space_index::SpaceSetIndex; use super::space_index::SpaceSetObject; +use super::CoreQueryParameters; #[derive(Clone, Debug, Deserialize, Serialize)] pub struct SpaceDB { @@ -292,11 +293,13 @@ impl SpaceDB { self.lowest_resolution() } - pub fn get_resolution( - &self, - threshold_volume: &Option, - resolution: &Option>, - ) -> usize { + pub fn get_resolution(&self, parameters: &CoreQueryParameters) -> usize { + let CoreQueryParameters { + threshold_volume, + resolution, + .. + } = parameters; + // If a specific scale has been set, try to find it, otherwise use the // threshold volume to figure a default value, and fall back to the most // coarse resolution whenever nothing is specified. @@ -325,16 +328,29 @@ impl SpaceDB { pub fn get_by_id( &self, id: usize, - threshold_volume: &Option, - resolution: &Option>, + parameters: &CoreQueryParameters, ) -> Result, String> { // Is that ID referenced in the current space? if let Ok(offset) = self.values.binary_search(&id.into()) { - let index = self.get_resolution(threshold_volume, resolution); + let index = self.get_resolution(parameters); - let mut results = self.resolutions[index] + // Convert the view port to the encoded space coordinates + let space = parameters.db.space(&self.reference_space)?; + let view_port = parameters.view_port(space); + + // Select the objects + let objects = self.resolutions[index] .find_by_value(&SpaceFields::new(self.name().into(), offset.into())); + let mut results = if let Some(view_port) = view_port { + objects + .into_iter() + .filter(|o| view_port.contains(o.position())) + .collect::>() + } else { + objects + }; + // Convert the Value back to caller's references // Here we do not use decode() as we have a single id value to manage. for o in &mut results { @@ -351,17 +367,25 @@ impl SpaceDB { pub fn get_by_positions( &self, positions: &[Position], - threshold_volume: &Option, - resolution: &Option>, + parameters: &CoreQueryParameters, ) -> Result, String> { let index = self.get_resolution(threshold_volume, resolution); + // FIXME: Should I do it here, or add the assumption this is a clean list? + // Convert the view port to the encoded space coordinates + //let space = parameters.db.space(&self.reference_space)?; + //let view_port = parameters.view_port(space); + + // Select the objects let results = positions .iter() .flat_map(|position| self.resolutions[index].find(position)) .collect::>(); - Ok(self.decode(results)) + // Decode the Value reference + let results = self.decode_value(results); + + Ok(results) } // Search by Shape defining a volume: @@ -371,11 +395,20 @@ impl SpaceDB { pub fn get_by_shape( &self, shape: &Shape, - threshold_volume: &Option, - resolution: &Option>, + parameters: &CoreQueryParameters, ) -> Result, String> { let index = self.get_resolution(threshold_volume, resolution); - Ok(self.decode(self.resolutions[index].find_by_shape(&shape)?)) + // Convert the view port to the encoded space coordinates + let space = parameters.db.space(&self.reference_space)?; + let view_port = parameters.view_port(space); + + // Select the objects + let results = self.resolutions[index].find_by_shape(&shape, &view_port)?; + + // Decode the Value reference + let results = self.decode_value(results); + + Ok(results) } } diff --git a/src/database/space_index.rs b/src/database/space_index.rs index 3e09823..46fdb42 100644 --- a/src/database/space_index.rs +++ b/src/database/space_index.rs @@ -1,3 +1,5 @@ +use std::cmp::Ord; + use ironsea_index::IndexedOwned; use ironsea_table_vector::VectorTable; @@ -123,26 +125,89 @@ impl SpaceIndex { &self.scale } + // Inputs and Results are expressed in encoded space coordinates. pub fn find(&self, key: &Position) -> Vec { self.index.find(key) } + // Inputs and Results are expressed in encoded space coordinates. fn find_range(&self, start: &Position, end: &Position) -> Vec { self.index.find_range(start, end) } + // Inputs and Results are expressed in encoded space coordinates. pub fn find_by_value(&self, id: &SpaceFields) -> Vec { self.index.find_by_value(id) } - // The shape provided in arguments needs to be expressed in encoded space positions. - // Results are also in encoded space coordinates. - pub fn find_by_shape(&self, shape: &Shape) -> Result, String> { + /// Inputs and Results are also in encoded space coordinates. + pub fn find_by_shape( + &self, + shape: &Shape, + view_port: &Option, + ) -> Result, String> { match shape { - Shape::Point(position) => Ok(self.find(position)), - Shape::BoundingBox(lower, higher) => Ok(self.find_range(lower, higher)), + Shape::Point(position) => { + if let Some(mbb) = view_port { + if mbb.contains(position) { + Ok(self.find(position)) + } else { + Err(format!( + "View port '{:?}' does not contain '{:?}'", + mbb, position + )) + } + } else { + Ok(self.find(position)) + } + } + Shape::BoundingBox(bl, bh) => { + if let Some(mbb) = view_port { + match mbb { + Shape::BoundingBox(vl, vh) => { + // Compute the intersection of the two boxes. + let lower = bl.max(vl); + let higher = bh.min(vh); + if higher < lower { + Err(format!( + "View port '{:?}' does not intersect '{:?}'", + mbb, shape + )) + } else { + trace!( + "mbb {:?} shape {:?} lower {:?} higher {:?}", + mbb, + shape, + lower, + higher + ); + Ok(self.find_range(lower, higher)) + } + } + _ => Err(format!("Invalid view port shape '{:?}'", mbb)), + } + } else { + Ok(self.find_range(bl, bh)) + } + } Shape::HyperSphere(center, radius) => { - let (lower, higher) = shape.get_mbb(); + let (bl, bh) = &shape.get_mbb(); + let lower; + let higher; + + if let Some(mbb) = view_port { + match mbb { + Shape::BoundingBox(vl, vh) => { + // Compute the intersection of the two boxes. + lower = bl.max(vl); + higher = bh.min(vh); + } + _ => return Err(format!("Invalid view port shape '{:?}'", mbb)), + } + } else { + lower = bl; + higher = bh; + } // Filter out results using using a range query over the MBB, // then add the condition of the radius as we are working within