1use serde::ser::{SerializeMap, Serializer};
20use serde::Serialize;
21
22use crate::{
23 json::{serialize_map_optional_kv, MergeSerialize, NoOuter, ShouldSkip},
24 units::{Distance, DistanceType, GeoBox, Location},
25};
26
27use super::{common::FieldBasedQuery, Query};
28
29#[derive(Debug, Serialize)]
30pub enum ShapeOption {
31 #[serde(rename = "shape")]
32 Shape(Shape),
33 #[serde(rename = "indexed_shape")]
34 IndexedShape(IndexedShape),
35 #[cfg(feature = "geo")]
36 #[serde(rename = "shape")]
37 Geojson(geojson::Geometry),
38}
39
40from!(Shape, ShapeOption, Shape);
41from!(IndexedShape, ShapeOption, IndexedShape);
42
43#[derive(Debug, Serialize)]
45pub struct GeoShapeQuery(FieldBasedQuery<Option<ShapeOption>, NoOuter>);
46
47impl Query {
48 pub fn build_geo_shape<A>(field: A) -> GeoShapeQuery
49 where
50 A: Into<String>,
51 {
52 GeoShapeQuery(FieldBasedQuery::new(field.into(), None, NoOuter))
53 }
54}
55
56impl GeoShapeQuery {
57 pub fn with_shape<A>(mut self, shape: A) -> Self
58 where
59 A: Into<Shape>,
60 {
61 self.0.inner = Some(ShapeOption::Shape(shape.into()));
62 self
63 }
64
65 pub fn with_indexed_shape<A>(mut self, indexed_shape: A) -> Self
66 where
67 A: Into<IndexedShape>,
68 {
69 self.0.inner = Some(ShapeOption::IndexedShape(indexed_shape.into()));
70 self
71 }
72
73 #[cfg(feature = "geo")]
74 pub fn with_geojson<A>(mut self, shape: A) -> Self
77 where
78 A: Into<geojson::Geometry>,
79 {
80 self.0.inner = Some(ShapeOption::Geojson(shape.into()));
81 self
82 }
83
84 build!(GeoShape);
85}
86
87#[derive(Debug, Serialize)]
89pub struct Shape {
90 #[serde(rename = "type")]
91 shape_type: String,
92 coordinates: Vec<(f64, f64)>,
93}
94
95impl Shape {
96 pub fn new<A: Into<String>>(shape_type: A, coordinates: Vec<(f64, f64)>) -> Shape {
97 Shape {
98 shape_type: shape_type.into(),
99 coordinates,
100 }
101 }
102}
103
104#[derive(Debug, Serialize)]
105pub struct IndexedShape {
106 id: String,
107 doc_type: String,
108 index: String,
109 path: String,
110}
111
112impl IndexedShape {
113 pub fn new<A, B, C, D>(id: A, doc_type: B, index: C, path: D) -> IndexedShape
114 where
115 A: Into<String>,
116 B: Into<String>,
117 C: Into<String>,
118 D: Into<String>,
119 {
120 IndexedShape {
121 id: id.into(),
122 doc_type: doc_type.into(),
123 index: index.into(),
124 path: path.into(),
125 }
126 }
127}
128
129#[derive(Debug, Serialize)]
131pub struct GeoBoundingBoxQuery(FieldBasedQuery<GeoBoundingBoxQueryInner, NoOuter>);
132
133#[derive(Debug, Default, Serialize)]
134pub struct GeoBoundingBoxQueryInner {
135 geo_box: GeoBox,
136 #[serde(skip_serializing_if = "ShouldSkip::should_skip")]
137 coerce: Option<bool>,
138 #[serde(skip_serializing_if = "ShouldSkip::should_skip")]
139 ignore_malformed: Option<bool>,
140 #[serde(skip_serializing_if = "ShouldSkip::should_skip", rename = "type")]
141 filter_type: Option<Type>,
142}
143
144impl Query {
145 pub fn build_geo_bounding_box<A, B>(field: A, geo_box: B) -> GeoBoundingBoxQuery
146 where
147 A: Into<String>,
148 B: Into<GeoBox>,
149 {
150 GeoBoundingBoxQuery(FieldBasedQuery::new(
151 field.into(),
152 GeoBoundingBoxQueryInner {
153 geo_box: geo_box.into(),
154 ..Default::default()
155 },
156 NoOuter,
157 ))
158 }
159}
160
161impl GeoBoundingBoxQuery {
162 add_inner_field!(with_coerce, coerce, bool);
163 add_inner_field!(with_ignore_malformed, ignore_malformed, bool);
164 add_inner_field!(with_type, filter_type, Type);
165
166 build!(GeoBoundingBox);
167}
168
169#[derive(Debug)]
171pub enum Type {
172 Indexed,
173 Memory,
174}
175
176impl Serialize for Type {
177 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
178 where
179 S: Serializer,
180 {
181 use self::Type::*;
182 match self {
183 Indexed => "indexed",
184 Memory => "memory",
185 }
186 .serialize(serializer)
187 }
188}
189
190#[derive(Debug, Serialize)]
194pub struct GeoDistanceQuery(FieldBasedQuery<Location, GeoDistanceQueryOuter>);
195
196#[derive(Debug, Default)]
197struct GeoDistanceQueryOuter {
198 distance: Distance,
199 distance_type: Option<DistanceType>,
200 optimize_bbox: Option<OptimizeBbox>,
201 coerce: Option<bool>,
202 ignore_malformed: Option<bool>,
203}
204
205impl MergeSerialize for GeoDistanceQueryOuter {
206 fn merge_serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
207 where
208 S: SerializeMap,
209 {
210 serializer.serialize_entry("distance", &self.distance)?;
211 serialize_map_optional_kv(serializer, "distance_type", &self.distance_type)?;
212 serialize_map_optional_kv(serializer, "optimize_bbox", &self.optimize_bbox)?;
213 serialize_map_optional_kv(serializer, "coerce", &self.coerce)?;
214 serialize_map_optional_kv(serializer, "ignore_malformed", &self.ignore_malformed)?;
215 Ok(())
216 }
217}
218
219impl Query {
220 pub fn build_geo_distance<A, B, C>(field: A, location: B, distance: C) -> GeoDistanceQuery
221 where
222 A: Into<String>,
223 B: Into<Location>,
224 C: Into<Distance>,
225 {
226 let outer = GeoDistanceQueryOuter {
227 distance: distance.into(),
228 ..Default::default()
229 };
230 GeoDistanceQuery(FieldBasedQuery::new(field.into(), location.into(), outer))
231 }
232}
233
234impl GeoDistanceQuery {
235 add_outer_field!(with_distance_type, distance_type, DistanceType);
236 add_outer_field!(with_optimize_bbox, optimize_bbox, OptimizeBbox);
237 add_outer_field!(with_coerce, coerce, bool);
238 add_outer_field!(with_ignore_malformed, ignore_malformed, bool);
239
240 build!(GeoDistance);
241}
242
243#[derive(Debug)]
245pub enum OptimizeBbox {
246 Memory,
247 Indexed,
248 None,
249}
250
251impl Serialize for OptimizeBbox {
252 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
253 where
254 S: Serializer,
255 {
256 use self::OptimizeBbox::*;
257 match self {
258 Memory => "memory".serialize(serializer),
259 Indexed => "indexed".serialize(serializer),
260 None => "none".serialize(serializer),
261 }
262 }
263}
264
265#[derive(Debug, Serialize)]
267pub struct GeoPolygonQuery(FieldBasedQuery<GeoPolygonQueryInner, NoOuter>);
268
269#[derive(Debug, Default, Serialize)]
270pub struct GeoPolygonQueryInner {
271 points: Vec<Location>,
272 #[serde(skip_serializing_if = "ShouldSkip::should_skip")]
273 coerce: Option<bool>,
274 #[serde(skip_serializing_if = "ShouldSkip::should_skip")]
275 ignore_malformed: Option<bool>,
276}
277
278impl Query {
279 pub fn build_geo_polygon<A, B>(field: A, points: B) -> GeoPolygonQuery
280 where
281 A: Into<String>,
282 B: Into<Vec<Location>>,
283 {
284 GeoPolygonQuery(FieldBasedQuery::new(
285 field.into(),
286 GeoPolygonQueryInner {
287 points: points.into(),
288 ..Default::default()
289 },
290 NoOuter,
291 ))
292 }
293}
294
295impl GeoPolygonQuery {
296 add_inner_field!(with_coerce, coerce, bool);
297 add_inner_field!(with_ignore_malformed, ignore_malformed, bool);
298
299 build!(GeoPolygon);
300}
301
302#[derive(Debug, Serialize)]
304pub struct GeohashCellQuery(FieldBasedQuery<Location, GeohashCellQueryOuter>);
305
306#[derive(Debug, Default)]
307pub struct GeohashCellQueryOuter {
308 precision: Option<Precision>,
309 neighbors: Option<bool>,
310}
311
312impl MergeSerialize for GeohashCellQueryOuter {
313 fn merge_serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
314 where
315 S: SerializeMap,
316 {
317 serialize_map_optional_kv(serializer, "precision", &self.precision)?;
318 serialize_map_optional_kv(serializer, "neighbors", &self.neighbors)?;
319 Ok(())
320 }
321}
322
323impl Query {
324 pub fn build_geohash_cell<A, B>(field: A, location: B) -> GeohashCellQuery
325 where
326 A: Into<String>,
327 B: Into<Location>,
328 {
329 GeohashCellQuery(FieldBasedQuery::new(
330 field.into(),
331 location.into(),
332 Default::default(),
333 ))
334 }
335}
336
337impl GeohashCellQuery {
338 add_outer_field!(with_precision, precision, Precision);
339 add_outer_field!(with_neighbors, neighbors, bool);
340
341 build!(GeohashCell);
342}
343
344#[derive(Debug)]
345pub enum Precision {
346 Geohash(u64),
347 Distance(Distance),
348}
349
350impl Default for Precision {
351 fn default() -> Self {
352 Precision::Distance(Default::default())
353 }
354}
355
356from!(u64, Precision, Geohash);
357from!(Distance, Precision, Distance);
358
359impl Serialize for Precision {
360 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
361 where
362 S: Serializer,
363 {
364 use self::Precision::*;
365 match self {
366 Geohash(precision) => precision.serialize(serializer),
367 Distance(ref dist) => dist.serialize(serializer),
368 }
369 }
370}
371
372#[cfg(test)]
373#[cfg(feature = "geo")]
374pub mod tests {
375 use crate::operations::mapping::{Analysis, MappingOperation, Settings};
376 use crate::operations::search::SearchResult;
377 use crate::query::Query;
378 use crate::tests::{clean_db, make_client};
379 use crate::Client;
380 use serde::{Deserialize, Serialize};
381 use std::collections::HashMap;
382
383 #[derive(Debug, Serialize, Deserialize)]
384 pub struct GeoTestDocument {
385 pub str_field: String,
386 pub geojson_field: geojson::Geometry,
387 }
388
389 impl Default for GeoTestDocument {
390 fn default() -> GeoTestDocument {
391 GeoTestDocument {
392 str_field: "null island".to_owned(),
393 geojson_field: geojson::Geometry::new(geojson::Value::Point(vec![0.0, 0.0])),
394 }
395 }
396 }
397
398 impl GeoTestDocument {
399 pub fn with_str_field(mut self, s: &str) -> GeoTestDocument {
400 self.str_field = s.to_owned();
401 self
402 }
403
404 pub fn with_point(mut self, p: Vec<f64>) -> GeoTestDocument {
405 self.geojson_field = geojson::Geometry::new(geojson::Value::Point(p));
406 self
407 }
408 }
409
410 pub fn setup_test_data(mut client: &mut Client, index_name: &str) {
411 let mut mapping = HashMap::new();
412 let mut doc = HashMap::new();
413 let mut geo_field = HashMap::new();
414 let mut str_field = HashMap::new();
415 str_field.insert("type", "string");
416 geo_field.insert("type", "geo_shape");
417 doc.insert("str_field", str_field);
418 doc.insert("geojson_field", geo_field);
419 mapping.insert("geo_test_type", doc);
420
421 let settings = Settings {
422 number_of_shards: 1,
423 analysis: Analysis {
424 filter: serde_json::json!({}).as_object().unwrap().clone(),
425 analyzer: serde_json::json!({}).as_object().unwrap().clone(),
426 .. Default::default()
427 },
428 };
429
430 let _ = client.delete_index(index_name);
433
434 let result = MappingOperation::new(&mut client, index_name)
435 .with_mapping(&mapping)
436 .with_settings(&settings)
437 .send();
438 result.unwrap();
439 let documents = vec![
440 GeoTestDocument::default(),
441 GeoTestDocument::default()
442 .with_str_field("p1")
443 .with_point(vec![1.0, 1.0]),
444 GeoTestDocument::default()
445 .with_str_field("p2")
446 .with_point(vec![5.0, 1.0]),
447 ];
448 for doc in documents.iter() {
449 client
450 .index(index_name, "geo_test_type")
451 .with_doc(doc)
452 .send()
453 .unwrap();
454 }
455 client.refresh().with_indexes(&[index_name]).send().unwrap();
456 }
457
458 #[test]
459 fn test_geoshape_search_point() {
460 let index_name = "test_geoshape_search_point";
461 let mut client = make_client();
462
463 clean_db(&mut client, index_name);
464 setup_test_data(&mut client, index_name);
465
466 let all_results: SearchResult<GeoTestDocument> = client
467 .search_query()
468 .with_indexes(&[index_name])
469 .with_query(
470 &Query::build_geo_shape("geojson_field")
471 .with_geojson(geojson::Geometry::new(geojson::Value::Point(vec![
472 0.0, 0.0,
473 ])))
474 .build(),
475 )
476 .send()
477 .unwrap();
478 assert_eq!(1, all_results.hits.total);
479 }
480
481 #[test]
482 fn test_geoshape_search_polygon() {
483 let index_name = "test_geoshape_search_polygon";
484 let mut client = make_client();
485
486 clean_db(&mut client, index_name);
487 setup_test_data(&mut client, index_name);
488
489 let all_results: SearchResult<GeoTestDocument> = client
490 .search_query()
491 .with_indexes(&[index_name])
492 .with_query(
493 &Query::build_geo_shape("geojson_field")
494 .with_geojson(geojson::Geometry::new(geojson::Value::Polygon(vec![vec![
495 vec![1.0, 1.0],
496 vec![1.0, -1.0],
497 vec![-1.0, -1.0],
498 vec![-1.0, 1.0],
499 vec![1.0, 1.0],
500 ]])))
501 .build(),
502 )
503 .send()
504 .unwrap();
505 assert_eq!(2, all_results.hits.total);
506 }
507}