Skip to content

Commit f06f291

Browse files
author
Dewey Dunnington
authored
Merge pull request #112 from r-spatial/dewey-dev
provide a path for k-nearest neighbours for points
2 parents e190ef8 + 1b29c66 commit f06f291

File tree

8 files changed

+117
-0
lines changed

8 files changed

+117
-0
lines changed

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export(s2_bounds_rect)
5858
export(s2_buffer_cells)
5959
export(s2_centroid)
6060
export(s2_centroid_agg)
61+
export(s2_closest_edges)
6162
export(s2_closest_feature)
6263
export(s2_closest_point)
6364
export(s2_contains)

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# s2 (development version)
22

3+
* Added `s2_closest_edges()` to make k-nearest neighbours calculation
4+
possible on the sphere (#111, #112).
35
* Added `s2_interpolate()`, `s2_interpolate_normalized()`,
46
`s2_project()`, and `s2_project_normalized()` to provide linear
57
referencing support on the sphere (#96, #110).

R/RcppExports.R

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ cpp_s2_farthest_feature <- function(geog1, geog2) {
125125
.Call(`_s2_cpp_s2_farthest_feature`, geog1, geog2)
126126
}
127127

128+
cpp_s2_closest_edges <- function(geog1, geog2, n, min_distance) {
129+
.Call(`_s2_cpp_s2_closest_edges`, geog1, geog2, n, min_distance)
130+
}
131+
128132
cpp_s2_may_intersect_matrix <- function(geog1, geog2, maxEdgesPerCell, maxFeatureCells, s2options) {
129133
.Call(`_s2_cpp_s2_may_intersect_matrix`, geog1, geog2, maxEdgesPerCell, maxFeatureCells, s2options)
130134
}

R/s2-matrix.R

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111
#' @inheritParams s2_contains
1212
#' @param x,y Geography vectors, coerced using [as_s2_geography()].
1313
#' `x` is considered the source, where as `y` is considered the target.
14+
#' @param k The number of closest edges to consider when searching. Note
15+
#' that in S2 a point is also considered an edge.
16+
#' @param min_distance The minimum distance to consider when searching for
17+
#' edges. This filter is applied after the search is complete (i.e.,
18+
#' may cause fewer than `k` values to be returned).
1419
#' @param max_edges_per_cell For [s2_may_intersect_matrix()],
1520
#' this values controls the nature of the index on `y`, with higher values
1621
#' leading to coarser index. Values should be between 10 and 50; the default
@@ -42,6 +47,11 @@
4247
#' # for each feature in x
4348
#' country_names[s2_farthest_feature(cities, countries)]
4449
#'
50+
#' # use s2_closest_edges() to find the k-nearest neighbours
51+
#' nearest <- s2_closest_edges(cities, cities, k = 2, min_distance = 0)
52+
#' city_names
53+
#' city_names[unlist(nearest)]
54+
#'
4555
#' # predicate matrices
4656
#' country_names[s2_intersects_matrix(cities, countries)[[1]]]
4757
#'
@@ -53,6 +63,13 @@ s2_closest_feature <- function(x, y) {
5363
cpp_s2_closest_feature(as_s2_geography(x), as_s2_geography(y))
5464
}
5565

66+
#' @rdname s2_closest_feature
67+
#' @export
68+
s2_closest_edges <- function(x, y, k, min_distance = -1, radius = s2_earth_radius_meters()) {
69+
stopifnot(k >= 1)
70+
cpp_s2_closest_edges(as_s2_geography(x), as_s2_geography(y), k, min_distance / radius)
71+
}
72+
5673
#' @rdname s2_closest_feature
5774
#' @export
5875
s2_farthest_feature <- function(x, y) {

man/s2_closest_feature.Rd

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/RcppExports.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,20 @@ BEGIN_RCPP
368368
return rcpp_result_gen;
369369
END_RCPP
370370
}
371+
// cpp_s2_closest_edges
372+
List cpp_s2_closest_edges(List geog1, List geog2, int n, double min_distance);
373+
RcppExport SEXP _s2_cpp_s2_closest_edges(SEXP geog1SEXP, SEXP geog2SEXP, SEXP nSEXP, SEXP min_distanceSEXP) {
374+
BEGIN_RCPP
375+
Rcpp::RObject rcpp_result_gen;
376+
Rcpp::RNGScope rcpp_rngScope_gen;
377+
Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP);
378+
Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP);
379+
Rcpp::traits::input_parameter< int >::type n(nSEXP);
380+
Rcpp::traits::input_parameter< double >::type min_distance(min_distanceSEXP);
381+
rcpp_result_gen = Rcpp::wrap(cpp_s2_closest_edges(geog1, geog2, n, min_distance));
382+
return rcpp_result_gen;
383+
END_RCPP
384+
}
371385
// cpp_s2_may_intersect_matrix
372386
List cpp_s2_may_intersect_matrix(List geog1, List geog2, int maxEdgesPerCell, int maxFeatureCells, List s2options);
373387
RcppExport SEXP _s2_cpp_s2_may_intersect_matrix(SEXP geog1SEXP, SEXP geog2SEXP, SEXP maxEdgesPerCellSEXP, SEXP maxFeatureCellsSEXP, SEXP s2optionsSEXP) {
@@ -920,6 +934,7 @@ static const R_CallMethodDef CallEntries[] = {
920934
{"_s2_data_frame_from_s2_lnglat", (DL_FUNC) &_s2_data_frame_from_s2_lnglat, 1},
921935
{"_s2_cpp_s2_closest_feature", (DL_FUNC) &_s2_cpp_s2_closest_feature, 2},
922936
{"_s2_cpp_s2_farthest_feature", (DL_FUNC) &_s2_cpp_s2_farthest_feature, 2},
937+
{"_s2_cpp_s2_closest_edges", (DL_FUNC) &_s2_cpp_s2_closest_edges, 4},
923938
{"_s2_cpp_s2_may_intersect_matrix", (DL_FUNC) &_s2_cpp_s2_may_intersect_matrix, 5},
924939
{"_s2_cpp_s2_contains_matrix", (DL_FUNC) &_s2_cpp_s2_contains_matrix, 3},
925940
{"_s2_cpp_s2_within_matrix", (DL_FUNC) &_s2_cpp_s2_within_matrix, 3},

src/s2-matrix.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,39 @@ IntegerVector cpp_s2_farthest_feature(List geog1, List geog2) {
165165
return op.processVector(geog1);
166166
}
167167

168+
// [[Rcpp::export]]
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+
168201
// ----------- indexed binary predicate operators -----------
169202

170203
class IndexedMatrixPredicateOperator: public IndexedBinaryGeographyOperator<List, IntegerVector> {

tests/testthat/test-s2-matrix.R

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,36 @@ test_that("s2_closest|farthest_feature() works", {
1414
expect_identical(s2_data_tbl_countries$name[country_match_farthest], "New Zealand")
1515
})
1616

17+
test_that("s2_closest_edges() works", {
18+
expect_identical(
19+
s2_closest_edges(
20+
"POINT (0 0)",
21+
c("POINT (0 0)", "POINT (0 1)", "POINT (0 2)", "POINT (0 3)"),
22+
k = 1
23+
),
24+
list(1L)
25+
)
26+
27+
expect_identical(
28+
s2_closest_edges(
29+
"POINT (0 0)",
30+
c("POINT (0 0)", "POINT (0 1)", "POINT (0 2)", "POINT (0 3)"),
31+
k = 2,
32+
min_distance = 0
33+
),
34+
list(2L)
35+
)
36+
37+
expect_identical(
38+
s2_closest_edges(
39+
"POINT (0 0)",
40+
c("POINT (0 0)", "POINT (0 1)", "POINT (0 2)", "POINT (0 3)"),
41+
k = 5
42+
) %>% lapply(sort),
43+
list(1:4)
44+
)
45+
})
46+
1747
test_that("matrix predicates work", {
1848
expect_identical(
1949
s2_contains_matrix(

0 commit comments

Comments
 (0)