spatial_join/
index.rs

1use std::convert::TryInto;
2
3use smallvec::SmallVec;
4
5#[cfg(feature = "parallel")]
6use rayon::prelude::*;
7
8use crate::rtrees::Envelope;
9use crate::{
10    Config, Error, Interaction, ProxMapGeoRow, ProxMapRow, SJoinGeoRow, SJoinRow, SpatialIndex,
11    SplitGeoSeq,
12};
13#[cfg(feature = "parallel")]
14use crate::{Par, ParSpatialIndex};
15
16use crate::relates::Relates;
17
18macro_rules! chain {
19    ($thing:expr) => ($thing);
20    ($head:expr, $($tail:expr),+) => ($head.chain(chain!($($tail),+)));
21
22}
23
24macro_rules! join_outer {
25    ($big:expr;
26     $geo_big:ident, $ext_index_big:ident, $env:ident;
27     $expr_copyable:expr, $expr_noncopyable:expr) => {
28        chain!(
29            $big.geos
30                .points
31                .into_iter()
32                .zip($big.indexes.points.into_iter())
33                .flat_map(move |($geo_big, $ext_index_big)| {
34                    let $env = $geo_big.to_env();
35                    $expr_copyable
36                }),
37            $big.geos
38                .lines
39                .into_iter()
40                .zip($big.indexes.lines.into_iter())
41                .flat_map(move |($geo_big, $ext_index_big)| {
42                    let $env = $geo_big.to_env();
43                    $expr_copyable
44                }),
45            $big.geos
46                .rects
47                .into_iter()
48                .zip($big.indexes.rects.into_iter())
49                .flat_map(move |($geo_big, $ext_index_big)| {
50                    let $env = $geo_big.to_env();
51                    $expr_copyable
52                }),
53            $big.geos
54                .tris
55                .into_iter()
56                .zip($big.indexes.tris.into_iter())
57                .flat_map(move |($geo_big, $ext_index_big)| {
58                    let $env = $geo_big.to_env();
59                    $expr_copyable
60                }),
61            $big.geos
62                .polys
63                .into_iter()
64                .zip($big.indexes.polys.into_iter())
65                .flat_map(move |($geo_big, $ext_index_big)| {
66                    let $env = $geo_big.to_env();
67                    let $geo_big = &$geo_big;
68                    $expr_noncopyable.into_iter()
69                }),
70            $big.geos
71                .line_strings
72                .into_iter()
73                .zip($big.indexes.line_strings.into_iter())
74                .flat_map(move |($geo_big, $ext_index_big)| {
75                    let $env = $geo_big.to_env();
76                    let $geo_big = &$geo_big;
77                    $expr_noncopyable.into_iter()
78                })
79        )
80    };
81}
82
83#[cfg(feature = "parallel")]
84macro_rules! par_join_outer {
85    ($big:expr;
86     $geo_big:ident, $ext_index_big:ident, $env:ident;
87     $expr_copyable:expr, $expr_noncopyable:expr) => {{
88        chain!(
89            $big.geos
90                .points
91                .into_par_iter()
92                .zip($big.indexes.points.into_par_iter())
93                .flat_map(move |($geo_big, $ext_index_big)| {
94                    let $env = $geo_big.to_env();
95                    ($expr_copyable).par_bridge()
96                }),
97            $big.geos
98                .lines
99                .into_par_iter()
100                .zip($big.indexes.lines.into_par_iter())
101                .flat_map(move |($geo_big, $ext_index_big)| {
102                    let $env = $geo_big.to_env();
103                    ($expr_copyable).par_bridge()
104                }),
105            $big.geos
106                .rects
107                .into_par_iter()
108                .zip($big.indexes.rects.into_par_iter())
109                .flat_map(move |($geo_big, $ext_index_big)| {
110                    let $env = $geo_big.to_env();
111                    ($expr_copyable).par_bridge()
112                }),
113            $big.geos
114                .tris
115                .into_par_iter()
116                .zip($big.indexes.tris.into_par_iter())
117                .flat_map(move |($geo_big, $ext_index_big)| {
118                    let $env = $geo_big.to_env();
119                    ($expr_copyable).par_bridge()
120                }),
121            $big.geos
122                .polys
123                .into_par_iter()
124                .zip($big.indexes.polys.into_par_iter())
125                .flat_map(move |($geo_big, $ext_index_big)| {
126                    let $env = $geo_big.to_env();
127                    let $geo_big = &$geo_big;
128                    $expr_noncopyable.into_iter().par_bridge()
129                }),
130            $big.geos
131                .line_strings
132                .into_par_iter()
133                .zip($big.indexes.line_strings.into_par_iter())
134                .flat_map(move |($geo_big, $ext_index_big)| {
135                    let $env = $geo_big.to_env();
136                    let $geo_big = &$geo_big;
137                    $expr_noncopyable.into_iter().par_bridge()
138                })
139        )
140    }};
141}
142
143macro_rules! join_inner_copyable {
144    ($pm:expr;
145     $geo_big:ident, $ext_index_big:ident, $env:ident,
146     $geo_small:ident, $ext_index_small:ident;
147     $expr:expr) => {{
148        chain!(
149            $pm.point_tree
150                .locate_in_envelope_intersecting(&$env)
151                .map(|fake| fake.id)
152                .filter_map({
153                    let $geo_big = $geo_big.clone();
154                    move |index_small| {
155                        let $geo_small = &$pm.small.geos.points[index_small];
156                        let $ext_index_small = $pm.small.indexes.points.get(index_small);
157                        let $geo_big = &$geo_big;
158                        $expr
159                    }
160                }),
161            $pm.line_tree
162                .locate_in_envelope_intersecting(&$env)
163                .map(|fake| fake.id)
164                .filter_map({
165                    let $geo_big = $geo_big.clone();
166                    move |index_small| {
167                        let $geo_small = &$pm.small.geos.lines[index_small];
168                        let $ext_index_small = $pm.small.indexes.lines.get(index_small);
169                        let $geo_big = &$geo_big;
170                        $expr
171                    }
172                }),
173            $pm.poly_tree
174                .locate_in_envelope_intersecting(&$env)
175                .map(|fake| fake.id)
176                .filter_map({
177                    move |index_small| {
178                        let $geo_small = &$pm.small.geos.polys[index_small];
179                        let $ext_index_small = $pm.small.indexes.polys.get(index_small);
180                        let $geo_big = &$geo_big;
181                        $expr
182                    }
183                }),
184            $pm.ls_tree
185                .locate_in_envelope_intersecting(&$env)
186                .map(|fake| fake.id)
187                .filter_map({
188                    move |index_small| {
189                        let $geo_small = &$pm.small.geos.line_strings[index_small];
190                        let $ext_index_small = $pm.small.indexes.line_strings.get(index_small);
191                        let $geo_big = &$geo_big;
192                        $expr
193                    }
194                }),
195            $pm.rect_tree
196                .locate_in_envelope_intersecting(&$env)
197                .map(|fake| fake.id)
198                .filter_map({
199                    let $geo_big = $geo_big.clone();
200                    move |index_small| {
201                        let $geo_small = &$pm.small.geos.rects[index_small];
202                        let $ext_index_small = $pm.small.indexes.rects.get(index_small);
203                        let $geo_big = &$geo_big;
204                        $expr
205                    }
206                }),
207            $pm.tri_tree
208                .locate_in_envelope_intersecting(&$env)
209                .map(|fake| fake.id)
210                .filter_map({
211                    let $geo_big = $geo_big.clone();
212                    move |index_small| {
213                        let $geo_small = &$pm.small.geos.tris[index_small];
214                        let $ext_index_small = $pm.small.indexes.tris.get(index_small);
215                        let $geo_big = &$geo_big;
216                        $expr
217                    }
218                })
219        )
220    }};
221}
222
223macro_rules! join_inner_noncopyable {
224    ($pm:expr; $expr_type:ty;
225     $geo_big:ident, $ext_index_big:ident, $env:ident,
226     $geo_small:ident, $ext_index_small:ident;
227     $expr:expr) => {{
228        let mut result = SmallVec::<[$expr_type; 10]>::new();
229        result.extend(
230            $pm.point_tree
231                .locate_in_envelope_intersecting(&$env)
232                .map(|fake| fake.id)
233                .filter_map({
234                    move |index_small| {
235                        let $geo_small = &$pm.small.geos.points[index_small];
236                        let $ext_index_small = $pm.small.indexes.points.get(index_small);
237                        $expr
238                    }
239                }),
240        );
241        result.extend(
242            $pm.line_tree
243                .locate_in_envelope_intersecting(&$env)
244                .map(|fake| fake.id)
245                .filter_map({
246                    move |index_small| {
247                        let $geo_small = &$pm.small.geos.lines[index_small];
248                        let $ext_index_small = $pm.small.indexes.lines.get(index_small);
249                        $expr
250                    }
251                }),
252        );
253        result.extend(
254            $pm.poly_tree
255                .locate_in_envelope_intersecting(&$env)
256                .map(|fake| fake.id)
257                .filter_map({
258                    move |index_small| {
259                        let $geo_small = &$pm.small.geos.polys[index_small];
260                        let $ext_index_small = $pm.small.indexes.polys.get(index_small);
261                        $expr
262                    }
263                }),
264        );
265        result.extend(
266            $pm.ls_tree
267                .locate_in_envelope_intersecting(&$env)
268                .map(|fake| fake.id)
269                .filter_map({
270                    move |index_small| {
271                        let $geo_small = &$pm.small.geos.line_strings[index_small];
272                        let $ext_index_small = $pm.small.indexes.line_strings.get(index_small);
273                        $expr
274                    }
275                }),
276        );
277        result.extend(
278            $pm.rect_tree
279                .locate_in_envelope_intersecting(&$env)
280                .map(|fake| fake.id)
281                .filter_map({
282                    move |index_small| {
283                        let $geo_small = &$pm.small.geos.rects[index_small];
284                        let $ext_index_small = $pm.small.indexes.rects.get(index_small);
285                        $expr
286                    }
287                }),
288        );
289        result.extend(
290            $pm.tri_tree
291                .locate_in_envelope_intersecting(&$env)
292                .map(|fake| fake.id)
293                .filter_map({
294                    move |index_small| {
295                        let $geo_small = &$pm.small.geos.tris[index_small];
296                        let $ext_index_small = $pm.small.indexes.tris.get(index_small);
297                        $expr
298                    }
299                }),
300        );
301        result
302    }};
303}
304
305macro_rules! join {
306    ($pm:expr,
307     $expr_type:ty,
308     $big:expr;
309
310     $geo_big:ident, $ext_index_big:ident, $env:ident,
311     $geo_small:ident, $ext_index_small:ident;
312
313     $expr:expr) => {
314        join_outer!(
315            $big;
316            $geo_big,
317            $ext_index_big,
318            $env;
319            join_inner_copyable!(
320                $pm;
321                $geo_big,
322                $ext_index_big,
323                $env,
324                $geo_small,
325                $ext_index_small;
326		$expr
327            ),
328	    join_inner_noncopyable!(
329                $pm; $expr_type;
330                $geo_big,
331                $ext_index_big,
332                $env,
333                $geo_small,
334                $ext_index_small;
335		$expr
336            )
337
338        )
339    }
340}
341
342#[cfg(feature = "parallel")]
343macro_rules! par_join {
344    ($pm:expr,
345     $expr_type:ty,
346     $big:expr;
347
348     $geo_big:ident, $ext_index_big:ident, $env:ident,
349     $geo_small:ident, $ext_index_small:ident;
350
351     $expr:expr) => {
352        par_join_outer!(
353            $big;
354            $geo_big,
355            $ext_index_big,
356            $env;
357            join_inner_copyable!(
358                $pm;
359                $geo_big,
360                $ext_index_big,
361                $env,
362                $geo_small,
363                $ext_index_small;
364		$expr
365            ),
366	    join_inner_noncopyable!(
367                $pm; $expr_type;
368                $geo_big,
369                $ext_index_big,
370                $env,
371                $geo_small,
372                $ext_index_small;
373		$expr
374            )
375
376        )
377    }
378}
379
380pub(crate) fn sgs_try_into<T, U>(thing: T) -> Result<SplitGeoSeq, Error>
381where
382    T: TryInto<SplitGeoSeq, Error = U>,
383    U: std::any::Any,
384{
385    let thing: Result<SplitGeoSeq, _> = thing.try_into();
386    // FIXME: maybe map_error
387    match thing {
388        Ok(thing) => {
389            // conversion into SplitGeoSeq worked!
390            Ok(thing)
391        }
392        Err(e) => {
393            let any_e = &e as &dyn std::any::Any;
394            Err(any_e.downcast_ref::<Error>().expect("impossible").clone())
395        }
396    }
397}
398
399impl SpatialIndex {
400    pub fn new<T, U>(small: T, config: Config) -> Result<Self, Error>
401    where
402        T: TryInto<SplitGeoSeq, Error = U>,
403        U: std::any::Any,
404    {
405        let max_distance = config.max_distance;
406        let small = sgs_try_into(small)?;
407
408        let [point_tree, line_tree, poly_tree, ls_tree, rect_tree, tri_tree] =
409            small.to_rtrees(max_distance);
410        Ok(SpatialIndex {
411            small,
412            point_tree,
413            line_tree,
414            poly_tree,
415            ls_tree,
416            rect_tree,
417            tri_tree,
418            config,
419        })
420    }
421
422    pub fn proximity_map<'a, T: 'a, U>(
423        &'a self,
424        big: T,
425    ) -> Result<impl Iterator<Item = ProxMapRow> + 'a, Error>
426    where
427        T: TryInto<SplitGeoSeq, Error = U>,
428        U: std::any::Any + std::fmt::Debug,
429    {
430        let big = sgs_try_into(big)?;
431        Ok(join!(self, ProxMapRow, big;
432                  geo_big, ext_index_big, env,
433                  geo_small, ext_index_small;
434                  {
435              let distance = geo_big.EuclideanDistance(geo_small);
436              assert!(distance.is_finite());
437
438              if distance <= self.config.max_distance {
439                  Some(ProxMapRow {big_index: ext_index_big,
440                   small_index: ext_index_small,
441                   distance})
442              } else {
443                  None
444              }
445                  }
446        ))
447    }
448
449    pub fn proximity_map_with_geos<'a, T: 'a, U>(
450        &'a self,
451        big: T,
452    ) -> Result<impl Iterator<Item = ProxMapGeoRow> + 'a, Error>
453    where
454        T: TryInto<SplitGeoSeq, Error = U>,
455        U: std::any::Any + std::fmt::Debug,
456    {
457        let big = sgs_try_into(big)?;
458        Ok(join!(self, ProxMapGeoRow, big;
459                geo_big, ext_index_big, env,
460                geo_small, ext_index_small;
461                {
462            let distance = geo_big.EuclideanDistance(geo_small);
463            assert!(distance.is_finite());
464
465            if distance <= self.config.max_distance {
466        Some(ProxMapGeoRow {big_index: ext_index_big,
467                small_index: ext_index_small,
468              big: geo_big.clone().into(),
469                small: geo_small.clone().into(), distance})
470            } else {
471                None
472            }
473                }
474          ))
475    }
476
477    pub fn spatial_join<'a, T, U>(
478        &'a self,
479        big: T,
480        interaction: Interaction,
481    ) -> Result<impl Iterator<Item = SJoinRow> + 'a, Error>
482    where
483        T: TryInto<SplitGeoSeq, Error = U>,
484        U: std::any::Any + std::fmt::Debug,
485    {
486        let big = sgs_try_into(big)?;
487        // This is a weird structure designed to solve an odd
488        // problem. For performance, I want to have monomorphized code
489        // for each `Interaction` branch; in other words, I don't want
490        // to do a `match interaction` in the innermost loop. But
491        // trying to put a `match interaction` as the main body of
492        // this method fails since the different arms have different
493        // types that can't be unified! Hence this approach: we always
494        // run all three arms and chain them together, but two of the
495        // three arms get empty inputs. That way we satisfy the type
496        // system since we always emit the same type.
497        let (big_intersects, big_contains, big_within) = match interaction {
498            Interaction::Intersects => (big, SplitGeoSeq::default(), SplitGeoSeq::default()),
499            Interaction::Contains => (SplitGeoSeq::default(), big, SplitGeoSeq::default()),
500            Interaction::Within => (SplitGeoSeq::default(), SplitGeoSeq::default(), big),
501        };
502        Ok(chain!(
503            // These calls are identical except for the big_ variable
504            // and the geo_big.Interaction call.
505            join!(self, SJoinRow, big_intersects;
506                    geo_big, ext_index_big, env,
507                    geo_small, ext_index_small;
508
509            if geo_small.Intersects(geo_big)  {
510                Some(SJoinRow {big_index: ext_index_big, small_index: ext_index_small })
511                    } else {
512                        None
513                    }
514              ),
515            join!(self, SJoinRow, big_contains;
516                    geo_big, ext_index_big, env,
517                    geo_small, ext_index_small;
518            if geo_small.Contains(geo_big) {
519                Some(SJoinRow {big_index: ext_index_big, small_index: ext_index_small})
520                    } else {
521                        None
522                    }
523              ),
524            join!(self, SJoinRow, big_within;
525                    geo_big, ext_index_big, env,
526                    geo_small, ext_index_small;
527
528            if (geo_big).Contains(geo_small) {
529                Some(SJoinRow {big_index: ext_index_big, small_index: ext_index_small})
530                    } else {
531                        None
532                    }
533              )
534        ))
535    }
536
537    pub fn spatial_join_with_geos<'a, T, U>(
538        &'a self,
539        big: T,
540        interaction: Interaction,
541    ) -> Result<impl Iterator<Item = SJoinGeoRow> + 'a, Error>
542    where
543        T: TryInto<SplitGeoSeq, Error = U>,
544        U: std::any::Any + std::fmt::Debug,
545    {
546        let big = sgs_try_into(big)?;
547
548        // This is a weird structure designed to solve an odd
549        // problem. For performance, I want to have monomorphized code
550        // for each `Interaction` branch; in other words, I don't want
551        // to do a `match interaction` in the innermost loop. But
552        // trying to put a `match interaction` as the main body of
553        // this method fails since the different arms have different
554        // types that can't be unified! Hence this approach: we always
555        // run all three arms and chain them together, but two of the
556        // three arms get empty inputs. That way we satisfy the type
557        // system since we always emit the same type.
558        let (big_intersects, big_contains, big_within) = match interaction {
559            Interaction::Intersects => (big, SplitGeoSeq::default(), SplitGeoSeq::default()),
560            Interaction::Contains => (SplitGeoSeq::default(), big, SplitGeoSeq::default()),
561            Interaction::Within => (SplitGeoSeq::default(), SplitGeoSeq::default(), big),
562        };
563        Ok(chain!(
564            // These calls are identical except for the big_ variable
565            // and the geo_big.Interaction call.
566            join!(self, SJoinGeoRow, big_intersects;
567                        geo_big, ext_index_big, env,
568                        geo_small, ext_index_small;
569
570                if geo_small.Intersects(geo_big)  {
571            Some(SJoinGeoRow{big_index: ext_index_big, small_index: ext_index_small,
572                     big: geo_big.clone().into(), small: geo_small.clone().into()})
573                        } else {
574                            None
575                        }
576                  ),
577            join!(self, SJoinGeoRow, big_contains;
578                        geo_big, ext_index_big, env,
579                        geo_small, ext_index_small;
580                if geo_small.Contains(geo_big) {
581            Some(SJoinGeoRow{big_index: ext_index_big, small_index: ext_index_small,
582                     big: geo_big.clone().into(), small: geo_small.clone().into()})
583                        } else {
584                            None
585                        }
586                  ),
587            join!(self, SJoinGeoRow, big_within;
588                        geo_big, ext_index_big, env,
589                        geo_small, ext_index_small;
590
591                if geo_big.Contains(geo_small) {
592            Some(SJoinGeoRow{big_index: ext_index_big, small_index: ext_index_small,
593                     big: geo_big.clone().into(), small: geo_small.clone().into()})
594                        } else {
595                            None
596                        }
597                  )
598        ))
599    }
600}
601
602#[cfg(feature = "parallel")]
603pub(crate) fn par_sgs_try_into<T, U>(thing: T) -> Result<SplitGeoSeq, Error>
604where
605    T: TryInto<Par<SplitGeoSeq>, Error = U>,
606    U: std::any::Any,
607{
608    let thing: Result<Par<SplitGeoSeq>, _> = thing.try_into();
609    // FIXME: maybe map_error
610    match thing {
611        Ok(thing) => {
612            // conversion into SplitGeoSeq worked!
613            Ok(thing.0)
614        }
615        Err(e) => {
616            let any_e = &e as &dyn std::any::Any;
617            Err(any_e.downcast_ref::<Error>().expect("impossible").clone())
618        }
619    }
620}
621
622// We have to handle parallel with separate methods because parallel
623// and serial iterator have different types.
624
625#[cfg(feature = "parallel")]
626impl ParSpatialIndex {
627    pub fn new<T, U>(small: T, config: Config) -> Result<Self, Error>
628    where
629        T: TryInto<Par<SplitGeoSeq>, Error = U>,
630        U: std::any::Any,
631    {
632        let max_distance = config.max_distance;
633        let small = par_sgs_try_into(small)?;
634
635        let [point_tree, line_tree, poly_tree, ls_tree, rect_tree, tri_tree] =
636            small.to_rtrees(max_distance);
637        Ok(ParSpatialIndex(SpatialIndex {
638            small,
639            point_tree,
640            line_tree,
641            poly_tree,
642            ls_tree,
643            rect_tree,
644            tri_tree,
645            config,
646        }))
647    }
648
649    pub fn proximity_map<'a, T: 'a, U>(
650        &'a self,
651        big: T,
652    ) -> Result<impl ParallelIterator<Item = ProxMapRow> + 'a, Error>
653    where
654        T: TryInto<Par<SplitGeoSeq>, Error = U>,
655        U: std::any::Any + std::fmt::Debug,
656    {
657        let big = par_sgs_try_into(big)?;
658
659        Ok(par_join!(self.0, ProxMapRow, big;
660                  geo_big, ext_index_big, env,
661                  geo_small, ext_index_small;
662                  {
663              let distance = geo_big.EuclideanDistance(geo_small);
664              assert!(distance.is_finite());
665
666              if distance <= self.0.config.max_distance {
667                  Some(ProxMapRow {big_index: ext_index_big,
668                   small_index: ext_index_small,
669                   distance})
670              } else {
671                  None
672              }
673                  }
674        ))
675    }
676
677    pub fn proximity_map_with_geos<'a, T: 'a, U>(
678        &'a self,
679        big: T,
680    ) -> Result<impl ParallelIterator<Item = ProxMapGeoRow> + 'a, Error>
681    where
682        T: TryInto<Par<SplitGeoSeq>, Error = U>,
683        U: std::any::Any + std::fmt::Debug,
684    {
685        let big = par_sgs_try_into(big)?;
686        Ok(par_join!(self.0, ProxMapGeoRow, big;
687                geo_big, ext_index_big, env,
688                geo_small, ext_index_small;
689                {
690            let distance = geo_big.EuclideanDistance(geo_small);
691            assert!(distance.is_finite());
692
693            if distance <= self.0.config.max_distance {
694        Some(ProxMapGeoRow {big_index: ext_index_big,
695                small_index: ext_index_small,
696              big: geo_big.clone().into(),
697                small: geo_small.clone().into(), distance})
698            } else {
699                None
700            }
701                }
702          ))
703    }
704
705    pub fn spatial_join<'a, T, U>(
706        &'a self,
707        big: T,
708        interaction: Interaction,
709    ) -> Result<impl ParallelIterator<Item = SJoinRow> + 'a, Error>
710    where
711        T: TryInto<Par<SplitGeoSeq>, Error = U>,
712        U: std::any::Any + std::fmt::Debug,
713    {
714        let big = par_sgs_try_into(big)?;
715        // This is a weird structure designed to solve an odd
716        // problem. For performance, I want to have monomorphized code
717        // for each `Interaction` branch; in other words, I don't want
718        // to do a `match interaction` in the innermost loop. But
719        // trying to put a `match interaction` as the main body of
720        // this method fails since the different arms have different
721        // types that can't be unified! Hence this approach: we always
722        // run all three arms and chain them together, but two of the
723        // three arms get empty inputs. That way we satisfy the type
724        // system since we always emit the same type.
725        let (big_intersects, big_contains, big_within) = match interaction {
726            Interaction::Intersects => (big, SplitGeoSeq::default(), SplitGeoSeq::default()),
727            Interaction::Contains => (SplitGeoSeq::default(), big, SplitGeoSeq::default()),
728            Interaction::Within => (SplitGeoSeq::default(), SplitGeoSeq::default(), big),
729        };
730        Ok(chain!(
731            // These calls are identical except for the big_ variable
732            // and the geo_big.Interaction call.
733            par_join!(self.0, SJoinRow, big_intersects;
734                    geo_big, ext_index_big, env,
735                    geo_small, ext_index_small;
736
737            if geo_small.Intersects(geo_big)  {
738                Some(SJoinRow {big_index: ext_index_big, small_index: ext_index_small })
739                    } else {
740                        None
741                    }
742              ),
743            par_join!(self.0, SJoinRow, big_contains;
744                    geo_big, ext_index_big, env,
745                    geo_small, ext_index_small;
746            if geo_small.Contains(geo_big) {
747                Some(SJoinRow {big_index: ext_index_big, small_index: ext_index_small})
748                    } else {
749                        None
750                    }
751              ),
752            par_join!(self.0, SJoinRow, big_within;
753                    geo_big, ext_index_big, env,
754                    geo_small, ext_index_small;
755
756            if (geo_big).Contains(geo_small) {
757                Some(SJoinRow {big_index: ext_index_big, small_index: ext_index_small})
758                    } else {
759                        None
760                    }
761              )
762        ))
763    }
764
765    pub fn spatial_join_with_geos<'a, T, U>(
766        &'a self,
767        big: T,
768        interaction: Interaction,
769    ) -> Result<impl ParallelIterator<Item = SJoinGeoRow> + 'a, Error>
770    where
771        T: TryInto<Par<SplitGeoSeq>, Error = U>,
772        U: std::any::Any + std::fmt::Debug,
773    {
774        let big = par_sgs_try_into(big)?;
775
776        // This is a weird structure designed to solve an odd
777        // problem. For performance, I want to have monomorphized code
778        // for each `Interaction` branch; in other words, I don't want
779        // to do a `match interaction` in the innermost loop. But
780        // trying to put a `match interaction` as the main body of
781        // this method fails since the different arms have different
782        // types that can't be unified! Hence this approach: we always
783        // run all three arms and chain them together, but two of the
784        // three arms get empty inputs. That way we satisfy the type
785        // system since we always emit the same type.
786        let (big_intersects, big_contains, big_within) = match interaction {
787            Interaction::Intersects => (big, SplitGeoSeq::default(), SplitGeoSeq::default()),
788            Interaction::Contains => (SplitGeoSeq::default(), big, SplitGeoSeq::default()),
789            Interaction::Within => (SplitGeoSeq::default(), SplitGeoSeq::default(), big),
790        };
791        Ok(chain!(
792            // These calls are identical except for the big_ variable
793            // and the geo_big.Interaction call.
794            par_join!(self.0, SJoinGeoRow, big_intersects;
795                        geo_big, ext_index_big, env,
796                        geo_small, ext_index_small;
797
798                if geo_small.Intersects(geo_big)  {
799            Some(SJoinGeoRow{big_index: ext_index_big, small_index: ext_index_small,
800                     big: geo_big.clone().into(), small: geo_small.clone().into()})
801                        } else {
802                            None
803                        }
804                  ),
805            par_join!(self.0, SJoinGeoRow, big_contains;
806                        geo_big, ext_index_big, env,
807                        geo_small, ext_index_small;
808                if geo_small.Contains(geo_big) {
809            Some(SJoinGeoRow{big_index: ext_index_big, small_index: ext_index_small,
810                     big: geo_big.clone().into(), small: geo_small.clone().into()})
811                        } else {
812                            None
813                        }
814                  ),
815            par_join!(self.0, SJoinGeoRow, big_within;
816                        geo_big, ext_index_big, env,
817                        geo_small, ext_index_small;
818
819                if geo_big.Contains(geo_small) {
820            Some(SJoinGeoRow{big_index: ext_index_big, small_index: ext_index_small,
821                     big: geo_big.clone().into(), small: geo_small.clone().into()})
822                        } else {
823                            None
824                        }
825                  )
826        ))
827    }
828}
829
830#[cfg(test)]
831mod tests {
832    #[test]
833    fn chain_macro() {
834        let x: Vec<i32> = chain!(3..7, 9..12, 2..4).collect();
835        assert_eq!(x, vec![3, 4, 5, 6, 9, 10, 11, 2, 3]);
836
837        let x: Vec<i32> = chain!(3..7).collect();
838        assert_eq!(x, vec![3, 4, 5, 6])
839    }
840}