Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement geo_shape query #97

Merged
merged 1 commit into from Jan 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
32 changes: 22 additions & 10 deletions src/search/params/geo_shapes.rs
Expand Up @@ -5,68 +5,80 @@ use crate::search::*;
/// Note: Elasticsearch uses WGS-84 coordinates only
#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
pub struct PointGeoShape {
coordinates: GeoCoordinate,
/// Coordinates
pub coordinates: GeoCoordinate,
}

/// An arbitrary line given two or more points
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct LineStringGeoShape {
coordinates: Vec<GeoCoordinate>,
/// Coordinates
pub coordinates: Vec<GeoCoordinate>,
}

/// A closed polygon whose first and last point must match, thus requiring
/// `n + 1` vertices to create an `n-sided` polygon and a minimum of `4`
/// vertices
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct PolygonGeoShape {
coordinates: Vec<Vec<GeoCoordinate>>,
/// Coordinates
pub coordinates: Vec<Vec<GeoCoordinate>>,
}

/// An array of unconnected, but likely related points
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct MultiPointGeoShape {
coordinates: Vec<GeoCoordinate>,
/// Coordinates
pub coordinates: Vec<GeoCoordinate>,
}

/// An array of separate linestrings
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct MultiLineStringGeoShape {
coordinates: Vec<Vec<GeoCoordinate>>,
/// Coordinates
pub coordinates: Vec<Vec<GeoCoordinate>>,
}

/// An array of separate polygons
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct MultiPolygonGeoShape {
coordinates: Vec<Vec<Vec<GeoCoordinate>>>,
/// Coordinates
pub coordinates: Vec<Vec<Vec<GeoCoordinate>>>,
}

/// A bounding rectangle, or envelope, specified by specifying only
/// the top left and bottom right points.
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct EnvelopeGeoShape {
coordinates: Vec<GeoCoordinate>,
/// Coordinates
pub coordinates: Vec<GeoCoordinate>,
}

/// A circle specified by a center point and radius with units,
/// which default to `METERS`
#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
pub struct CircleGeoShape {
coordinates: GeoCoordinate,
radius: Distance,
/// Coordinates
pub coordinates: GeoCoordinate,

/// Circle radius
pub radius: Distance,
}

/// A GeoJSON shape similar to the `multi*` shapes except that multiple types
/// can coexist (e.g., a Point and a LineString)
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct GeometryCollection {
geometries: Vec<GeoShape>,
/// A collection of geo shapes
pub geometries: Vec<GeoShape>,
}

/// The `geo_shape` data type facilitates the indexing of and searching with
/// arbitrary geo shapes such as rectangles and polygons. It should be used
/// when either the data being indexed or the queries being executed contain
/// shapes other than just points.
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(tag = "type")]
pub enum GeoShape {
/// A single geographic coordinate
///
Expand Down
150 changes: 150 additions & 0 deletions src/search/queries/geo/geo_shape_query.rs
@@ -0,0 +1,150 @@
use crate::search::*;
use crate::util::*;
use serde::Serialize;

/// Filter documents indexed using the `geo_shape` or `geo_point` type.
///
/// Requires the
/// [`geo_shape` mapping](https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-shape.html)
/// or the
/// [`geo_point` mapping](https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-point.html).
///
/// The `geo_shape` query uses the same grid square representation as the
/// `geo_shape` mapping to find documents that have a shape that intersects
/// with the query shape. It will also use the same Prefix Tree configuration
/// as defined for the field mapping.
///
/// <https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-geo-shape-query.html>
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct GeoShapeQuery {
#[serde(rename = "geo_shape")]
inner: Inner,
}

#[derive(Debug, Clone, PartialEq, Serialize)]
struct Inner {
#[serde(flatten)]
pair: KeyValuePair<String, Shape>,

#[serde(skip_serializing_if = "ShouldSkip::should_skip")]
ignore_unmapped: Option<bool>,

#[serde(skip_serializing_if = "ShouldSkip::should_skip")]
boost: Option<Boost>,

#[serde(skip_serializing_if = "ShouldSkip::should_skip")]
_name: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Serialize)]
struct Shape {
shape: GeoShape,

#[serde(skip_serializing_if = "ShouldSkip::should_skip")]
relation: Option<SpatialRelation>,
}

impl Query {
/// Creates an instance of [`GeoShapeQuery`]
///
/// - `field` - Field you wish to search
/// - `shape` - SHape you with to search
pub fn geo_shape<S, T>(field: S, shape: T) -> GeoShapeQuery
where
S: ToString,
T: Into<GeoShape>,
{
GeoShapeQuery {
inner: Inner {
pair: KeyValuePair::new(
field.to_string(),
Shape {
shape: shape.into(),
relation: None,
},
),
ignore_unmapped: None,
boost: None,
_name: None,
},
}
}
}

impl GeoShapeQuery {
/// The [geo_shape strategy](https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-shape.html#spatial-strategy)
/// mapping parameter determines which spatial relation operators may be
/// used at search time.
pub fn relation(mut self, relation: SpatialRelation) -> Self {
self.inner.pair.value.relation = Some(relation);
self
}

/// When set to true the `ignore_unmapped` option will ignore an unmapped
/// field and will not match any documents for this query. This can be
/// useful when querying multiple indexes which might have different
/// mappings. When set to `false` (the default value) the query will throw
/// an exception if the field is not mapped.
pub fn ignore_unmapped(mut self, ignore_unmapped: bool) -> Self {
self.inner.ignore_unmapped = Some(ignore_unmapped);
self
}

add_boost_and_name!();
}

impl ShouldSkip for GeoShapeQuery {}

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

#[test]
fn test_serialization() {
assert_serialize(
Query::geo_shape(
"pin.location",
GeoShape::Point(PointGeoShape {
coordinates: GeoCoordinate::from([2.2, 1.1]),
}),
),
json!({
"geo_shape": {
"pin.location": {
"shape": {
"type": "point",
"coordinates": [2.2, 1.1]
}
},
}
}),
);

assert_serialize(
Query::geo_shape(
"pin.location",
GeoShape::Point(PointGeoShape {
coordinates: GeoCoordinate::from([2.2, 1.1]),
}),
)
.boost(2)
.name("test")
.ignore_unmapped(true)
.relation(SpatialRelation::Within),
json!({
"geo_shape": {
"_name": "test",
"boost": 2,
"ignore_unmapped": true,
"pin.location": {
"shape": {
"type": "point",
"coordinates": [2.2, 1.1]
},
"relation": "WITHIN"
},
}
}),
);
}
}
2 changes: 2 additions & 0 deletions src/search/queries/geo/mod.rs
Expand Up @@ -4,7 +4,9 @@
mod geo_bounding_box_query;
mod geo_distance_query;
mod geo_shape_lookup_query;
mod geo_shape_query;

pub use self::geo_bounding_box_query::*;
pub use self::geo_distance_query::*;
pub use self::geo_shape_lookup_query::*;
pub use self::geo_shape_query::*;
1 change: 1 addition & 0 deletions src/search/queries/mod.rs
Expand Up @@ -135,6 +135,7 @@ query!(
GeoDistance(GeoDistanceQuery),
GeoBoundingBox(GeoBoundingBoxQuery),
GeoShapeLookup(GeoShapeLookupQuery),
GeoShape(GeoShapeQuery),
Json(JsonQuery),
Wrapper(WrapperQuery),
);
Expand Down