From 07c64d816f229ecb9f1e00ed8cee707a194b7791 Mon Sep 17 00:00:00 2001 From: Jason Flower Date: Mon, 22 Apr 2024 21:54:28 +1000 Subject: [PATCH 1/2] fix sf_to_grid for issue #17 --- DESCRIPTION | 2 ++ R/sf_to_grid.R | 98 ++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 82 insertions(+), 18 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index f9612b9..7d6d795 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -7,6 +7,8 @@ License: GPL (>= 3) + file LICENSE Encoding: UTF-8 Roxygen: list(markdown = TRUE) RoxygenNote: 7.3.1 +Depends: + R (>= 3.5.0) Imports: dplyr, exactextractr, diff --git a/R/sf_to_grid.R b/R/sf_to_grid.R index 5db88da..3eeb01d 100644 --- a/R/sf_to_grid.R +++ b/R/sf_to_grid.R @@ -27,52 +27,96 @@ sf_to_grid <- function(dat, spatial_grid, matching_crs, name, feature_names, ant {if(antimeridian) sf::st_shift_longitude(.) else .} dat_cropped <- dat %>% - {if(antimeridian & !unique(sf::st_geometry_type(.)) %in% c("POINT", "MULTIPOINT")) { + {if(all(antimeridian & !(c("POINT", "MULTIPOINT") %in% unique(sf::st_geometry_type(.))))) { sf::st_break_antimeridian(., lon_0 = 180) %>% sf::st_shift_longitude()} - else if(antimeridian & unique(sf::st_geometry_type(.)) %in% c("POINT", "MULTIPOINT")){ + else if(all(antimeridian & (c("POINT", "MULTIPOINT") %in% unique(sf::st_geometry_type(.))))){ sf::st_shift_longitude(.)} else .} %>% sf::st_crop(grid_temp) %>% sf::st_transform(sf::st_crs(spatial_grid)) } - if(nrow(dat_cropped) == 0) stop("No data in grid.") - if(is.null(feature_names)){ if(is.null(name)) name <- "data" + if(nrow(dat_cropped) == 0){ + message("No ", name, " in grid") + if(is_raster){ + return(spatial_grid %>% + terra::subst(1,0) %>% + setNames(name)) + } else{ + return(spatial_grid %>% + dplyr::mutate({{name}} := 0, .before = ncol(.))) + } + } + dat_grouped <- dat_cropped %>% dplyr::mutate({{name}} := 1, .before = 1) %>% dplyr::group_by({{name}}) %>% dplyr::summarise() %>% dplyr::ungroup() %>% - {if(sf::st_geometry_type(., by_geometry = FALSE) == "GEOMETRY") sf::st_cast(., to = "MULTIPOLYGON") else .} + {if(all(c("POLYGON", "MULTIPOLYGON") %in% (sf::st_geometry_type(.) %>% unique() %>% as.character()))) sf::st_cast(., to = "MULTIPOLYGON") else .} } else { + if(nrow(dat_cropped) == 0){ + message("No data in grid") + + layer_names <- unique(dat[[feature_names]]) + + if(is_raster){ + return(spatial_grid %>% + terra::subst(1,0) %>% + rep(length(layer_names)) %>% + setNames(layer_names)) + } else{ + new_cols <- data.frame(matrix(data = 0, ncol= length(layer_names), nrow=nrow(spatial_grid), dimnames=list(NULL, layer_names))) + return(spatial_grid %>% + dplyr::bind_cols(new_cols)) + } + } + dat_grouped <- dat_cropped %>% dplyr::group_by(.data[[feature_names]]) %>% dplyr::summarise() %>% dplyr::ungroup() %>% - {if(sf::st_geometry_type(., by_geometry = FALSE) == "GEOMETRY") sf::st_cast(., to = "MULTIPOLYGON") else .} + {if(all(c("POLYGON", "MULTIPOLYGON") %in% (sf::st_geometry_type(.) %>% unique() %>% as.character()))) sf::st_cast(., to = "MULTIPOLYGON") else .} } - if(is_raster){ + nms <- dat_grouped[[1]] - exactextractr::coverage_fraction(spatial_grid, dat_grouped) %>% + temp_rast <- exactextractr::coverage_fraction(spatial_grid, dat_grouped) %>% terra::rast() %>% setNames(nms) %>% terra::mask(spatial_grid) %>% - {if(apply_cutoff) terra::classify(., matrix(c(-1, cutoff, NA, cutoff, 1.2, 1), ncol = 3, byrow = TRUE), include.lowest = FALSE, right = FALSE) else .} %>% - .[[lapply(., function(x) !all(terra::values(x) == 0)) %>% unlist()]] #removes all zero layers and by default also all NA layers + {if(apply_cutoff) terra::classify(., matrix(c(-1, cutoff, 0, cutoff, 1.2, 1), ncol = 3, byrow = TRUE), include.lowest = FALSE, right = FALSE) else .} + + #check if some features were cropped out; if so, need to add raster layers with zeroes in + if(is.null(feature_names)) { + return(temp_rast) + } else if(all(unique(dat[[feature_names]]) %in% nms)){ + return(temp_rast) + } else{ + missing_feature_nms <- setdiff(unique(dat[[feature_names]]), nms) + + missing_features_rast <- spatial_grid %>% + terra::subst(1,0) %>% + rep(length(missing_feature_nms)) %>% + setNames(missing_feature_nms) + + return(c(temp_rast, missing_features_rast)) + } } else{ grid_has_extra_cols <- if(ncol(spatial_grid)>1) TRUE else FALSE if(grid_has_extra_cols) extra_cols <- sf::st_drop_geometry(spatial_grid) - spatial_grid_with_id <- spatial_grid %>% + spatial_grid_geom <- spatial_grid %>% sf::st_geometry() %>% - sf::st_sf() %>% + sf::st_sf() + + spatial_grid_with_id <- spatial_grid_geom %>% dplyr::mutate(cellID = 1:nrow(.)) spatial_grid_with_area <- spatial_grid_with_id %>% @@ -86,12 +130,13 @@ sf_to_grid <- function(dat, spatial_grid, matching_crs, name, feature_names, ant intersected_data_list <- list() for (layer in layer_names) { - temp_intersection <- sf::st_intersection(spatial_grid_with_id, dat_list[[layer]]) + temp_intersection <- sf::st_intersection(spatial_grid_with_id, dat_list[[layer]]) %>% + {if(all(sf::st_is_valid(.))) . else sf::st_make_valid(.)} if(nrow(temp_intersection)>0) { intersected_data_list[[layer]] <- temp_intersection %>% dplyr::mutate(area = as.numeric(sf::st_area(.))) %>% - sf::st_drop_geometry(.) %>% + sf::st_drop_geometry() %>% dplyr::full_join(spatial_grid_with_area, ., by = c("cellID")) %>% dplyr::mutate(perc_area = .data$area / .data$area_cell, .keep = "unused", .before = 1) %>% dplyr::mutate(perc_area = dplyr::case_when(is.na(.data$perc_area) ~ 0, @@ -104,15 +149,32 @@ sf_to_grid <- function(dat, spatial_grid, matching_crs, name, feature_names, ant ) %>% dplyr::select({{layer}}) }} + } else{ + intersected_data_list[[layer]] <- spatial_grid_geom %>% + dplyr::mutate({{layer}} := 0, .before = 1) } } + intersected_data_df <- lapply(intersected_data_list, function(x) sf::st_drop_geometry(x)) %>% + do.call(cbind, .) - if(length(intersected_data_list) == 0) stop("No data in grid") + nms <- colnames(intersected_data_df) - lapply(intersected_data_list, function(x) sf::st_drop_geometry(x) %>% dplyr::select(dplyr::where(~any(. != 0)))) %>% - do.call(cbind, .) %>% + intersected_data_sf <- intersected_data_df %>% {if(grid_has_extra_cols) cbind(extra_cols, .) else .} %>% sf::st_set_geometry(sf::st_geometry(intersected_data_list[[1]])) %>% sf::st_set_geometry("geometry") - } + + if(is.null(feature_names)) { + return(intersected_data_sf) + } else if(all(unique(dat[[feature_names]]) %in% nms)){ + return(intersected_data_sf) + } else{ + missing_feature_nms <- setdiff(unique(dat[[feature_names]]), nms) + + new_cols <- data.frame(matrix(data = 0, ncol= length(missing_feature_nms), nrow=nrow(spatial_grid), dimnames=list(NULL, missing_feature_nms))) + return(intersected_data_sf %>% + dplyr::bind_cols(new_cols)) + + } + } } From a966f7e5abe0d7fe60a828ebc7ede342bec03b18 Mon Sep 17 00:00:00 2001 From: Jason Flower Date: Tue, 23 Apr 2024 11:04:26 +1000 Subject: [PATCH 2/2] increment version --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 7d6d795..e1cc783 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: spatialgridr Title: Grid spatial data for conservation planning purposes -Version: 0.0.0.9000 +Version: 0.0.0.9001 Authors@R: person(given = "Jason", family = "Flower", email = "jflower@ucsb.edu", role = c("aut", "cre"), comment = c(ORCID = "0000-0002-6731-8182")) Description: An R package that allows easy gridding of spatial data. Principally intended for spatial conservation planning uses. License: GPL (>= 3) + file LICENSE