Skip to content

Commit 3c77949

Browse files
Merge pull request #330 from geo-engine/point-clustering
Point clustering
2 parents 272cd90 + 0b261d1 commit 3c77949

File tree

19 files changed

+3058
-1
lines changed

19 files changed

+3058
-1
lines changed

datatypes/src/collections/feature_collection_builder.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,42 @@ where
227227
Ok(())
228228
}
229229

230+
/// Append a null to `column` if possible
231+
pub fn push_null(&mut self, column: &str) -> Result<()> {
232+
// also checks that column exists
233+
let data_builder = if let Some(builder) = self.builders.get_mut(column) {
234+
builder
235+
} else {
236+
return Err(FeatureCollectionError::ColumnDoesNotExist {
237+
name: column.to_string(),
238+
}
239+
.into());
240+
};
241+
242+
match self.types.get(column).expect("checked before") {
243+
FeatureDataType::Category => {
244+
let category_builder: &mut UInt8Builder = downcast_mut_array(data_builder.as_mut());
245+
category_builder.append_null()?;
246+
}
247+
FeatureDataType::Int => {
248+
let category_builder: &mut Int64Builder = downcast_mut_array(data_builder.as_mut());
249+
category_builder.append_null()?;
250+
}
251+
FeatureDataType::Float => {
252+
let category_builder: &mut Float64Builder =
253+
downcast_mut_array(data_builder.as_mut());
254+
category_builder.append_null()?;
255+
}
256+
FeatureDataType::Text => {
257+
let category_builder: &mut StringBuilder =
258+
downcast_mut_array(data_builder.as_mut());
259+
category_builder.append_null()?;
260+
}
261+
}
262+
263+
Ok(())
264+
}
265+
230266
/// Indicate a finished row
231267
pub fn finish_row(&mut self) {
232268
self.rows += 1;

datatypes/src/operations/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
pub mod image;
22
pub mod reproject;
3+
mod spatial_relation;
4+
5+
pub use spatial_relation::Contains;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/// This method returns `true` iff `Self` contains the input `T`.
2+
/// It is valid if the `T` touches the `Self`'s borders.
3+
pub trait Contains<T> {
4+
fn contains(&self, other: &T) -> bool;
5+
}
6+
7+
/// This method returns `true` iff does this `Self` intersect with the input `T`
8+
pub trait Intersects<T> {
9+
fn contains(&self, other: &T) -> bool;
10+
}

datatypes/src/primitives/bounding_box.rs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,35 @@ impl BoundingBox2D {
124124
BoundingBox2D::new_unchecked(lower_left_coordinate, upper_right_coordinate)
125125
}
126126

127+
/// Creates a new bounding box with `upper_left` and `lower_right` coordinates
128+
/// This is usually used with raster data and matches with the gdal geotransform
129+
///
130+
/// # Examples
131+
///
132+
/// ```
133+
/// use geoengine_datatypes::primitives::{Coordinate2D, BoundingBox2D};
134+
///
135+
/// let ul = Coordinate2D::new(1.0, 2.0);
136+
/// let lr = Coordinate2D::new(2.0, 1.0);
137+
/// let bbox = BoundingBox2D::new_upper_left_lower_right(ul, lr).unwrap();
138+
/// ```
139+
///
140+
/// # Errors
141+
///
142+
/// This constructor fails if the order of coordinates is not correct
143+
///
144+
pub fn new_from_center(
145+
center: Coordinate2D,
146+
half_width: f64,
147+
half_height: f64,
148+
) -> Result<Self> {
149+
// TODO: fail if half_width or half_height is negative
150+
151+
let lower_left_coordinate = (center.x - half_width, center.y - half_height).into();
152+
let upper_right_coordinate = (center.x + half_width, center.y + half_height).into();
153+
BoundingBox2D::new(lower_left_coordinate, upper_right_coordinate)
154+
}
155+
127156
/// Checks if a coordinate is located inside the bounding box
128157
///
129158
/// # Examples
@@ -340,6 +369,49 @@ impl BoundingBox2D {
340369
f
341370
})
342371
}
372+
373+
/// This method generates four new `BoundingBox2D`s by splitting the current one in four quadrants.
374+
pub fn split_into_quarters(&self) -> (Self, Self, Self, Self) {
375+
let half_width = self.size_x() / 2.;
376+
let half_height = self.size_x() / 2.;
377+
378+
let upper_left = Self::new_unchecked(
379+
Coordinate2D::new(
380+
self.lower_left_coordinate.x,
381+
self.lower_left_coordinate.y + half_height,
382+
),
383+
Coordinate2D::new(
384+
self.lower_left_coordinate.x + half_width,
385+
self.upper_right_coordinate.y,
386+
),
387+
);
388+
let upper_right = Self::new_unchecked(
389+
Coordinate2D::new(
390+
self.lower_left_coordinate.x + half_width,
391+
self.lower_left_coordinate.y + half_height,
392+
),
393+
self.upper_right_coordinate,
394+
);
395+
let lower_left = Self::new_unchecked(
396+
self.lower_left_coordinate,
397+
Coordinate2D::new(
398+
self.lower_left_coordinate.x + half_width,
399+
self.lower_left_coordinate.y + half_height,
400+
),
401+
);
402+
let lower_right = Self::new_unchecked(
403+
Coordinate2D::new(
404+
self.lower_left_coordinate.x + half_width,
405+
self.lower_left_coordinate.y,
406+
),
407+
Coordinate2D::new(
408+
self.upper_right_coordinate.x,
409+
self.lower_left_coordinate.y + half_height,
410+
),
411+
);
412+
413+
(lower_left, lower_right, upper_left, upper_right)
414+
}
343415
}
344416

345417
impl AxisAlignedRectangle for BoundingBox2D {
@@ -481,6 +553,7 @@ impl TryFrom<BoundingBox2D> for gdal::vector::Geometry {
481553
mod tests {
482554

483555
use crate::primitives::{AxisAlignedRectangle, BoundingBox2D, Coordinate2D, SpatialBounded};
556+
484557
#[test]
485558
#[allow(clippy::float_cmp)]
486559
fn bounding_box_new() {
@@ -985,4 +1058,36 @@ mod tests {
9851058
let bbox = BoundingBox2D::from_coord_ref_iter(coordinates.iter()).unwrap();
9861059
assert_eq!(bbox, expected);
9871060
}
1061+
1062+
#[test]
1063+
fn test_split_into_quarters() {
1064+
let bbox = BoundingBox2D::new((-50., -50.).into(), (50., 50.).into()).unwrap();
1065+
1066+
let (lower_left, lower_right, upper_left, upper_right) = bbox.split_into_quarters();
1067+
1068+
assert_eq!(
1069+
upper_left,
1070+
BoundingBox2D::new((-50., 0.).into(), (0., 50.).into()).unwrap()
1071+
);
1072+
assert_eq!(
1073+
upper_right,
1074+
BoundingBox2D::new((0., 0.).into(), (50., 50.).into()).unwrap()
1075+
);
1076+
assert_eq!(
1077+
lower_left,
1078+
BoundingBox2D::new((-50., -50.).into(), (0., 0.).into()).unwrap()
1079+
);
1080+
assert_eq!(
1081+
lower_right,
1082+
BoundingBox2D::new((0., -50.).into(), (50., 0.).into()).unwrap()
1083+
);
1084+
}
1085+
1086+
#[test]
1087+
fn test_new_from_center() {
1088+
assert_eq!(
1089+
BoundingBox2D::new_from_center((0., 0.).into(), 50., 50.).unwrap(),
1090+
BoundingBox2D::new((-50., -50.).into(), (50., 50.).into()).unwrap(),
1091+
);
1092+
}
9881093
}

0 commit comments

Comments
 (0)