1 
2 #include <unordered_map>
3 #include <unordered_set>
4 #include <algorithm>
5 
6 #include "s2/s2boolean_operation.h"
7 #include "s2/s2closest_edge_query.h"
8 #include "s2/s2furthest_edge_query.h"
9 #include "s2/s2shape_index_region.h"
10 
11 #include "geography-operator.h"
12 #include "s2-options.h"
13 
14 #include <Rcpp.h>
15 using namespace Rcpp;
16 
buildSourcedIndex(List geog,MutableS2ShapeIndex * index)17 std::unordered_map<int, R_xlen_t> buildSourcedIndex(List geog, MutableS2ShapeIndex* index) {
18   std::unordered_map<int, R_xlen_t> indexSource;
19   std::vector<int> shapeIds;
20 
21   for (R_xlen_t j = 0; j < geog.size(); j++) {
22     checkUserInterrupt();
23     SEXP item2 = geog[j];
24 
25     // build index and store index IDs so that shapeIds can be
26     // mapped back to the geog index
27     if (item2 == R_NilValue) {
28       Rcpp::stop("Missing `y` not allowed in binary indexed operators()");
29     } else {
30       Rcpp::XPtr<Geography> feature2(item2);
31       shapeIds = feature2->BuildShapeIndex(index);
32       for (size_t k = 0; k < shapeIds.size(); k ++) {
33         indexSource[shapeIds[k]] = j;
34       }
35     }
36   }
37 
38   return indexSource;
39 }
40 
findPossibleIntersections(const S2Region & region,const MutableS2ShapeIndex * index,std::unordered_map<int,R_xlen_t> & source,int maxRegionCells)41 std::unordered_set<R_xlen_t> findPossibleIntersections(const S2Region& region,
42                                                        const MutableS2ShapeIndex* index,
43                                                        std::unordered_map<int, R_xlen_t>& source,
44                                                        int maxRegionCells) {
45 
46   std::unordered_set<R_xlen_t> mightIntersectIndices;
47   MutableS2ShapeIndex::Iterator indexIterator(index);
48 
49   // generate a small covering of the region
50   S2RegionCoverer coverer;
51   coverer.mutable_options()->set_max_cells(maxRegionCells);
52   S2CellUnion covering = coverer.GetCovering(region);
53 
54   // iterate over cells in the featureIndex
55   for (S2CellId featureCellId: covering) {
56     S2ShapeIndex::CellRelation relation = indexIterator.Locate(featureCellId);
57 
58     if (relation == S2ShapeIndex::CellRelation::INDEXED) {
59       // we're in luck! these indexes have this cell in common
60       // add all the features it contains as possible intersectors for featureIndex
61       const S2ShapeIndexCell& cell = indexIterator.cell();
62       for (int k = 0; k < cell.num_clipped(); k++) {
63         int shapeId = cell.clipped(k).shape_id();
64         mightIntersectIndices.insert(source[shapeId]);
65       }
66 
67     } else if(relation  == S2ShapeIndex::CellRelation::SUBDIVIDED) {
68       // promising! the geog2 index has a child cell of it.id()
69       // (at which indexIterator is now positioned)
70       // keep iterating until the iterator is done OR we're no longer at a child cell of
71       // it.id(). The ordering of the iterator isn't guaranteed anywhere in the documentation;
72       // however, this ordering would be consistent with that of a Normalized
73       // S2CellUnion.
74       while (!indexIterator.done() && featureCellId.contains(indexIterator.id())) {
75         // potentially many cells in the indexIterator, so let the user cancel if this is
76         // running too long
77         checkUserInterrupt();
78 
79         // add all the features the child cell contains as possible intersectors for featureIndex
80         const S2ShapeIndexCell& cell = indexIterator.cell();
81         for (int k = 0; k < cell.num_clipped(); k++) {
82           int shapeId = cell.clipped(k).shape_id();
83           mightIntersectIndices.insert(source[shapeId]);
84         }
85 
86         // go to the next cell in the index
87         indexIterator.Next();
88       }
89     }
90 
91     // else: relation == S2ShapeIndex::CellRelation::DISJOINT (do nothing)
92   }
93 
94   return mightIntersectIndices;
95 }
96 
97 template<class VectorType, class ScalarType>
98 class IndexedBinaryGeographyOperator: public UnaryGeographyOperator<VectorType, ScalarType> {
99 public:
100   std::unique_ptr<MutableS2ShapeIndex> geog2Index;
101   std::unordered_map<int, R_xlen_t> geog2IndexSource;
102 
IndexedBinaryGeographyOperator()103   IndexedBinaryGeographyOperator() {
104     this->geog2Index = absl::make_unique<MutableS2ShapeIndex>();
105   }
106 
107   // maxEdgesPerCell should be between 10 and 50, with lower numbers
108   // leading to more memory usage (but potentially faster query times). Benchmarking
109   // with binary prediates seems to indicate that values on the high end
110   // of the spectrum do a reasonable job of efficient preselection, and that
111   // decreasing this value does little to increase performance.
buildIndex(List geog2,int maxEdgesPerCell=50)112   virtual void buildIndex(List geog2, int maxEdgesPerCell = 50) {
113     MutableS2ShapeIndex::Options indexOptions;
114     indexOptions.set_max_edges_per_cell(maxEdgesPerCell);
115     this->geog2Index = absl::make_unique<MutableS2ShapeIndex>(indexOptions);
116     this->geog2IndexSource = buildSourcedIndex(geog2, this->geog2Index.get());
117   }
118 };
119 
120 // -------- closest/farthest feature ----------
121 
122 // [[Rcpp::export]]
cpp_s2_closest_feature(List geog1,List geog2)123 IntegerVector cpp_s2_closest_feature(List geog1, List geog2) {
124 
125   class Op: public IndexedBinaryGeographyOperator<IntegerVector, int> {
126   public:
127     int processFeature(Rcpp::XPtr<Geography> feature, R_xlen_t i) {
128       S2ClosestEdgeQuery query(this->geog2Index.get());
129       S2ClosestEdgeQuery::ShapeIndexTarget target(feature->ShapeIndex());
130       const auto& result = query.FindClosestEdge(&target);
131       if (result.is_empty()) {
132         return NA_INTEGER;
133       } else {
134         // convert to R index (+1)
135         return this->geog2IndexSource[result.shape_id()] + 1;
136       }
137     }
138   };
139 
140   Op op;
141   op.buildIndex(geog2);
142   return op.processVector(geog1);
143 }
144 
145 // [[Rcpp::export]]
cpp_s2_farthest_feature(List geog1,List geog2)146 IntegerVector cpp_s2_farthest_feature(List geog1, List geog2) {
147 
148   class Op: public IndexedBinaryGeographyOperator<IntegerVector, int> {
149   public:
150     int processFeature(Rcpp::XPtr<Geography> feature, R_xlen_t i) {
151       S2FurthestEdgeQuery query(this->geog2Index.get());
152       S2FurthestEdgeQuery::ShapeIndexTarget target(feature->ShapeIndex());
153       const auto& result = query.FindFurthestEdge(&target);
154       if (result.is_empty()) {
155         return NA_INTEGER;
156       } else {
157         // convert to R index (+1)
158         return this->geog2IndexSource[result.shape_id()] + 1;
159       }
160     }
161   };
162 
163   Op op;
164   op.buildIndex(geog2);
165   return op.processVector(geog1);
166 }
167 
168 // [[Rcpp::export]]
cpp_s2_closest_edges(List geog1,List geog2,int n,double min_distance)169 List cpp_s2_closest_edges(List geog1, List geog2, int n, double min_distance) {
170 
171   class Op: public IndexedBinaryGeographyOperator<List, IntegerVector> {
172   public:
173     IntegerVector processFeature(Rcpp::XPtr<Geography> feature, R_xlen_t i) {
174       S2ClosestEdgeQuery query(this->geog2Index.get());
175       query.mutable_options()->set_max_results(n);
176       S2ClosestEdgeQuery::ShapeIndexTarget target(feature->ShapeIndex());
177       const auto& result = query.FindClosestEdges(&target);
178 
179       // this code searches edges, which may come from the same feature
180       std::unordered_set<int> features;
181       for (S2ClosestEdgeQuery::Result res : result) {
182         if (res.distance().radians() > this->min_distance) {
183           features.insert(this->geog2IndexSource[res.shape_id()] + 1);
184         }
185       }
186 
187       return IntegerVector(features.begin(), features.end());
188     }
189 
190     int n;
191     double min_distance;
192   };
193 
194   Op op;
195   op.n = n;
196   op.min_distance = min_distance;
197   op.buildIndex(geog2);
198   return op.processVector(geog1);
199 }
200 
201 // ----------- indexed binary predicate operators -----------
202 
203 class IndexedMatrixPredicateOperator: public IndexedBinaryGeographyOperator<List, IntegerVector> {
204 public:
205   // a max_cells value of 8 was suggested in the S2RegionCoverer docs as a
206   // reasonable approximation of a geometry, although benchmarking seems to indicate that
207   // increasing this number above 4 actually decreasses performance (using a value
208   // of 1 dramatically decreases performance)
IndexedMatrixPredicateOperator(List s2options,int maxFeatureCells=4)209   IndexedMatrixPredicateOperator(List s2options, int maxFeatureCells = 4):
210     maxFeatureCells(maxFeatureCells) {
211     GeographyOperationOptions options(s2options);
212     this->options = options.booleanOperationOptions();
213   }
214 
215   // See IndexedBinaryGeographyOperator::buildIndex() for why 50 is the default value
216   // for maxEdgesPerCell
buildIndex(List geog2,int maxEdgesPerCell=50)217   void buildIndex(List geog2, int maxEdgesPerCell = 50) {
218     this->geog2  = geog2;
219     IndexedBinaryGeographyOperator<List, IntegerVector>::buildIndex(geog2, maxEdgesPerCell);
220   }
221 
processFeature(Rcpp::XPtr<Geography> feature,R_xlen_t i)222   IntegerVector processFeature(Rcpp::XPtr<Geography> feature, R_xlen_t i) {
223     S2ShapeIndex* index1 = feature->ShapeIndex();
224     S2ShapeIndexRegion<S2ShapeIndex> region = MakeS2ShapeIndexRegion(index1);
225 
226     // build a list of candidate feature indices
227     std::unordered_set<R_xlen_t> mightIntersectIndices = findPossibleIntersections(
228       region,
229       this->geog2Index.get(),
230       this->geog2IndexSource,
231       this->maxFeatureCells
232     );
233 
234     // loop through features from geog2 that might intersect feature
235     // and build a list of indices that actually intersect (based on
236     // this->actuallyIntersects(), which might perform alternative
237     // comparisons)
238     std::vector<int> actuallyIntersectIndices;
239     for (R_xlen_t j: mightIntersectIndices) {
240       SEXP item = this->geog2[j];
241       XPtr<Geography> feature2(item);
242       if (this->actuallyIntersects(index1, feature2->ShapeIndex(), i, j)) {
243         // convert to R index here + 1
244         actuallyIntersectIndices.push_back(j + 1);
245       }
246     }
247 
248     // return sorted integer vector
249     std::sort(actuallyIntersectIndices.begin(), actuallyIntersectIndices.end());
250     return Rcpp::IntegerVector(actuallyIntersectIndices.begin(), actuallyIntersectIndices.end());
251   };
252 
253   virtual bool actuallyIntersects(S2ShapeIndex* index1, S2ShapeIndex* index2, R_xlen_t i, R_xlen_t j) = 0;
254 
255   protected:
256     List geog2;
257     S2BooleanOperation::Options options;
258     int maxFeatureCells;
259 };
260 
261 // [[Rcpp::export]]
cpp_s2_may_intersect_matrix(List geog1,List geog2,int maxEdgesPerCell,int maxFeatureCells,List s2options)262 List cpp_s2_may_intersect_matrix(List geog1, List geog2,
263                                  int maxEdgesPerCell, int maxFeatureCells, List s2options) {
264   class Op: public IndexedMatrixPredicateOperator {
265   public:
266     Op(List s2options, int maxFeatureCells):
267       IndexedMatrixPredicateOperator(s2options, maxFeatureCells) {}
268 
269     bool actuallyIntersects(S2ShapeIndex* index1, S2ShapeIndex* index2, R_xlen_t i, R_xlen_t j) {
270       return true;
271     };
272   };
273 
274   Op op(s2options, maxFeatureCells);
275   op.buildIndex(geog2, maxEdgesPerCell);
276   return op.processVector(geog1);
277 }
278 
279 // [[Rcpp::export]]
cpp_s2_contains_matrix(List geog1,List geog2,List s2options)280 List cpp_s2_contains_matrix(List geog1, List geog2, List s2options) {
281   class Op: public IndexedMatrixPredicateOperator {
282   public:
283     Op(List s2options): IndexedMatrixPredicateOperator(s2options) {}
284     bool actuallyIntersects(S2ShapeIndex* index1, S2ShapeIndex* index2, R_xlen_t i, R_xlen_t j) {
285       return S2BooleanOperation::Contains(*index1, *index2, this->options);
286     };
287   };
288 
289   Op op(s2options);
290   op.buildIndex(geog2);
291   return op.processVector(geog1);
292 }
293 
294 // [[Rcpp::export]]
cpp_s2_within_matrix(List geog1,List geog2,List s2options)295 List cpp_s2_within_matrix(List geog1, List geog2, List s2options) {
296   class Op: public IndexedMatrixPredicateOperator {
297   public:
298     Op(List s2options): IndexedMatrixPredicateOperator(s2options) {}
299     bool actuallyIntersects(S2ShapeIndex* index1, S2ShapeIndex* index2, R_xlen_t i, R_xlen_t j) {
300       // note reversed index2, index1
301       return S2BooleanOperation::Contains(*index2, *index1, this->options);
302     };
303   };
304 
305   Op op(s2options);
306   op.buildIndex(geog2);
307   return op.processVector(geog1);
308 }
309 
310 // [[Rcpp::export]]
cpp_s2_intersects_matrix(List geog1,List geog2,List s2options)311 List cpp_s2_intersects_matrix(List geog1, List geog2, List s2options) {
312   class Op: public IndexedMatrixPredicateOperator {
313   public:
314     Op(List s2options): IndexedMatrixPredicateOperator(s2options) {}
315     bool actuallyIntersects(S2ShapeIndex* index1, S2ShapeIndex* index2, R_xlen_t i, R_xlen_t j) {
316       return S2BooleanOperation::Intersects(*index1, *index2, this->options);
317     };
318   };
319 
320   Op op(s2options);
321   op.buildIndex(geog2);
322   return op.processVector(geog1);
323 }
324 
325 // [[Rcpp::export]]
cpp_s2_equals_matrix(List geog1,List geog2,List s2options)326 List cpp_s2_equals_matrix(List geog1, List geog2, List s2options) {
327   class Op: public IndexedMatrixPredicateOperator {
328   public:
329     Op(List s2options): IndexedMatrixPredicateOperator(s2options) {}
330     bool actuallyIntersects(S2ShapeIndex* index1, S2ShapeIndex* index2, R_xlen_t i, R_xlen_t j) {
331       return S2BooleanOperation::Equals(*index1, *index2, this->options);
332     };
333   };
334 
335   Op op(s2options);
336   op.buildIndex(geog2);
337   return op.processVector(geog1);
338 }
339 
340 // [[Rcpp::export]]
cpp_s2_touches_matrix(List geog1,List geog2,List s2options)341 List cpp_s2_touches_matrix(List geog1, List geog2, List s2options) {
342   class Op: public IndexedMatrixPredicateOperator {
343   public:
344     Op(List s2options): IndexedMatrixPredicateOperator(s2options) {
345       this->closedOptions = this->options;
346       this->closedOptions.set_polygon_model(S2BooleanOperation::PolygonModel::CLOSED);
347       this->closedOptions.set_polyline_model(S2BooleanOperation::PolylineModel::CLOSED);
348 
349       this->openOptions = this->options;
350       this->openOptions.set_polygon_model(S2BooleanOperation::PolygonModel::OPEN);
351       this->openOptions.set_polyline_model(S2BooleanOperation::PolylineModel::OPEN);
352     }
353 
354     bool actuallyIntersects(S2ShapeIndex* index1, S2ShapeIndex* index2, R_xlen_t i, R_xlen_t j) {
355       // efficiently re-uses the index on geog2 and takes advantage of short-circuiting &&
356       return S2BooleanOperation::Intersects(*index1, *index2, this->closedOptions) &&
357         !S2BooleanOperation::Intersects(*index1, *index2, this->openOptions);
358     };
359 
360   private:
361     S2BooleanOperation::Options closedOptions;
362     S2BooleanOperation::Options openOptions;
363   };
364 
365   Op op(s2options);
366   op.buildIndex(geog2);
367   return op.processVector(geog1);
368 }
369 
370 
371 // ----------- brute force binary predicate operators ------------------
372 
373 class BruteForceMatrixPredicateOperator {
374 public:
375   std::vector<S2ShapeIndex*> geog2Indices;
376   S2BooleanOperation::Options options;
377 
BruteForceMatrixPredicateOperator()378   BruteForceMatrixPredicateOperator() {}
379 
BruteForceMatrixPredicateOperator(Rcpp::List s2options)380   BruteForceMatrixPredicateOperator(Rcpp::List s2options) {
381     GeographyOperationOptions options(s2options);
382     this->options = options.booleanOperationOptions();
383   }
384 
processVector(Rcpp::List geog1,Rcpp::List geog2)385   List processVector(Rcpp::List geog1, Rcpp::List geog2) {
386     List output(geog1.size());
387 
388     // using instead of IntegerVector because
389     // std::vector is much faster with repeated calls to .push_back()
390     std::vector<int> trueIndices;
391 
392     for (R_xlen_t i = 0; i < geog1.size(); i++) {
393       trueIndices.clear();
394 
395       SEXP item1 = geog1[i];
396       if (item1 ==  R_NilValue) {
397         output[i] = R_NilValue;
398       } else {
399         Rcpp::XPtr<Geography> feature1(item1);
400 
401         for (size_t j = 0; j < geog2.size(); j++) {
402           checkUserInterrupt();
403           SEXP item2 = geog2[j];
404           if (item2 == R_NilValue) {
405             stop("Missing `y` not allowed in binary index operations");
406           }
407 
408           XPtr<Geography> feature2(item2);
409 
410           bool result = this->processFeature(feature1, feature2, i, j);
411           if (result) {
412             // convert to R index here (+1)
413             trueIndices.push_back(j + 1);
414           }
415         }
416 
417         IntegerVector itemOut(trueIndices.size());
418         for (size_t k = 0; k < trueIndices.size(); k++) {
419           itemOut[k] = trueIndices[k];
420         }
421         output[i] = itemOut;
422       }
423     }
424 
425     return output;
426   }
427 
428   virtual bool processFeature(XPtr<Geography> feature1, XPtr<Geography> feature2,
429                               R_xlen_t i, R_xlen_t j) = 0;
430 };
431 
432 // [[Rcpp::export]]
cpp_s2_dwithin_matrix(List geog1,List geog2,double distance)433 List cpp_s2_dwithin_matrix(List geog1, List geog2, double distance) {
434   class Op: public BruteForceMatrixPredicateOperator {
435   public:
436     double distance;
437     Op(double distance): distance(distance) {}
438     bool processFeature(XPtr<Geography> feature1, XPtr<Geography> feature2,
439                         R_xlen_t i, R_xlen_t j) {
440       S2ClosestEdgeQuery query(feature2->ShapeIndex());
441       S2ClosestEdgeQuery::ShapeIndexTarget target(feature1->ShapeIndex());
442       return query.IsDistanceLessOrEqual(&target, S1ChordAngle::Radians(this->distance));
443     };
444   };
445 
446   Op op(distance);
447   return op.processVector(geog1, geog2);
448 }
449 
450 // ----------- distance matrix operators -------------------
451 
452 template<class MatrixType, class ScalarType>
453 class MatrixGeographyOperator {
454 public:
processVector(Rcpp::List geog1,Rcpp::List geog2)455   MatrixType processVector(Rcpp::List geog1, Rcpp::List geog2) {
456 
457     MatrixType output(geog1.size(), geog2.size());
458 
459     SEXP item1;
460     SEXP item2;
461 
462     for (R_xlen_t i = 0; i < geog1.size(); i++) {
463       item1 = geog1[i];
464       if (item1 ==  R_NilValue) {
465         for (R_xlen_t j = 0; j < geog2.size(); j++) {
466           output(i, j) = MatrixType::get_na();
467         }
468       } else {
469         Rcpp::XPtr<Geography> feature1(item1);
470 
471         for (R_xlen_t j = 0; j < geog2.size(); j++) {
472           checkUserInterrupt();
473           item2 = geog2[j];
474 
475           if (item2 == R_NilValue) {
476             output(i, j) = MatrixType::get_na();
477           } else {
478             Rcpp::XPtr<Geography> feature2(item2);
479             output(i, j) = this->processFeature(feature1, feature2, i, j);
480           }
481         }
482       }
483     }
484 
485     return output;
486   }
487 
488   virtual ScalarType processFeature(Rcpp::XPtr<Geography> feature1,
489                                     Rcpp::XPtr<Geography> feature2,
490                                     R_xlen_t i, R_xlen_t j) = 0;
491 };
492 
493 // [[Rcpp::export]]
cpp_s2_distance_matrix(List geog1,List geog2)494 NumericMatrix cpp_s2_distance_matrix(List geog1, List geog2) {
495   class Op: public MatrixGeographyOperator<NumericMatrix, double> {
496 
497     double processFeature(XPtr<Geography> feature1, XPtr<Geography> feature2,
498                           R_xlen_t i, R_xlen_t j) {
499       S2ClosestEdgeQuery query(feature1->ShapeIndex());
500       S2ClosestEdgeQuery::ShapeIndexTarget target(feature2->ShapeIndex());
501       const auto& result = query.FindClosestEdge(&target);
502 
503       S1ChordAngle angle = result.distance();
504       double distance = angle.ToAngle().radians();
505 
506       if (distance == R_PosInf) {
507         return NA_REAL;
508       } else {
509         return distance;
510       }
511     }
512   };
513 
514   Op op;
515   return op.processVector(geog1, geog2);
516 }
517 
518 // [[Rcpp::export]]
cpp_s2_max_distance_matrix(List geog1,List geog2)519 NumericMatrix cpp_s2_max_distance_matrix(List geog1, List geog2) {
520   class Op: public MatrixGeographyOperator<NumericMatrix, double> {
521 
522     double processFeature(XPtr<Geography> feature1, XPtr<Geography> feature2,
523                           R_xlen_t i, R_xlen_t j) {
524       S2FurthestEdgeQuery query(feature1->ShapeIndex());
525       S2FurthestEdgeQuery::ShapeIndexTarget target(feature2->ShapeIndex());
526       const auto& result = query.FindFurthestEdge(&target);
527 
528       S1ChordAngle angle = result.distance();
529       double distance = angle.ToAngle().radians();
530 
531       // returns -1 if one of the indexes is empty
532       // NA is more consistent with the BigQuery
533       // function, and makes way more sense
534       if (distance < 0) {
535         return NA_REAL;
536       } else {
537         return distance;
538       }
539     }
540   };
541 
542   Op op;
543   return op.processVector(geog1, geog2);
544 }
545 
546 
547 // ----------- brute force binary predicate operators (for testing) ------------------
548 
549 // [[Rcpp::export]]
cpp_s2_contains_matrix_brute_force(List geog1,List geog2,List s2options)550 List cpp_s2_contains_matrix_brute_force(List geog1, List geog2, List s2options) {
551   class Op: public BruteForceMatrixPredicateOperator {
552   public:
553     Op(List s2options): BruteForceMatrixPredicateOperator(s2options) {}
554     bool processFeature(XPtr<Geography> feature1, XPtr<Geography> feature2,
555                         R_xlen_t i, R_xlen_t j) {
556       // by default Contains() will return true for Contains(x, EMPTY), which is
557       // not true in BigQuery or GEOS
558       if (feature2->IsEmpty()) {
559         return false;
560       } else {
561         return S2BooleanOperation::Contains(
562           *feature1->ShapeIndex(),
563           *feature2->ShapeIndex(),
564           this->options
565         );
566       }
567     };
568   };
569 
570   Op op(s2options);
571   return op.processVector(geog1, geog2);
572 }
573 
574 // [[Rcpp::export]]
cpp_s2_within_matrix_brute_force(List geog1,List geog2,List s2options)575 List cpp_s2_within_matrix_brute_force(List geog1, List geog2, List s2options) {
576   class Op: public BruteForceMatrixPredicateOperator {
577   public:
578     Op(List s2options): BruteForceMatrixPredicateOperator(s2options) {}
579     bool processFeature(XPtr<Geography> feature1, XPtr<Geography> feature2,
580                         R_xlen_t i, R_xlen_t j) {
581       // note reversed index2, index1
582 
583       // by default Contains() will return true for Contains(x, EMPTY), which is
584       // not true in BigQuery or GEOS
585       if (feature1->IsEmpty()) {
586         return false;
587       } else {
588         return S2BooleanOperation::Contains(
589           *feature2->ShapeIndex(),
590           *feature1->ShapeIndex(),
591           this->options
592         );
593       }
594     };
595   };
596 
597   Op op(s2options);
598   return op.processVector(geog1, geog2);
599 }
600 
601 // [[Rcpp::export]]
cpp_s2_intersects_matrix_brute_force(List geog1,List geog2,List s2options)602 List cpp_s2_intersects_matrix_brute_force(List geog1, List geog2, List s2options) {
603   class Op: public BruteForceMatrixPredicateOperator {
604   public:
605     Op(List s2options): BruteForceMatrixPredicateOperator(s2options) {}
606     bool processFeature(XPtr<Geography> feature1, XPtr<Geography> feature2,
607                         R_xlen_t i, R_xlen_t j) {
608       return S2BooleanOperation::Intersects(
609         *feature1->ShapeIndex(),
610         *feature2->ShapeIndex(),
611         this->options
612       );
613     }
614   };
615 
616   Op op(s2options);
617   return op.processVector(geog1, geog2);
618 }
619 
620 // [[Rcpp::export]]
cpp_s2_disjoint_matrix_brute_force(List geog1,List geog2,List s2options)621 List cpp_s2_disjoint_matrix_brute_force(List geog1, List geog2, List s2options) {
622   class Op: public BruteForceMatrixPredicateOperator {
623   public:
624     Op(List s2options): BruteForceMatrixPredicateOperator(s2options) {}
625     bool processFeature(XPtr<Geography> feature1, XPtr<Geography> feature2,
626                         R_xlen_t i, R_xlen_t j) {
627       return !S2BooleanOperation::Intersects(
628         *feature1->ShapeIndex(),
629         *feature2->ShapeIndex(),
630         this->options
631       );
632     }
633   };
634 
635   Op op(s2options);
636   return op.processVector(geog1, geog2);
637 }
638 
639 // [[Rcpp::export]]
cpp_s2_equals_matrix_brute_force(List geog1,List geog2,List s2options)640 List cpp_s2_equals_matrix_brute_force(List geog1, List geog2, List s2options) {
641   class Op: public BruteForceMatrixPredicateOperator {
642   public:
643     Op(List s2options): BruteForceMatrixPredicateOperator(s2options) {}
644     bool processFeature(XPtr<Geography> feature1, XPtr<Geography> feature2,
645                         R_xlen_t i, R_xlen_t j) {
646       return S2BooleanOperation::Equals(
647         *feature1->ShapeIndex(),
648         *feature2->ShapeIndex(),
649         this->options
650       );
651     }
652   };
653 
654   Op op(s2options);
655   return op.processVector(geog1, geog2);
656 }
657