Skip to content

Commit c0743c9

Browse files
Merge pull request #37 from inbo/36-new-function-calculate_polygon_centroid
36 new function calculate polygon centroid
2 parents 46e08d0 + 945ae25 commit c0743c9

15 files changed

+454
-8
lines changed

DESCRIPTION

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package: fistools
22
Title: Tools & data used for wildlife management & invasive species in Flanders
3-
Version: 0.2.0
3+
Version: 1.0.0
44
Authors@R: c(
55
person(given = "Sander", middle = "", family = "Devisscher", "sander.devisscher@inbo.be",
66
role = c("aut", "cre"), comment = c(ORCID = "0000-0003-2015-5731")),
@@ -17,7 +17,7 @@ Encoding: UTF-8
1717
LazyData: true
1818
LazyDataCompression: xz
1919
Roxygen: list(markdown = TRUE)
20-
RoxygenNote: 7.3.1
20+
RoxygenNote: 7.3.2
2121
Imports:
2222
dplyr (>= 1.1.4),
2323
httr (>= 1.4.7),
@@ -32,5 +32,7 @@ Imports:
3232
devtools (>= 2.4.5),
3333
DBI (>= 1.2.3),
3434
sf (>= 1.0.16),
35-
osmdata (>= 0.2.5),
36-
units (>= 0.8.5)
35+
units (>= 0.8.5),
36+
sp (>= 2.1.4),
37+
mapview (>= 2.11.2),
38+
osmdata (>= 0.2.5)

NAMESPACE

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
# Generated by roxygen2: do not edit by hand
22

3+
export(CRS_extracter)
34
export(UUID_List)
45
export(apply_grtsdb)
6+
export(calculate_polygon_centroid)
57
export(check)
68
export(cleanup_sqlite)
79
export(colcompare)
810
export(collect_osm_features)
911
export(download_dep_media)
1012
export(download_gdrive_if_missing)
1113
export(download_seq_media)
14+
export(install_sp)
1215
export(label_converter)
16+
importClassesFrom(sp,CRS)
1317
importFrom(magrittr,"%>%")
18+
importFrom(sp,CRS)
19+
importFrom(utils,install.packages)

R/CRS_extracter.R

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#' CRS_extracter
2+
#'
3+
#' Extracts a coordinate reference system (CRS) from a library of commonly used CRS codes
4+
#' This also fixes issues with the belge lambert 72 we use.
5+
#' The EPSG code is 31370, but the proj4string is not the same as the one in the EPSG database.
6+
#'
7+
#' @param CRS A character string with the name of the CRS
8+
#' @param EPSG A logical indicating whether the output should be in EPSG format
9+
#'
10+
#' @return A CRS object
11+
#'
12+
#' @examples
13+
#' \dontrun{
14+
#' # Example of how to use the CRS_extracter function
15+
#' crs_wgs <- CRS_extracter("WGS", EPSG = FALSE)
16+
#' crs_bel <- CRS_extracter("BEL72", EPSG = FALSE)
17+
#'
18+
#' epsg_wgs <- CRS_extracter("WGS", EPSG = TRUE)
19+
#'
20+
#' # Example of how to use the CRS_extracter function in combination with the sf & leaflet packages
21+
#' library(sf)
22+
#' library(tidyverse)
23+
#' library(leaflet)
24+
#' boswachterijen_df <- boswachterijen$boswachterijen_2024 %>%
25+
#' st_transform(crs_bel) %>%
26+
#' mutate(centroid = st_centroid(geometry)) %>%
27+
#' st_drop_geometry() %>%
28+
#' st_as_sf(sf_column_name = "centroid",
29+
#' crs = crs_bel) %>%
30+
#' st_transform(crs_wgs)
31+
#'
32+
#' leaflet() %>%
33+
#' addTiles() %>%
34+
#' addPolylines(data = boswachterijen$boswachterijen_2024) %>%
35+
#' addCircles(data = boswachterijen_df, color = "red", radius = 1000)
36+
#' }
37+
#'
38+
#' @importFrom sp CRS
39+
#' @importClassesFrom sp CRS
40+
#' @export
41+
#' @author Sander Devisscher
42+
43+
CRS_extracter <- function(CRS,
44+
EPSG = TRUE){
45+
46+
install_sp()
47+
48+
Lib_CRS <- lib_crs
49+
50+
if(grepl("wgs", CRS, ignore.case = TRUE)){
51+
CRS <- "WGS"
52+
}
53+
54+
if(grepl("bel", CRS, ignore.case = TRUE)){
55+
CRS <- "BEL72"
56+
}
57+
58+
if(EPSG == TRUE){
59+
CRS_output <- CRS(Lib_CRS$EPSG[CRS == Lib_CRS$CRS_Naam])
60+
}else{
61+
CRS_output <- CRS(Lib_CRS$Proj4s[CRS == Lib_CRS$CRS_Naam])
62+
}
63+
64+
return(CRS_output)
65+
}

R/calculate_polygon_centroid.R

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
#' Calculate the centroid of a polygon
2+
#'
3+
#' This function calculates the centroid of a polygon and returns the latitude, longitude and uncertainty of the centroid.
4+
#'
5+
#' @param sf_df A sf object with polygons
6+
#' @param id A character string with the name of the column containing the unique identifier
7+
#'
8+
#' @return A data frame with the unique identifier, latitude, longitude and uncertainty of the centroid
9+
#'
10+
#' @examples
11+
#' \dontrun{
12+
#' # Example of how to use the calculate_polygon_centroid function
13+
#' # Load the necessary data
14+
#' boswachterijen <- boswachterijen$boswachterijen_2024
15+
#'
16+
#' # add a unique identifier to the sf object
17+
#' boswachterijen <- boswachterijen %>%
18+
#' dplyr::mutate(UUID = as.character(row_number()))
19+
#'
20+
#' # Calculate the centroid of the polygons
21+
#' centroids_data_final <- calculate_polygon_centroid(sf_df = boswachterijen, id = "UUID")
22+
#'
23+
#' # Plot the polygons and the centroids
24+
#' library(leaflet)
25+
#'
26+
#' # Sample 1 polygon and 1 centroid to plot using id
27+
#' sample_id <- sample(centroids_data_final$UUID, 1)
28+
#'
29+
#' leaflet() %>%
30+
#' addProviderTiles("CartoDB.Positron") %>%
31+
#' addPolygons(data = boswachterijen %>% dplyr::filter(UUID == sample_id),
32+
#' weight = 1, color = "black", fillOpacity = 0.5) %>%
33+
#' addCircles(data = centroids_data_final %>% dplyr::filter(UUID == sample_id),
34+
#' lat = ~centroidLatitude, lng = ~centroidLongitude, radius = 5,
35+
#' color = "black") %>%
36+
#' addCircles(data = centroids_data_final %>% dplyr::filter(UUID == sample_id),
37+
#' lat = ~centroidLatitude, lng = ~centroidLongitude, radius = ~centroidUncertainty,
38+
#' color = "red", weight = 1)
39+
#' }
40+
#'
41+
#' @export
42+
#' @author Sander Devisscher
43+
44+
calculate_polygon_centroid <- function(sf_df, id){
45+
# Checks ####
46+
## Check if the input is an sf object ####
47+
if(!inherits(sf_df, "sf")){
48+
stop("The input should be an sf object")
49+
}
50+
51+
## Check if the id is a character string ####
52+
if(!is.character(id)){
53+
id <- as.character(id)
54+
}
55+
56+
## Check if the id is in the sf object ####
57+
if(!(id %in% names(sf_df))){
58+
stop("The id is not in the sf object")
59+
}
60+
61+
## Check if the id is unique ####
62+
if(length(unique(sf_df[[id]])) != nrow(sf_df)){
63+
warning("The id is not unique >> the function will continue but the output will be incorrect >> try to add a unique identifier to the sf object")
64+
}
65+
66+
# prepare data ####
67+
## Rename the id column to "id" ####
68+
id_col <- id
69+
names(sf_df)[names(sf_df) == id_col] <- "id"
70+
71+
## Extract the CRS ####
72+
crs_wgs <- CRS_extracter("wgs")
73+
74+
## Transform the data to the correct CRS ####
75+
sf_df <- sf_df %>%
76+
sf::st_transform(crs_wgs)
77+
78+
## Calculate the number of vertices ####
79+
sf_df <- sf_df %>%
80+
sf::st_make_valid() %>%
81+
dplyr::mutate(NbrVertex = mapview::npts(sf_df, by_feature = TRUE))
82+
83+
# Caculate Centroids ####
84+
## Calculate centroids from sp_df ####
85+
centroids <- sf_df %>%
86+
sf::st_centroid()
87+
88+
## Create output ####
89+
centroids_data_final <- data.frame()
90+
91+
UUIDS <- unique(sf_df$id)
92+
93+
## Create a progress bar ####
94+
pb <- progress::progress_bar$new(format = " [:bar] :percent ETA: :eta",
95+
total = nrow(sf_df),
96+
clear = FALSE,
97+
width = 60)
98+
99+
## Calculate the distance between the centroid and the polygon ####
100+
for(u in UUIDS){
101+
### Update the progress bar ####
102+
pb$tick()
103+
### Filter the sf data ####
104+
sf_df_sub <- sf_df %>%
105+
dplyr::filter(id == u)
106+
### Check if the polygon is valid ####
107+
if(nrow(sf_df_sub) == 0){
108+
next
109+
warning(paste0("no fortified shape for ", u))
110+
}else{
111+
### split the polygons into vertrex points ####
112+
sf_df_sub <- sf_df_sub %>%
113+
st_cast("MULTIPOINT") %>%
114+
st_cast("POINT", do_split = TRUE)
115+
116+
### Check if the number of points is equal to the number of vertices ####
117+
if(nrow(sf_df_sub) != unique(sf_df_sub$NbrVertex)){
118+
warning(paste0("The number of points is not equal to the number of vertices for ", u))
119+
}
120+
}
121+
122+
### Filter the centroid data ####
123+
centroids_sub <- centroids %>%
124+
dplyr::filter(id == u)
125+
126+
### Check if the centroid is valid ####
127+
if(nrow(centroids_sub)==0){
128+
next
129+
warning(paste0("no centroid for ", u))
130+
}
131+
### Calculate the distance ####
132+
distance <- st_distance(sf_df_sub, centroids_sub) %>%
133+
units::drop_units()
134+
135+
### Calculate the maximum distance ####
136+
maxDistance <- round(max(distance, na.rm = TRUE))
137+
138+
### Set the maximum distance to 4 if it is smaller than 4 ####
139+
if(maxDistance < 4){
140+
warning(paste0("The maximum distance is smaller than 4 for ", u, " >> setting the maximum distance to 4"))
141+
maxDistance <- 4 # reasonable accuracy of handheld GPS devices
142+
}
143+
144+
### Add the maximum distance to the centroid data ####
145+
centroids_sub$centroidUncertainty <- maxDistance
146+
centroids_data_final <- rbind(centroids_data_final, centroids_sub)
147+
}
148+
149+
## Transform the data to a data frame ####
150+
centroids_data_final <- centroids_data_final %>%
151+
dplyr::mutate(centroidLatitude = sf::st_coordinates(geometry)[, 2],
152+
centroidLongitude = sf::st_coordinates(geometry)[, 1]) %>%
153+
dplyr::select(id,
154+
centroidLatitude,
155+
centroidLongitude,
156+
centroidUncertainty) %>%
157+
sf::st_drop_geometry()
158+
159+
## Rename the id column to the original name ####
160+
names(centroids_data_final)[names(centroids_data_final) == "id"] <- id_col
161+
162+
## Return the data ####
163+
return(centroids_data_final)
164+
}

R/install_sp.R

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#' install sp
2+
#'
3+
#' Helper function that installs sp when missing, from a tarball.
4+
#'
5+
#' @details
6+
#' The "sp" package should be unloaded from the namespace when it is not needed anymore.
7+
#' Every function that uses "sp" should start with a call to this function.
8+
#' And end with a call to `unloadNamespace("sp")`.
9+
#' This is because the "sp" package is known to cause conflicts with other packages.
10+
#'
11+
#' @param force A logical indicating whether the installation should be forced
12+
#'
13+
#' @importFrom utils install.packages
14+
#'
15+
#' @export
16+
#'
17+
#' @author Sander Devisscher
18+
19+
install_sp <- function(force = FALSE) {
20+
if (!rlang::is_installed("sp")) {
21+
print("sp is not installed, installing it now")
22+
# download the tarball from CRAN https://cran.r-project.org/src/contrib/Archive/sp/sp_2.1-3.tar.gz
23+
# and place it in a temporary directory
24+
tempfile <- tempfile()
25+
download.file("https://cran.r-project.org/src/contrib/Archive/sp/sp_2.1-3.tar.gz", destfile = tempfile)
26+
27+
# install the tarball
28+
install.packages(tempfile, repos = NULL, type = "source", force = force)
29+
}
30+
print("sp is installed \U0001F389")
31+
}

R/lib_crs.R

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#' Lib CRS
2+
#'
3+
#' Library of commonly used coordinate reference system (CRS) codes
4+
#'
5+
#' @format A named list with the following elements:
6+
#' \describe{
7+
#' \item{CRS_Naam}{ID of the CRS}
8+
#' \item{Proj4s}{Javascript Object Notation (JSON) of the CRS}
9+
#' \item{ESPG}{EPSG code of the CRS}
10+
#' }
11+
#' @source WGS <https://epsg.io/4326>
12+
#' @source BEL72 <https://epsg.io/31300>
13+
"lib_crs"

data/lib_crs.rda

378 Bytes
Binary file not shown.

data_raw/Lib_CRS.csv

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
CRS_Naam;Proj4s;EPSG
2+
WGS;"+proj=longlat +datum=WGS84 +no_defs";"EPSG:4326"
3+
BEL72;"+init=epsg:31370";"EPSG:31370"

data_raw/sp_2.1-3.tar.gz

1.19 MB
Binary file not shown.

man/CRS_extracter.Rd

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

0 commit comments

Comments
 (0)