Skip to content

Commit 11a2dab

Browse files
authored
Merge pull request #14 from FishEcologyScience/CST-564-client-support
Handling points falling outside the polygon
2 parents 8fac158 + 504dccc commit 11a2dab

File tree

8 files changed

+139
-16
lines changed

8 files changed

+139
-16
lines changed

DESCRIPTION

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Package: SAVM
22
Title: Submerged aquatic vegetation model
3-
Version: 0.0.1.9004
4-
Date: 2025-07-15
3+
Version: 0.0.1.9005
4+
Date: 2025-08-12
55
Authors@R: c(
66
person(given = "Kevin",
77
family = "Cazelles",

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
S3method(preview_grid,sav_data)
44
export(compute_fetch)
5+
export(identify_outsiders)
56
export(invert_polygon)
67
export(plot_sav_density)
78
export(plot_sav_distribution)

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# SAVM (devel)
22

3+
* `compute_fetch()` has a new argument `remove_outsider` to remove points falling outside the polygon. Also, `identify_outsiders()` helps visualize outsiders (see #13).
34
* Model and plot functions have been adjusted to handle `sf` objects (see #11).
45
* The element `mean_fetch` returned by `compute_fetch()` is now a `sf` object (see #9).
56
* `preview_grid()` allows to preview grid (see #8).

R/compute_fetch.R

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
#' @param crs {`object`}\cr{}
1717
#' Coordinate reference system (CRS) passed to [sf::st_crs()], used to
1818
#' transform `points` and `polygon`.
19+
#' @param remove_outsiders {`logical`}\cr{}
20+
#' Remove points outside the polygion. An error will be thrown if all points
21+
#' are rejected. Default is `FALSE`.
22+
#'
1923
#'
2024
#' @details
2125
#' Wind fetch is the unobstructed distance over which wind travels
@@ -88,7 +92,7 @@
8892
#' plot(res$transect_lines |> sf::st_geometry(), add = TRUE, col = 2, lwd = 0.5)
8993
#' }
9094
compute_fetch <- function(
91-
points, polygon, max_dist = 15, n_bearings = 16, wind_weights = NULL, crs = NULL) {
95+
points, polygon, max_dist = 15, n_bearings = 16, wind_weights = NULL, crs = NULL, remove_outsiders = FALSE) {
9296
valid_points(points)
9397
points$id_point <- seq_len(nrow(points))
9498
valid_polygon(polygon)
@@ -130,7 +134,7 @@ compute_fetch <- function(
130134
}
131135
}
132136

133-
valid_polygon_contains_points(points, polygon)
137+
valid_polygon_contains_points(points, polygon, remove_outsiders)
134138

135139
if (is.null(wind_weights)) {
136140
d_direction <- data.frame(
@@ -187,14 +191,32 @@ compute_fetch <- function(
187191
)
188192
),
189193
by = "id_point"
190-
) |>
194+
) |>
191195
dplyr::select(
192196
c("id_point", "fetch_km", "weighted_fetch_km")
193197
),
194198
transect_lines = transect_lines
195199
)
196200
}
197201

202+
203+
#' @describeIn compute_fetch Identify outsiders using a plot where points
204+
#' located outside the polygon are highlighted in red.
205+
#' @export
206+
identify_outsiders <- function(points, polygon) {
207+
plot(polygon |> sf::st_geometry(), border = 1)
208+
plot(
209+
points |> sf::st_geometry(),
210+
# there godd be more than one polygon
211+
col = suppressMessages(
212+
2 - apply(sf::st_within(points, polygon, sparse = FALSE), 1, any)
213+
),
214+
pch = 19,
215+
cex = 2,
216+
add = TRUE
217+
)
218+
}
219+
198220
# HELPERS
199221

200222
is_proj_unit_meter <- function(x) {
@@ -227,19 +249,33 @@ valid_polygon <- function(x) {
227249
}
228250
}
229251

230-
valid_polygon_contains_points <- function(points, polygon) {
252+
valid_polygon_contains_points <- function(points, polygon, remove_outsiders = FALSE) {
253+
if (remove_outsiders) {
254+
points <- remove_points_outside_polygon(points, polygon)
255+
if (!(points |> nrow())) {
256+
rlang::abort("All points were outside the polygon considered.")
257+
}
258+
}
231259
chk <- suppressMessages({
232260
sf::st_contains(polygon, points, sparse = FALSE) |>
233-
apply(2, any) |>
234-
all()
261+
apply(2, any)
235262
})
236-
if (chk) {
263+
if (all(chk)) {
237264
TRUE
238265
} else {
239-
rlang::abort("`polygon` must include `points`.")
266+
rlang::abort("`polygon` must include all points in `points`.
267+
Use `remove_outsiders = TRUE` to remove points outside the polygon.
268+
Alternatively use `identify_outsiders()` to vizualize outsiders.
269+
")
240270
}
241271
}
242272

273+
remove_points_outside_polygon <- function(points, polygon) {
274+
suppressMessages(
275+
points[apply(sf::st_within(points, polygon, sparse = FALSE), 1, any), ]
276+
)
277+
}
278+
243279
valid_direction <- function(direction) {
244280
if (!all(direction >= 0 & direction <= 360)) {
245281
rlang::abort("All directions must be within the range [0, 360].")

inst/example/le_in_out.geojson

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"type": "FeatureCollection",
3+
"features": [
4+
{
5+
"type": "Feature",
6+
"properties": {},
7+
"geometry": {
8+
"coordinates": [
9+
-81.36588837103685,
10+
42.242309732588126
11+
],
12+
"type": "Point"
13+
}
14+
},
15+
{
16+
"type": "Feature",
17+
"properties": {},
18+
"geometry": {
19+
"coordinates": [
20+
-80.77248459680594,
21+
42.1838784204125
22+
],
23+
"type": "Point"
24+
}
25+
},
26+
{
27+
"type": "Feature",
28+
"properties": {},
29+
"geometry": {
30+
"coordinates": [
31+
-81.4927603895497,
32+
42.96924260540226
33+
],
34+
"type": "Point"
35+
}
36+
},
37+
{
38+
"type": "Feature",
39+
"properties": {},
40+
"geometry": {
41+
"coordinates": [
42+
-80.45704257888741,
43+
43.00937083157342
44+
],
45+
"type": "Point"
46+
}
47+
}
48+
]
49+
}

man/compute_fetch.Rd

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

tests/testthat/test-compute-fetch.R

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ le_pt_mid <- system.file("example", "le_middle.geojson", package = "SAVM") |>
1212
le_pt_out <- system.file("example", "le_land.geojson", package = "SAVM") |>
1313
sf::st_read(quiet = TRUE)
1414

15+
le_pt_in_out <- system.file("example", "le_in_out.geojson", package = "SAVM") |>
16+
sf::st_read(quiet = TRUE)
17+
1518

1619
test_that("helpers work", {
1720
expect_false(is_proj_unit_meter(4326))
@@ -39,13 +42,22 @@ test_that("helpers work", {
3942
"All directions must be within the range [0, 360].",
4043
fixed = TRUE
4144
)
45+
#
46+
expect_identical(
47+
remove_points_outside_polygon(le_pt_in_out, le_bound),
48+
le_pt_in_out[1:2, ]
49+
)
50+
expect_identical(
51+
remove_points_outside_polygon(le_pt_out, le_bound) |> nrow(),
52+
0L
53+
)
4254
})
4355

4456

4557
test_that("helpers work", {
4658
withr::with_options(
4759
list(savm.verbose = "quiet"),
48-
{
60+
{
4961
expect_error(
5062
compute_fetch(le_pt, le_bound, n_bearings = 3),
5163
"`n_bearings` should be equal or greater than 4."
@@ -59,8 +71,12 @@ test_that("helpers work", {
5971
"Projection units must be meters."
6072
)
6173
expect_error(
62-
compute_fetch(le_pt_out, le_bound_merc),
63-
"`polygon` must include `points`"
74+
compute_fetch(le_pt_in_out, le_bound_merc),
75+
"`polygon` must include all points in `points`"
76+
)
77+
expect_error(
78+
compute_fetch(le_pt_out, le_bound_merc, remove_outsiders = TRUE),
79+
"All points were outside the polygon considered"
6480
)
6581
}
6682
)
@@ -82,14 +98,16 @@ test_that("compute_fetch() work", {
8298
expect_s3_class(res$transect_lines, "sf")
8399
expect_true(
84100
all(
85-
c("direction", "weight", "transect_length", "rank") %in%
86-
names(res$transect_lines)
101+
c("direction", "weight", "transect_length", "rank") %in%
102+
names(res$transect_lines)
87103
)
88104
)
89105
# test with a point in the middle of the lake
90106
res2 <- compute_fetch(le_pt_mid, le_bound_merc, max_dist = 15)
91107
expect_equal(res2$mean_fetch$fetch_km, 15)
92108
expect_equal(res2$mean_fetch$weighted_fetch_km, 15)
109+
res3 <- compute_fetch(le_pt_mid, le_bound_merc, max_dist = 15, remove_outsiders = TRUE)
110+
expect_identical(res2, res3)
93111
}
94112
)
95113
})

vignettes/get_started.Rmd

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ study_depth <- stars::read_stars(system.file("example", "le_bathy.tiff", package
127127
> ***Note on spatial projections***: *The `SAVM` package works with spatial coordinates in meters and the WGS 84 / UTM zone 17N ([epsg:32617](https://epsg.io/32617)) projection as a default option since it was originally developped for use in the Great Lakes (WGS 84 / UTM zone 18N ([epsg:32618](https://epsg.io/32618)) is also a an option). Other viable options in a North American context are NAD83 / UTM zone 17N ([epsg:26917](https://epsg.io/26917)) and NAD83(CSRS) / UTM zone 17N ([egsg::2958](https://epsg.io/2958)).*
128128
129129

130+
130131
## Compute fetch and extract depth
131132

132133
### Compute fetch
@@ -148,6 +149,9 @@ fetch$mean_fetch |> sf::st_write("fetch.shp")
148149
```
149150

150151

152+
> ***Note for points outside the polygon***: If any points fall outside the polygon, an error is raised. To remove outsiders, set `remove_outsider = TRUE`. Alternatively, you can identify outsiders using `identify_outsider()`.
153+
154+
151155
### Extract depth
152156

153157
To extract depth data from a raster file for our study points, we use `st_extract()` from the package `stars`. Note that the points and the raster must use the same projection.

0 commit comments

Comments
 (0)