Push down one more layer CoreQueryParameters
* Push down the parameters in order for filtering to be done as early as possible w.r.t the view port. * Add view_port-based filtering in the lower-layers of the DB. * Implement PartialOrd on Position, to simplify point in box checks.
This commit is contained in:
@@ -15,6 +15,20 @@ pub struct CoreQueryParameters<'a> {
|
||||
pub resolution: Option<Vec<u32>>,
|
||||
}
|
||||
|
||||
impl CoreQueryParameters<'_> {
|
||||
pub fn view_port(&self, space: &Space) -> Option<Shape> {
|
||||
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::<Vec<_>>(),
|
||||
Some(view_port) => positions
|
||||
.iter()
|
||||
.filter(|&p| view_port.contains(p))
|
||||
.collect::<Vec<_>>(),
|
||||
};
|
||||
|
||||
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<f64> = 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<String>,
|
||||
{
|
||||
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<String>,
|
||||
{
|
||||
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::<Vec<_>>();
|
||||
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::<Vec<_>>(),
|
||||
)),
|
||||
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::<Vec<Position>>(),
|
||||
)),
|
||||
}).filter_map(|(space_id, list)|)
|
||||
.collect::<Vec<_>>();
|
||||
*/
|
||||
.flat_map(|v| v);
|
||||
|
||||
let search_volume = if let Some(view) = view_port {
|
||||
search_volume
|
||||
.filter(|p| view.contains(p))
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
search_volume.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
// 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)?;
|
||||
|
||||
@@ -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<Ordering> {
|
||||
// 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::<Vec<_>>();
|
||||
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<usize> 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<Vec<f64>> for Position {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Vec<f64>> for Position {
|
||||
fn from(coordinates: &Vec<f64>) -> Self {
|
||||
coordinates
|
||||
.iter()
|
||||
.map(|c| (*c).into())
|
||||
.collect::<Vec<Coordinate>>()
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u64>> for Position {
|
||||
fn from(coordinates: Vec<u64>) -> Self {
|
||||
coordinates
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<f64>,
|
||||
resolution: &Option<Vec<u32>>,
|
||||
) -> 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<f64>,
|
||||
resolution: &Option<Vec<u32>>,
|
||||
parameters: &CoreQueryParameters,
|
||||
) -> Result<Vec<SpaceSetObject>, 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::<Vec<SpaceSetObject>>()
|
||||
} 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<f64>,
|
||||
resolution: &Option<Vec<u32>>,
|
||||
parameters: &CoreQueryParameters,
|
||||
) -> Result<Vec<SpaceSetObject>, 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::<Vec<SpaceSetObject>>();
|
||||
|
||||
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<f64>,
|
||||
resolution: &Option<Vec<u32>>,
|
||||
parameters: &CoreQueryParameters,
|
||||
) -> Result<Vec<SpaceSetObject>, 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<SpaceSetObject> {
|
||||
self.index.find(key)
|
||||
}
|
||||
|
||||
// Inputs and Results are expressed in encoded space coordinates.
|
||||
fn find_range(&self, start: &Position, end: &Position) -> Vec<SpaceSetObject> {
|
||||
self.index.find_range(start, end)
|
||||
}
|
||||
|
||||
// Inputs and Results are expressed in encoded space coordinates.
|
||||
pub fn find_by_value(&self, id: &SpaceFields) -> Vec<SpaceSetObject> {
|
||||
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<Vec<SpaceSetObject>, String> {
|
||||
/// Inputs and Results are also in encoded space coordinates.
|
||||
pub fn find_by_shape(
|
||||
&self,
|
||||
shape: &Shape,
|
||||
view_port: &Option<Shape>,
|
||||
) -> Result<Vec<SpaceSetObject>, 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
|
||||
|
||||
Reference in New Issue
Block a user