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:
2019-10-18 10:59:50 +02:00
parent 26ea443a33
commit 750857a5bb
5 changed files with 269 additions and 78 deletions

View File

@@ -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(&current_shape, threshold_volume, resolution)?;
let r = s.get_by_shape(&current_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)?;

View File

@@ -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

View File

@@ -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

View File

@@ -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)
}
}

View File

@@ -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