1use crate::core::{DocId, NO_MORE_DOCS, Result, ScoreMode, Scorer, TwoPhaseIterator};
6
7use super::geo::{GeoPoint, GeoPointStore, haversine_km};
8use super::shape::GeoShapeStore;
9use crate::query::ast::SpatialRelation;
10use crate::query::{BoundQuery, Query, ScorerSupplier};
11use crate::search::searcher::Searcher;
12use crate::segment::reader::SegmentReader;
13
14pub struct GeoDistanceQuery {
16 pub field: String,
17 pub center: GeoPoint,
18 pub distance_km: f64,
19}
20
21impl Query for GeoDistanceQuery {
22 fn bind(&self, _: &Searcher, _: ScoreMode) -> Result<Box<dyn BoundQuery>> {
23 Ok(Box::new(BoundGeoDistanceQuery {
24 field: self.field.clone(),
25 center: self.center,
26 distance_km: self.distance_km,
27 }))
28 }
29}
30
31struct BoundGeoDistanceQuery {
32 field: String,
33 center: GeoPoint,
34 distance_km: f64,
35}
36
37impl BoundQuery for BoundGeoDistanceQuery {
38 fn scorer_supplier(&self, reader: &SegmentReader) -> Result<Option<Box<dyn ScorerSupplier>>> {
39 let field_id = match reader
40 .header()
41 .fields
42 .iter()
43 .find(|f| f.field_name == self.field)
44 .map(|f| f.field_id)
45 {
46 Some(id) => id,
47 None => return Ok(None),
48 };
49
50 let store = match reader.geo_points(field_id) {
51 Some(s) => s,
52 None => return Ok(None),
53 };
54
55 Ok(Some(Box::new(GeoDistanceScorerSupplier {
56 center: self.center,
57 distance_km: self.distance_km,
58 store,
59 })))
60 }
61}
62
63struct GeoDistanceScorerSupplier {
64 center: GeoPoint,
65 distance_km: f64,
66 store: GeoPointStore,
67}
68
69impl ScorerSupplier for GeoDistanceScorerSupplier {
70 fn cost(&self) -> u64 {
71 self.store.len() as u64
72 }
73
74 fn scorer(self: Box<Self>) -> Result<Box<dyn Scorer>> {
75 const KM_PER_DEG_LAT: f64 = 111.0;
77 let lat_delta = self.distance_km / KM_PER_DEG_LAT;
78 let min_lat = self.center.lat - lat_delta;
79 let max_lat = self.center.lat + lat_delta;
80
81 let candidates = self.store.docs_in_lat_range(min_lat, max_lat);
83 let mut matches = Vec::new();
84 for doc_id in candidates {
85 if let Some(point) = self.store.get(doc_id) {
86 let d = haversine_km(self.center, point);
87 if d <= self.distance_km {
88 matches.push((doc_id, d));
89 }
90 }
91 }
92
93 Ok(Box::new(GeoScorer { matches, pos: 0 }))
94 }
95}
96
97pub struct GeoBoundingBoxQuery {
99 pub field: String,
100 pub top_left: GeoPoint,
101 pub bottom_right: GeoPoint,
102}
103
104impl Query for GeoBoundingBoxQuery {
105 fn bind(&self, _: &Searcher, _: ScoreMode) -> Result<Box<dyn BoundQuery>> {
106 Ok(Box::new(BoundGeoBBoxQuery {
107 field: self.field.clone(),
108 top_left: self.top_left,
109 bottom_right: self.bottom_right,
110 }))
111 }
112}
113
114struct BoundGeoBBoxQuery {
115 field: String,
116 top_left: GeoPoint,
117 bottom_right: GeoPoint,
118}
119
120impl BoundQuery for BoundGeoBBoxQuery {
121 fn scorer_supplier(&self, reader: &SegmentReader) -> Result<Option<Box<dyn ScorerSupplier>>> {
122 let field_id = match reader
123 .header()
124 .fields
125 .iter()
126 .find(|f| f.field_name == self.field)
127 .map(|f| f.field_id)
128 {
129 Some(id) => id,
130 None => return Ok(None),
131 };
132
133 let store = match reader.geo_points(field_id) {
134 Some(s) => s,
135 None => return Ok(None),
136 };
137
138 Ok(Some(Box::new(GeoBBoxScorerSupplier {
139 top_left: self.top_left,
140 bottom_right: self.bottom_right,
141 store,
142 })))
143 }
144}
145
146struct GeoBBoxScorerSupplier {
147 top_left: GeoPoint,
148 bottom_right: GeoPoint,
149 store: GeoPointStore,
150}
151
152impl ScorerSupplier for GeoBBoxScorerSupplier {
153 fn cost(&self) -> u64 {
154 self.store.len() as u64
155 }
156
157 fn scorer(self: Box<Self>) -> Result<Box<dyn Scorer>> {
158 let candidates = self
160 .store
161 .docs_in_lat_range(self.bottom_right.lat, self.top_left.lat);
162 let mut matches = Vec::new();
163 for doc_id in candidates {
164 if let Some(point) = self.store.get(doc_id) {
165 if point.lon >= self.top_left.lon && point.lon <= self.bottom_right.lon {
166 matches.push((doc_id, 0.0));
167 }
168 }
169 }
170
171 Ok(Box::new(GeoScorer { matches, pos: 0 }))
172 }
173}
174
175struct GeoScorer {
177 matches: Vec<(u32, f64)>, pos: usize,
179}
180
181impl Scorer for GeoScorer {
182 fn doc_id(&self) -> DocId {
183 if self.pos < self.matches.len() {
184 DocId::new(self.matches[self.pos].0)
185 } else {
186 NO_MORE_DOCS
187 }
188 }
189 fn next(&mut self) -> DocId {
190 if self.pos < self.matches.len() {
191 self.pos += 1;
192 }
193 self.doc_id()
194 }
195 fn advance(&mut self, target: DocId) -> DocId {
196 while self.pos < self.matches.len() && self.matches[self.pos].0 < target.as_u32() {
197 self.pos += 1;
198 }
199 self.doc_id()
200 }
201 fn score(&mut self) -> f32 {
202 1.0
203 }
204 fn two_phase(&mut self) -> Option<&mut dyn TwoPhaseIterator> {
205 None
206 }
207}
208
209fn is_axis_aligned_rect(geom: &::geo::Geometry<f64>) -> bool {
215 if let ::geo::Geometry::Polygon(p) = geom {
216 if !p.interiors().is_empty() {
217 return false;
218 }
219 let coords = &p.exterior().0;
220 if coords.len() != 5 {
221 return false;
222 }
223 let xs: std::collections::BTreeSet<u64> =
225 coords[..4].iter().map(|c| c.x.to_bits()).collect();
226 let ys: std::collections::BTreeSet<u64> =
227 coords[..4].iter().map(|c| c.y.to_bits()).collect();
228 xs.len() == 2 && ys.len() == 2
229 } else {
230 false
231 }
232}
233
234pub struct GeoShapeQuery {
240 pub field: String,
241 pub query_shape: ::geo::Geometry<f64>,
242 pub query_bbox: (f64, f64, f64, f64),
243 pub relation: SpatialRelation,
244}
245
246impl Query for GeoShapeQuery {
247 fn bind(&self, _: &Searcher, _: ScoreMode) -> Result<Box<dyn BoundQuery>> {
248 Ok(Box::new(BoundGeoShapeQuery {
249 field: self.field.clone(),
250 query_shape: self.query_shape.clone(),
251 query_bbox: self.query_bbox,
252 relation: self.relation.clone(),
253 }))
254 }
255}
256
257struct BoundGeoShapeQuery {
258 field: String,
259 query_shape: ::geo::Geometry<f64>,
260 query_bbox: (f64, f64, f64, f64),
261 relation: SpatialRelation,
262}
263
264impl BoundQuery for BoundGeoShapeQuery {
265 fn scorer_supplier(&self, reader: &SegmentReader) -> Result<Option<Box<dyn ScorerSupplier>>> {
266 let field_id = match reader
267 .header()
268 .fields
269 .iter()
270 .find(|f| f.field_name == self.field)
271 .map(|f| f.field_id)
272 {
273 Some(id) => id,
274 None => return Ok(None),
275 };
276
277 let store = match reader.geo_shapes(field_id) {
278 Some(s) => s,
279 None => return Ok(None),
280 };
281
282 Ok(Some(Box::new(GeoShapeScorerSupplier {
283 query_shape: self.query_shape.clone(),
284 query_bbox: self.query_bbox,
285 relation: self.relation.clone(),
286 store,
287 })))
288 }
289}
290
291struct GeoShapeScorerSupplier {
292 query_shape: ::geo::Geometry<f64>,
293 query_bbox: (f64, f64, f64, f64),
294 relation: SpatialRelation,
295 store: GeoShapeStore,
296}
297
298impl GeoShapeScorerSupplier {
299 fn score_disjoint(self, rtree_data: &[u8]) -> Result<Box<dyn Scorer>> {
302 use ::geo::algorithm::Intersects;
303
304 let intersecting_candidates = GeoShapeStore::search_rtree(
305 rtree_data,
306 self.query_bbox,
307 self.store.shape_offsets_ref(),
308 );
309
310 let mut intersecting = std::collections::HashSet::new();
312 for doc_id in intersecting_candidates {
313 if let Some(doc_shape) = self.store.get_shape(doc_id) {
314 if self.query_shape.intersects(&doc_shape) {
315 intersecting.insert(doc_id);
316 }
317 }
318 }
319
320 let mut matches = Vec::new();
322 for doc_id in 0..self.store.len() as u32 {
323 if self.store.get_bbox(doc_id).is_some() && !intersecting.contains(&doc_id) {
324 matches.push((doc_id, 0.0));
325 }
326 }
327
328 Ok(Box::new(GeoScorer { matches, pos: 0 }))
329 }
330}
331
332impl ScorerSupplier for GeoShapeScorerSupplier {
333 fn cost(&self) -> u64 {
334 self.store.len() as u64
335 }
336
337 fn scorer(self: Box<Self>) -> Result<Box<dyn Scorer>> {
338 use ::geo::algorithm::{Contains, Intersects, Relate};
339
340 let rtree_data_owned;
342 let rtree_data = if !self.store.rtree_data().is_empty() {
343 self.store.rtree_data()
344 } else {
345 rtree_data_owned = self.store.build_rtree();
346 &rtree_data_owned
347 };
348 if self.relation == SpatialRelation::Disjoint {
351 let rtree_owned = if self.store.rtree_data().is_empty() {
352 self.store.build_rtree()
353 } else {
354 self.store.rtree_data().to_vec()
355 };
356 return self.score_disjoint(&rtree_owned);
357 }
358
359 let candidates = GeoShapeStore::search_rtree(
360 rtree_data,
361 self.query_bbox,
362 self.store.shape_offsets_ref(),
363 );
364
365 let (q_min_x, q_min_y, q_max_x, q_max_y) = self.query_bbox;
366
367 let query_is_rect = matches!(self.query_shape, ::geo::Geometry::Rect(_))
370 || is_axis_aligned_rect(&self.query_shape);
371
372 let mut matches = Vec::new();
373 for doc_id in candidates {
374 let doc_bbox = match self.store.get_bbox(doc_id) {
375 Some(bb) => bb,
376 None => continue,
377 };
378 let (d_min_x, d_min_y, d_max_x, d_max_y) = doc_bbox;
379
380 match self.relation {
382 SpatialRelation::Within | SpatialRelation::CoveredBy => {
383 if d_min_x < q_min_x
384 || d_min_y < q_min_y
385 || d_max_x > q_max_x
386 || d_max_y > q_max_y
387 {
388 continue;
389 }
390 }
391 SpatialRelation::Contains
392 | SpatialRelation::Covers
393 | SpatialRelation::ContainsProperly => {
394 if q_min_x < d_min_x
395 || q_min_y < d_min_y
396 || q_max_x > d_max_x
397 || q_max_y > d_max_y
398 {
399 continue;
400 }
401 }
402 _ => {}
403 }
404
405 if query_is_rect {
408 match self.relation {
409 SpatialRelation::Within | SpatialRelation::CoveredBy => {
410 matches.push((doc_id, 0.0));
411 continue;
412 }
413 _ => {}
414 }
415 }
416
417 if self.store.is_rect_shape(doc_id) {
420 let bbox_proves_contains = q_min_x >= d_min_x
421 && q_min_y >= d_min_y
422 && q_max_x <= d_max_x
423 && q_max_y <= d_max_y;
424 if bbox_proves_contains {
425 match self.relation {
426 SpatialRelation::Contains | SpatialRelation::Covers => {
427 matches.push((doc_id, 0.0));
428 continue;
429 }
430 _ => {}
431 }
432 }
433 }
434
435 match self.relation {
439 SpatialRelation::Within | SpatialRelation::CoveredBy => {
440 let corners = [
441 ::geo::Coord {
442 x: d_min_x,
443 y: d_min_y,
444 },
445 ::geo::Coord {
446 x: d_max_x,
447 y: d_min_y,
448 },
449 ::geo::Coord {
450 x: d_max_x,
451 y: d_max_y,
452 },
453 ::geo::Coord {
454 x: d_min_x,
455 y: d_max_y,
456 },
457 ];
458 let all_inside = corners
459 .iter()
460 .all(|c| self.query_shape.contains(&::geo::Point(*c)));
461 if all_inside {
462 matches.push((doc_id, 0.0));
463 continue;
464 }
465 }
466 _ => {}
467 }
468
469 let doc_shape = match self.store.get_shape(doc_id) {
470 Some(s) => s,
471 None => continue,
472 };
473
474 let matched = match self.relation {
477 SpatialRelation::Intersects => self.query_shape.intersects(&doc_shape),
478 SpatialRelation::Disjoint => !self.query_shape.intersects(&doc_shape),
479 SpatialRelation::Contains | SpatialRelation::Covers => {
480 doc_shape.contains(&self.query_shape)
481 }
482 SpatialRelation::Within | SpatialRelation::CoveredBy => {
483 self.query_shape.contains(&doc_shape)
484 }
485 _ => {
487 let im = doc_shape.relate(&self.query_shape);
488 match self.relation {
489 SpatialRelation::Touches => im.is_touches(),
490 SpatialRelation::Crosses => im.is_crosses(),
491 SpatialRelation::Overlaps => im.is_overlaps(),
492 SpatialRelation::Equals => im.is_equal_topo(),
493 SpatialRelation::Covers => im.is_covers(),
494 SpatialRelation::CoveredBy => im.is_coveredby(),
495 SpatialRelation::ContainsProperly => im.is_contains_properly(),
496 _ => unreachable!(),
497 }
498 }
499 };
500
501 if matched {
502 matches.push((doc_id, 0.0));
503 }
504 }
505
506 matches.sort_by_key(|m| m.0);
507 Ok(Box::new(GeoScorer { matches, pos: 0 }))
508 }
509}