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