diff --git a/.Rbuildignore b/.Rbuildignore index 64ca8c9..a654359 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -1,3 +1,12 @@ ^data-raw$ +^_pkgdown\.yml$ +^docs$ +^pkgdown$ +^README\.md\.old$ +^README\.Rmd$ +^.*\.Rproj$ +^\.Rproj\.user$ +^LICENSE\.md$ +^pkg_build\.R$ ^doc$ ^Meta$ diff --git a/.gitignore b/.gitignore index 121933e..31e1bdd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,11 @@ wd/in wd/out +wd/code .Rhistory wd/.Rhistory -inst/doc -doc Meta +README.md.old +README.Rmd +.Rproj.user +pkg_build.R +doc diff --git a/DESCRIPTION b/DESCRIPTION index 54f12a4..e8e7b3a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: foot -Title: A package for processing building footprints -Version: 0.4.1 -Date: 2020-08-13 +Title: An R package for processing building footprints morphometrics +Version: 0.5 +Date: 2020-10-19 Authors@R: c(person(given="WorldPop Research Group, University of Southampton", role="aut"), @@ -14,15 +14,20 @@ Authors@R: role="ctb"), person(family="Leasure", given="Doug", - role="ctb") + role="ctb"), + person(family="Darin", + give="Edith", + role="ctb") ) License: GPL-3.0 URL: https://github.com/wpgp/foot +BugReports: https://github.com/wpgp/foot/issues Imports: data.table, purrr, sf, stars, + abind, lwgeom, doParallel, parallel, @@ -30,12 +35,13 @@ Imports: units, filelock LazyData: true -Description: Support for morphometric calculations and zonal summaries of measures derived from building footprint polygons. +Description: Support for morphometric calculations of building footprints implemented in R. It provides flexible tools for processing 2D vector polygon representations of structures. The functionality includes basic geometry and morphology measures, distance and clustering metrics. These calculations are supported with helper functions for spatial intersections to define zonal indices and tiled reading/writing of data files. Encoding: UTF-8 RoxygenNote: 7.1.0 Suggests: knitr, - rmarkdown + rmarkdown, + formatR VignetteBuilder: knitr Depends: R (>= 3.5) diff --git a/NAMESPACE b/NAMESPACE index 35110c6..a33f046 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -4,60 +4,17 @@ S3method(calculate_bigfoot,character) S3method(calculate_bigfoot,sf) S3method(calculate_bigfoot,sp) S3method(calculate_footstats,character) -S3method(calculate_footstats,list) S3method(calculate_footstats,sf) S3method(calculate_footstats,sfc) S3method(calculate_footstats,sp) S3method(fs_angle_entropy,sf) +S3method(fs_angle_entropy,sfc) S3method(fs_angle_entropy,sp) -S3method(fs_area_cv,sf) -S3method(fs_area_cv,sp) -S3method(fs_area_max,sf) -S3method(fs_area_max,sp) -S3method(fs_area_mean,sf) -S3method(fs_area_mean,sp) -S3method(fs_area_median,sf) -S3method(fs_area_median,sp) -S3method(fs_area_min,sf) -S3method(fs_area_min,sp) -S3method(fs_area_sd,sf) -S3method(fs_area_sd,sp) -S3method(fs_area_total,sf) -S3method(fs_area_total,sp) -S3method(fs_compact_mean,sf) -S3method(fs_compact_mean,sp) -S3method(fs_compact_median,sf) -S3method(fs_compact_median,sp) S3method(fs_count,sf) S3method(fs_count,sp) -S3method(fs_nndist_mean,sf) -S3method(fs_nndist_mean,sp) -S3method(fs_nndist_median,sf) -S3method(fs_nndist_median,sp) -S3method(fs_nndist_sd,sf) -S3method(fs_nndist_sd,sp) S3method(fs_nnindex,sf) +S3method(fs_nnindex,sfc) S3method(fs_nnindex,sp) -S3method(fs_perim_cv,sf) -S3method(fs_perim_cv,sp) -S3method(fs_perim_max,sf) -S3method(fs_perim_max,sp) -S3method(fs_perim_mean,sf) -S3method(fs_perim_mean,sp) -S3method(fs_perim_median,sf) -S3method(fs_perim_median,sp) -S3method(fs_perim_min,sf) -S3method(fs_perim_min,sp) -S3method(fs_perim_sd,sf) -S3method(fs_perim_sd,sp) -S3method(fs_perim_total,sf) -S3method(fs_perim_total,sp) -S3method(fs_settled,sf) -S3method(fs_settled,sp) -S3method(fs_shape_mean,sf) -S3method(fs_shape_mean,sp) -S3method(fs_shape_median,sf) -S3method(fs_shape_median,sp) S3method(gridTiles,RasterLayer) S3method(gridTiles,stars) S3method(zonalIndex,character) @@ -66,50 +23,22 @@ S3method(zonalIndex,sf) S3method(zonalIndex,sfc) S3method(zonalIndex,sp) S3method(zonalIndex,stars) -export(adjacentCells) export(calculate_bigfoot) export(calculate_footstats) export(fs_angle_entropy) export(fs_area) -export(fs_area_cv) -export(fs_area_max) -export(fs_area_mean) -export(fs_area_median) -export(fs_area_min) -export(fs_area_sd) -export(fs_area_total) export(fs_compact) -export(fs_compact_mean) -export(fs_compact_median) export(fs_count) export(fs_mbc) export(fs_mbr) export(fs_nndist) -export(fs_nndist_mean) -export(fs_nndist_median) -export(fs_nndist_sd) export(fs_nnindex) -export(fs_perim_cv) -export(fs_perim_max) -export(fs_perim_mean) -export(fs_perim_median) -export(fs_perim_min) -export(fs_perim_sd) -export(fs_perim_total) export(fs_perimeter) -export(fs_settled) export(fs_shape) -export(fs_shape_mean) -export(fs_shape_median) -export(get_fs_group) -export(get_fs_metrics) -export(get_fs_units) +export(fs_varlist) export(gridTiles) +export(is.fs_varlist) export(list_fs) -export(make_circular_filter) -export(make_templateGrid) -export(make_templateHeader) -export(write_imageBinary) export(zonalIndex) import(data.table) import(doParallel) @@ -117,7 +46,6 @@ import(filelock) import(foreach) import(iterators) import(lwgeom) -import(mmap) import(parallel) import(sf) import(stars) diff --git a/R/calculate_footstats.R b/R/calculate_footstats.R index a1883e8..8a024b0 100644 --- a/R/calculate_footstats.R +++ b/R/calculate_footstats.R @@ -4,89 +4,116 @@ #' @description Calculate groups of metrics for building footprint datasets #' @param X object with building footprint polygons. This argument can take #' multiple spatial types, including \code{sf} and \code{sp}, or a filepath -#' string to a file, or a list where each member provides a spatial object or -#' a filepath string. -#' @param index A spatial polygon file defining areas. Or a character or numeric -#' value identifying a column within \code{X} which provides a zonal index for -#' summarising values. Alternatively a vector of indices can be provided. If -#' omitted all observations with -#' \code{X} are assumed to be within one zone. -#' @param metrics character vector. Names of footprint statistics in the form of -#' "fs_area_mean", etc. Other options include \code{ALL} or \code{NODIST} to -#' calculate all available metrics and all except nearest neighbour distances, -#' respectively. -#' @param minArea numeric. Minimum footprint area to filter \code{X}. -#' @param maxArea numeric. Maximum footprint area to filter \code{X}. +#' string to a file. +#' @param zone A spatial polygon file defining areas. Or a string identifying a +#' column within \code{X} which provides a zonal index for summarising values. +#' Alternatively a vector of indices (with \code{length(index)==nrow(X)}) can +#' be provided. If omitted, all observations in \code{X} are assumed to be +#' within one zone. +#' @param what list of strings naming the columns or built-in geometry measures to +#' calculate for each footprint. Other options include \code{'all'} or +#' \code{'nodist'} to calculate all available characteristics and all except +#' nearest-neighbour distance metrics. +#' @param how list of strings naming functions to be used to calculate summary +#' statistics. The functions can be built-in functions (e.g. "mean","sd"), or +#' user-defined function names. +#' @param controlZone (optional) named list. Setting controls passed on to +#' \code{\link[foot]{zonalIndex}}. Elements can include \code{zoneName} and +#' \code{method}. #' @param controlUnits (optional) named list. Elements can include #' \code{areaUnit}, \code{perimUnit}, and \code{distUnit}. The values for #' these items should be strings that can be coerced into a \code{units} #' object. -#' @param clip (optional). Logical. Should polygons which span pixel zones be -#' clipped? Default is \code{FALSE}. -#' @param gridded Should a gridded output be created? Default \code{FALSE}. -#' @param template (optional). When creating a gridded output, a supplied -#' \code{stars} or \code{raster} dataset to align the data. -#' @param outputPath (optional). When creating a gridded output, a path for the -#' location of the output. -#' @param outputTag (optional). A character string that will be added tagged to -#' the beginning of the output gridded files. If \code{X} is a \code{list}, -#' then the names of the list items will be used or a vector of tags of the -#' same length as \code{X} should be supplied. -#' @param driver character. Currently supports geotiff ("GTiff"). +#' @param controlDist (optional) named list to override default +#' options for distance calculations. Elements can include \code{maxSearch} +#' and \code{method}. Ignored if \code{metrics} does not include a distance +#' calculation. See \code{\link[foot]{fs_nndist}}. +#' @param filter (optional) named list with \code{minArea} and \code{maxArea}. +#' These are numeric values to filter footprints prior to processing. Default +#' values are \code{NULL} and do not filter any records. #' @param verbose logical. Should progress messages be printed. -#' Default \code{False}. +#' Default \code{TRUE}. #' -#' @return a \code{data.table} with an 'index' column and named columns for each -#' footprint statistic. Alternatively, geoTiffs of +#' @details \code{calculate_footstats} is a wrapper function combining several +#' internal functions from \code{foot}. It can calculate various geometric +#' measures for each footprint polygon, including area, perimeter, +#' compactness, shape, angle of rotation, and nearest neighbour distance. To +#' find the list of built-in characteristics and summary metrics, use +#' \code{list_fs()}. +#' +#' The \code{what} and \code{how} arguments are lists specifying the +#' characteristics and the summary metrics to calculated, respectively. Each +#' "how" function will be applied to each "what" characteristic. To apply a +#' metric to only a subset of characteristics, nested lists, with groups of +#' characteristics and functions can be supplied. See examples. +#' +#' The \code{control} arguments are sets of lists with named arguments that +#' pass on these parameters to other \code{foot} functions. These are +#' optional. +#' +#' @return a \code{data.table} with an index column identifying the zones and +#' named columns for each footprint summary statistic. #' #' @examples -#' data(kampala) +#' data("kampala", package="foot") #' buildings <- kampala$buildings #' adminzones <- kampala$adminZones #' +#' # no summary statistics, just geometry calculations +#' calculate_footstats(buildings, adminzones, what=list("area","perimeter")) +#' +#' # average building footprint area #' calculate_footstats(buildings, -#' index=adminzones, -#' metrics=c("fs_area_mean","fs_area_cv")) +#' zone=adminzones, +#' what="area", how="mean") +#' +#' # calculate multiple metrics - nested lists to group arguments +#' calculate_footstats(buildings, adminzones, +#' what=list(list("area"), list("perimeter")), +#' how=list(list("mean","sum"), list("sd","cv"))) +#' +#' @seealso \link[foot]{zonalIndex}, \link[foot]{fs_nndist}, \link[foot]{list_fs} #' #' @aliases calculate_footstats #' @rdname calculate_footstats #' #' @export -calculate_footstats <- function(X, - index=NULL, - metrics='all', - minArea=NULL, - maxArea=NULL, - controlUnits=NULL, - clip=FALSE, - gridded=FALSE, - template=NULL, - outputPath=NULL, - outputTag=NULL, - driver="GTiff", - verbose=FALSE) UseMethod("calculate_footstats") +calculate_footstats <- function(X, zone=NULL, what='all', how=NULL, + controlZone=list(zoneName="zoneID", + method="centroid"), + controlUnits=list(areaUnit="m^2", + perimUnit="m", + distUnit="m"), + controlDist=list(maxSearch=100, + method="centroid", + unit=controlUnits$distUnit), + filter=list(minArea=NULL, + maxArea=NULL), + verbose=TRUE) UseMethod("calculate_footstats") #' @name calculate_footstats #' @export -calculate_footstats.sf <- function(X, index=NULL, metrics='all', - minArea=NULL, - maxArea=NULL, - controlUnits=NULL, - clip=FALSE, - gridded=FALSE, template=NULL, - outputPath=NULL, - outputTag=NULL, - driver="GTiff", - verbose=FALSE){ +calculate_footstats.sf <- function(X, zone=NULL, what='all', how=NULL, + controlZone=list(zoneName="zoneID", + method="centroid"), + controlUnits=list(areaUnit="m^2", + perimUnit="m", + distUnit="m"), + controlDist=list(maxSearch=100, + method="centroid", + unit=controlUnits$distUnit), + filter=list(minArea=NULL, + maxArea=NULL), + verbose=TRUE){ if(any(!sf::st_geometry_type(X) %in% c("POLYGON", "MULTIPOLYGON") )){ message("Footprint statistics require polygon shapes.") stop() } - result <- calc_fs_internal(X, index, metrics, minArea, maxArea, - controlUnits, clip, gridded, - template, outputPath, outputTag, driver, verbose) + result <- calc_fs_internal(X, zone, what, how, + controlZone, controlUnits, controlDist, + filter, verbose) return(result) } @@ -94,27 +121,26 @@ calculate_footstats.sf <- function(X, index=NULL, metrics='all', #' @name calculate_footstats #' @export -calculate_footstats.sfc <- function(X, index=NULL, metrics='all', - minArea=NULL, - maxArea=NULL, - controlUnits=NULL, - clip=FALSE, - gridded=FALSE, template=NULL, - outputPath=NULL, - outputTag=NULL, - driver="GTiff", - verbose=FALSE){ +calculate_footstats.sfc <- function(X, zone=NULL, what='all', how=NULL, + controlZone=list(zoneName="zoneID", + method="centroid"), + controlUnits=list(areaUnit="m^2", + perimUnit="m", + distUnit="m"), + controlDist=list(maxSearch=100, + method="centroid", + unit=controlUnits$distUnit), + filter=list(minArea=NULL, + maxArea=NULL), + verbose=TRUE){ # cast to sf for consistency X <- sf::st_as_sf(X) - result <- calculate_footstats(X, index=index, metrics=metrics, - minArea=minArea, maxArea=maxArea, + result <- calculate_footstats(X, zone=zone, what=what, how=how, + controlZone=controlZone, controlUnits=controlUnits, - clip=clip, - gridded=gridded, template=template, - outputPath=outputPath, - outputTag=outputTag, - driver=driver, + controlDist=controlDist, + filter=filter, verbose=verbose) return(result) } @@ -122,402 +148,348 @@ calculate_footstats.sfc <- function(X, index=NULL, metrics='all', #' @name calculate_footstats #' @export -calculate_footstats.sp <- function(X, index=NULL, metrics='all', - minArea=NULL, - maxArea=NULL, - controlUnits=NULL, - clip=FALSE, - gridded=FALSE, template=NULL, - outputPath=NULL, - outputTag=NULL, - driver="GTiff", - verbose=FALSE){ +calculate_footstats.sp <- function(X, zone=NULL, what='all', how=NULL, + controlZone=list(zoneName="zoneID", + method="centroid"), + controlUnits=list(areaUnit="m^2", + perimUnit="m", + distUnit="m"), + controlDist=list(maxSearch=100, + method="centroid", + unit=controlUnits$distUnit), + filter=list(minArea=NULL, + maxArea=NULL), + verbose=TRUE){ # convert to sf X <- sf::st_as_sf(X) - result <- calculate_footstats(X, index=index, metrics=metrics, - minArea=minArea, maxArea=maxArea, - controlUnits=controlUnits, clip=clip, - gridded=gridded, template=template, - outputPath=outputPath, - outputTag=outputTag, - driver=driver, - verbose=verbose) + result <- calculate_footstats(X, zone, what, how, + controlZone, controlUnits, controlDist, + filter, verbose) return(result) } #' @name calculate_footstats #' @export -calculate_footstats.character <- function(X, index=NULL, metrics='all', - minArea=NULL, - maxArea=NULL, - controlUnits=NULL, - clip=FALSE, - gridded=FALSE, template=NULL, - outputPath=NULL, - outputTag=NULL, - driver="GTiff", - verbose=FALSE){ +calculate_footstats.character <- function(X, zone=NULL, what='all', how=NULL, + controlZone=list(zoneName="zoneID", + method="centroid"), + controlUnits=list(areaUnit="m^2", + perimUnit="m", + distUnit="m"), + controlDist=list(maxSearch=100, + method="centroid", + unit=controlUnits$distUnit), + filter=list(minArea=NULL, + maxArea=NULL), + verbose=TRUE){ # attempt to read in file X <- sf::st_read(X) - result <- calculate_footstats(X, index=index, metrics=metrics, - minArea, maxArea, - controlUnits=controlUnits, - clip=clip, - gridded=gridded, template=template, - outputPath=outputPath, - outputTag=outputTag, - driver=driver, - verbose=verbose) + result <- calculate_footstats(X, zone, what, how, + controlZone, controlUnits, controlDist, + filter, verbose) return(result) } -#' @name calculate_footstats -#' @export -calculate_footstats.list <- function(X, index=NULL, metrics='all', - minArea=NULL, - maxArea=NULL, - controlUnits=NULL, - clip=FALSE, - gridded=FALSE, template=NULL, - outputPath=NULL, - outputTag=NULL, - driver="GTiff", - verbose=FALSE){ +# internal processing function +calc_fs_internal <- function(X, zone, what, how, + controlZone, controlUnits, controlDist, + filter, verbose){ - if(is.null(outputTag)){ - if(is.null(names(X))){ - outputTag <- seq_along(X) + if(is.na(st_crs(X))){ + stop("Footprints must have a spatial reference.") + } + + # get characteristics and metrics + if(verbose){ cat(paste0("Selecting metrics \n")) } + if("all" %in% what){ + argsDF <- list_fs(what="all", how=how) + } else if("nodist" %in% what){ + argsDF <- list_fs(what="nodist", how=how) + } else if(!is.null(how)){ + argsX <- crossargs(what, how) + argsDF <- do.call(rbind, argsX) + argsDF <- unique(argsDF) + } + + if(is.null(how)){ + argsDF <- list_fs(what, how=NULL) + uchars <- unlist(unique(argsDF)) + if("nndist" %in% argsDF){ + calcD <- TRUE } else{ - outputTag <- names(X) + calcD <- FALSE } } else{ - if(length(outputTag) != length(X)){ - stop("Invalid output tag length.") - } + uchars <- unlist(unique(argsDF$cols)) + calcD <- ifelse(nrow(argsDF[argsDF$cols=="nndist" & + argsDF$funs != "nnindex",])>0, + TRUE, FALSE) } - # expand other arguments - recycling values - args <- list(index=index, metrics=metrics, - minArea=minArea, maxArea=maxArea, - controlUnits=controlUnits, clip=clip, - gridded=gridded, template=template, - outputPath=outputPath, driver=driver, - verbose=verbose) - maxL <- max(lengths(args), length(X)) - args <- lapply(args, rep, length.out=maxL) - - result <- lapply(seq_along(X), FUN=function(i){ - calculate_footstats(X[[i]], - index=args$index[i], - metrics=args$metrics[i], - minArea=args$minArea[i], - maxArea=args$maxArea[i], - controlUnits=args$controlUnits[i], - clip=args$clip[i], - gridded=args$gridded[i], - template=args$template[i], - outputPath=args$outputPath[i], - outputTag=outputTag[[i]], - driver=args$driver[i], - verbose=args$verbose[i]) - }) + # set defaults for controls + if(verbose){ cat("Setting control values. \n") } + # defaults for zonal indexing + providedZone <- controlZone + controlZone <- list(zoneName="zoneID", method="centroid") + controlZone <- controlZone[order(names(controlZone))] + # update with provide values + if(!is.null(providedZone)){ + providedZone <- providedZone[order(names(providedZone))] + controlZone[names(controlZone) %in% names(providedZone)] <- providedZone + } - return(result) # should the list be simplified? -} - - -calc_fs_internal <- function(X, index, metrics, - minArea, maxArea, - controlUnits, clip, - gridded, template, - outputPath, outputTag, driver, verbose){ + # get full set of units - sort alphabetically to make matches + providedUnits <- controlUnits + controlUnits <- list(areaUnit="m^2", perimUnit="m", distUnit="m") + controlUnits <- controlUnits[order(names(controlUnits))] + # update with provide values + if(!is.null(providedUnits)){ + providedUnits <- providedUnits[order(names(providedUnits))] + controlUnits[names(controlUnits) %in% names(providedUnits)] <- providedUnits + } - if(is.na(st_crs(X))){ - stop("Polygons must have a spatial reference.") + # defaults for distance measures + providedDist <- controlUnits + controlDist=list(maxSearch=100, + method="centroid", + unit=controlUnits$distUnit) + controlDist <- controlDist[order(names(controlDist))] + # update with provide values + if(!is.null(providedDist)){ + providedDist <- providedDist[order(names(providedDist))] + controlDist[names(controlDist) %in% names(providedDist)] <- providedDist } - if(verbose){ cat("Creating zonal index \n") } - if(!is.null(index)){ - if(inherits(index, "Spatial")){ - index <- sf::st_as_sf(index) + # create zonal index + if(!is.null(zone)){ + if(verbose){ cat("Creating zonal index \n") } + if(inherits(zone, "Spatial")){ + zone <- sf::st_as_sf(zone) } - if(inherits(index, "sf")){ - if(any(sf::st_geometry_type(index) %in% c("POLYGON","MULTIPOLYGON"))){ - indexZones <- index # make copy - indexZones$index <- 1:nrow(indexZones) - - X <- zonalIndex(X, index, returnObject=TRUE, clip=clip) - index <- "zoneID" + if(inherits(zone, "sf")){ + if(sf::st_crs(zone) != sf::st_crs(X)){ + stop("Spatial reference for footprints and zones does not match.") + } + if(all(sf::st_geometry_type(zone) %in% c("POLYGON","MULTIPOLYGON"))){ + if(!controlZone$zoneName %in% colnames(zone)){ + zone[[controlZone$zoneName]] <- 1:nrow(zone) + } + # create index + X <- zonalIndex(X, + zone=zone, + zoneField=controlZone$zoneName, + method=controlZone$method, + returnObject=TRUE) # check for no intersecting if(is.null(X)){ return(NULL) } - } else{ - warning("Index must be a polygon or a column name. Ignoring input.") - index <- NULL + # get the geometry attribute + geomField <- attr(X, which="sf_column") + } else{ # found 'sf' data but not polygons + warning("Zone must be a spatial polygon. Ignoring input.") + zone <- NULL + X[[controlZone$zoneName]] <- rep(1, nrow(X)) } # drop non-intersecting buildings - X <- subset(X, !is.na(zoneID)) + # X <- subset(X, !is.na(controlZone$zoneName)) + X <- sf::st_as_sf(subset(data.table::setDT(X), !is.na(controlZone$zoneName))) # check for no intersecting if(nrow(X) == 0){ + if(verbose){ cat("No records found in zones. \n") } return(NULL) } - } else if(class(index) == "numeric"){ - if(length(index) != nrow(X)) stop("Index length does not match footprints.") - - } else if(class(index) == "character"){ - index <- index[1] - if(!index %in% names(X)) stop("Index column not found in footprints.") + } else if(FALSE){ #is raster or is stars){ - } else{ - warning("Index must be a polygon or a column name/index. Ignoring input.") - index <- rep(1, nrow(X)) + } else if(is.character(zone)){ # could be column or a vector of codes + if(length(zone)==1){ # do we only have 1 footprint? + if(nrow(X)>1){ # it must be a column name + if(!zone %in% colnames(X)){ + stop("Index column not found in footprints.") + } else{ + controlZone$zoneName <- zone + zone <- NULL + } + } # potential issue if 1 row X and 1 column name - won't affect calcs + } else if(length(zone != nrow(X))){ + stop("Invalid length of zonal index.") + } + } else if(is.numeric(zone)){ + if(length(zone) != nrow(X)){ + stop("Invalid length of zonal index.") + } else{ + X[[controlZone$zoneName]] <- zone + zone <- NULL + } } + } else{ # zone is null + # if(verbose) cat("No zone index provided, treating as one group. \n") + X[[controlZone$zoneName]] <- rep(1, nrow(X)) } + # check for empty zone intersection if(is.null(X) | nrow(X) == 0){ return(NULL) } - - # get full set of units - sort alphabetically to make matches - providedUnits <- controlUnits - - controlUnits <- list(areaUnit=get_fs_units("fs_area_mean"), - perimUnit=get_fs_units("fs_perim_mean"), - distUnit=get_fs_units("fs_nndist_mean")) - controlUnits <- controlUnits[order(names(controlUnits))] - # update with provide values - if(!is.null(providedUnits)){ - providedUnits <- providedUnits[order(names(providedUnits))] - controlUnits[names(controlUnits) %in% names(providedUnits)] <- providedUnits + + # pre-calculate 'whats' + if('area' %in% uchars | + !is.null(filter$minArea) | !is.null(filter$maxArea)){ + if(!'area' %in% colnames(X)){ + if(verbose){ cat("Pre-calculating areas \n") } + X[['area']] <- fs_area(X, unit=controlUnits$areaUnit) + } else{ + if(verbose){ cat("Area data column already exists \n") } + } + } + + # filter records + if(!is.null(filter$minArea)){ + if(verbose) { cat(paste0(" Filtering features larger than ", + filter$minArea," \n")) } + X <- sf::st_as_sf(subset(data.table::setDT(X), + area > units::set_units(filter$minArea, + controlUnits$areaUnit, + mode="standard"))) + } + if(!is.null(filter$maxArea)){ + if(verbose) { cat(paste0(" Filtering features smaller than ", + filter$maxArea," \n")) } + + X <- sf::st_as_sf(subset(data.table::setDT(X), + area < units::set_units(filter$maxArea, + controlUnits$areaUnit, + mode="standard"))) + } + if(nrow(X) == 0){ # filter removed all? + if(verbose){ cat("No records found in zones. \n") } + return(NULL) } - # select and expand list of metrics - if(verbose){ cat("Selecting metrics \n") } - metrics <- get_fs_metrics(short_names=metrics, group=metrics) - # if(toupper(metrics[1]) == 'ALL'){ - # metrics <- get_fs_metrics("ALL") - # } - # - # if(toupper(metrics[1]) == "NODIST"){ - # metrics <- get_fs_metrics("NODIST") - # } - - # if(any(grepl("area_cv", metrics, fixed=T))){ - # metrics <- c(metrics, "fs_area_mean", "fs_area_sd") - # metrics <- metrics[!grepl("area_cv", metrics)] - # area_cv <- TRUE - # } else{ - # area_cv <- FALSE - # } - # - # if(any(grepl("perim_cv", metrics, fixed=T))){ - # metrics <- c(metrics, "fs_perim_mean", "fs_perim_sd") - # metrics <- metrics[!grepl("perim_cv", metrics)] - # perim_cv <- TRUE - # } else{ - # perim_cv <- FALSE - # } - - # if(any(grepl("compact", metrics, fixed=T))){ - # compact <- TRUE - # } else{ - # compact <- FALSE - # } - - nnIndex <- FALSE - if(any(grepl("nnindex", metrics, fixed=T))){ - # metrics <- c(metrics, "fs_nndist_mean", "fs_count") - metrics <- metrics[!grepl("nnindex", metrics)] - nnIndex <- TRUE - # - if(!exists("indexZones")){ - warning("Nearest neighbour index requires zonal areas.") - nnIndex <- FALSE - # zonalArea <- data.table::data.table(index=indexZones$index, - # zoneArea=fs_area(indexZones, - # unit=controlUnits$areaUnit)) + if('perimeter' %in% uchars){ + if(!'perimeter' %in% colnames(X)){ + if(verbose){ cat("Pre-calculating perimeters \n") } + X[['perimeter']] <- fs_perimeter(X, unit=controlUnits$perimUnit) + } else{ + if(verbose){ cat("Perimeter data column already exists \n") } } - } - - if(any(grepl("angle", metrics, fixed=T))){ - normalize <- TRUE } - # pre-calculate unit geometry measures - if(any(grepl("area", metrics, fixed=T)) | - any(grepl("compact", metrics, fixed=T)) | - !is.null(minArea) | !is.null(maxArea)){ - if(!"fs_area" %in% names(X)){ - if(verbose){ cat("Pre-calculating footprint areas \n") } - X[["fs_area"]] <- fs_area(X, unit=controlUnits$areaUnit) + if('angle' %in% uchars){ + if(!'angle' %in% colnames(X)){ + if(verbose){ cat("Pre-calculating angles \n") } + X[['angle']] <- fs_mbr(X, returnShape=FALSE) + } else{ + if(verbose){ cat("Angle data column already exists \n") } } } - if(any(grepl("perim", metrics, fixed=T)) | - any(grepl("compact", metrics, fixed=T))){ - if(!"fs_perim" %in% names(X)){ - if(verbose){ cat("Pre-calculating footprint perimeters \n")} - X[["fs_perim"]] <- fs_perimeter(X, unit=controlUnits$perimUnit) + if('shape' %in% uchars){ + if(!'shape' %in% colnames(X)){ + if(verbose){ cat("Pre-calculating shape \n") } + X[['shape']] <- fs_shape(X, unit=controlUnits$areaUnit) + } else{ + if(verbose){ cat("Shape data column already exists \n") } } } - if(any(grepl("nndist", metrics, fixed=T))){ - if(!"fs_nndist" %in% names(X)){ - if(verbose){ cat("Pre-calculating nearest neighbour distances \n") } - X[["fs_nndist"]] <- fs_nndist(X, unit=controlUnits$distUnit) + if('compact' %in% uchars){ + if(!'compact' %in% colnames(X)){ + if(verbose){ cat("Pre-calculating compactness \n") } + X[['compact']] <- fs_compact(X) + } else{ + if(verbose){ cat("Shape data column already exists \n") } } } - if(any(grepl("angle", metrics, fixed=T))){ - if(!"fs_angle" %in% names(X)){ - if(verbose){ cat("Pre-calculating angles \n") } - X[["fs_angle"]] <- fs_mbr(X) - } + if('settled' %in% uchars){ + if(!'settled' %in% colnames(X)){ + X[['settled']] <- 1 + } } - # filter records - if(!is.null(minArea)){ - if(verbose) { cat(paste0("Filtering features larger than ", minArea," \n")) } - X <- subset(X, fs_area > units::as_units(minArea, - controlUnits$areaUnit)) + if('nndist' %in% uchars & calcD){ + if(!'dist' %in% colnames(X)){ + if(verbose){ cat("Pre-calculating nearest neighbour distances \n") } + X[['nndist']] <- fs_nndist(X, + maxSearch=controlDist$maxSearch, + method=controlDist$method, + unit=controlUnits$distUnit) + } else{ + if(verbose){ cat("NN distance data column already exists. \n") } + } } - if(!is.null(maxArea)){ - if(verbose) { cat(paste0("Filtering features smaller than ", maxArea," \n")) } - X <- subset(X, fs_area < units::as_units(maxArea, - controlUnits$areaUnit)) + if(is.null(how)){ + if(verbose){ cat("No summary functions found, returning metrics. \n\n") } + return(data.table::data.table(sf::st_drop_geometry(X[, uchars]))) } - if(nrow(X) == 0){ # filter removed all? - return(NULL) + # check for invalid characteristic/function pairs + # but need to exclude user-supplied columns + # if(verbose){ cat("Checking for valid function parameter pairings. \n") } + if(is.null(zone)){ + argsDF <- argsDF[!argsDF$funs == "nnindex", ] } + # # update parameters + # argsDF$params <- argsDF$cols + # argsDF[argsDF$funs=="nnindex" & + # argsDF$cols=="nndist", "params"] <- fs_varlist(geomField, zone) - # creating the names of the functions to call - metrics <- unique(metrics) - metrics_calc <- paste0(metrics, "_calc") - if(verbose){ cat("\nCalculating ", length(metrics_calc) ," metrics ... \n")} + # convert to data.frame + DT <- data.table::setDT(X) + data.table::setkeyv(DT, controlZone$zoneName) - result <- lapply(seq_along(metrics_calc), function(current_metric){ - if(verbose){ cat(" ", metrics[[current_metric]], " \n") } - func <- get(metrics_calc[[current_metric]], mode="function") - - # getUnit <- fs_footprint_metrics$default_units[match(metrics[[current_metric]], fs_footprint_metrics$name)] - getUnit <- switch(get_fs_group(metrics[[current_metric]]), - area = controlUnits$areaUnit, - perim = controlUnits$perimUnit, - dist = controlUnits$distUnit) + # main processing loop -- working! + if(verbose){ cat("\nCalculating ", nrow(argsDF) ," metrics ... \n")} + + result <- lapply(1:nrow(argsDF), function(i){ + params <- unlist(argsDF[i, "cols"]) + calc_func <- unlist(argsDF[i, "funs"]) + if(verbose){ cat(" ", params, calc_func, " \n") } + newNm <- paste(paste(params, collapse="_"), calc_func, sep="_") - assign("unit", value=getUnit, envir=parent.env(environment())) - arguments <- names(formals(func)) + if(calc_func == "nnindex"){ + calc_func <- gen_nnindex(zone, controlZone$zoneName, controlUnits$distUnit) + params <- geomField + } - tryCatch(do.call(what=func, - args=mget(arguments, - envir=parent.env(environment()), - ifnotfound=list(NULL)) - ), + tryCatch(DT[, setNames(list(do.call(calc_func, unname(.SD))), + newNm), + by=eval(controlZone$zoneName), + .SDcols=params], error = function(e){ message("") stop(e) } - ) - }) + ) + }) + + # result <- lapply(1:nrow(argsDF), function(i){ + # params <- unlist(argsDF[i, "cols"]) + # calc_func <- unlist(argsDF[i, "funs"]) + # if(verbose){ cat(" ", params, " ", calc_func, " \n") } + # newNm <- paste(paste(params, collapse="_"), calc_func, sep="_") + # + # tryCatch(DT[, setNames(list(do.call(calc_func, unname(.SD))), + # newNm), + # by=eval(controlZone$zoneName), + # .SDcols=params], + # error = function(e){ + # message("") + # stop(e) + # } + # ) + # }) # merge all merged_result <- Reduce(function(...) merge(...), result) - - # if(area_cv){ - # merged_result[, fs_area_cv:=fs_area_ha_sd / fs_area_ha_mean] - # } - # - # if(perim_cv){ - # merged_result[, fs_perim_cv:=fs_perim_m_sd / fs_perim_m_mean] - # } - if(nnIndex){ - if(verbose){ cat(" Calculating nearest neighbour index... \n")} - nniDT <- fs_nnindex(X, index=indexZones, unit=controlUnits$distUnit) - # nniDT <- merged_result[, list(index, fs_nndist_m_mean, fs_count)] - # nniDT <- merge(nniDT, zonalArea, by.x="index", by.y="zoneID") - # nniDT[, fs_nnindex := fs_nndist_m_mean / (0.5 * sqrt(zoneArea / fs_count)), by=index] - # units(nniDT$fs_nnindex) <- NULL - - merged_result <- merge(merged_result, - nniDT[, list(index, fs_nnindex)], - by="index") - } if(verbose){ cat("Finished calculating metrics. \n") } - # output - if(gridded==TRUE){ - if(verbose){ cat("\nCreating gridded datasets \n") } - - if(is.null(outputPath)){ - outputPath <- tempdir() - } else{ - dir.create(outputPath) - } - - # get template for aligning gridded output - if(is.null(template)){ - template <- stars::st_as_stars(sf::st_bbox(X), values=NA_real_) # default resolution - } else if(inherits(template, "stars")){ - # no change? - } else if(class(template) == "RasterLayer"){ - template <- stars::st_as_stars(template) - } else if(class(template) == "character"){ - template <- stars::read_stars(template)[1] - } else{ - stop("Error opening template dataset.") - } - - template[!is.na(template)] <- NA - - if(exists("indexZones")){ # process buildings - spatial_result <- merge(indexZones, merged_result, by.x="index", by.y="index") - - } else{ - if(sf::st_crs(template) != sf::st_crs(X)){ - stop("CRS for buildings and template raster not matching.") - } - - # use building centroids to rasterize - if(!any(sf::st_geometry_type(X) %in% c("POINT"))){ - X <- sf::st_centroid(X) - } - - mp <- sf::st_cast(sf::st_geometry(X), - to="MULTIPOINT", - ids=X[[index]], - group_or_split=TRUE) - X <- sf::st_sf(index=unique(X[[index]]), geometry=mp, crs=sf::st_crs(X)) - names(X)[1] <- index - - spatial_result <- merge(X, merged_result, by.x=index, by.y="index") - } - - if(verbose){ cat("Writing grids... \n") } - for(val in names(merged_result)[!names(merged_result) %in% "index"]){ - if(is.null(outputTag)){ - outName <- paste(val, "tif", sep=".") - } else{ - outName <- paste(outputTag, "_", val, "tif", sep=".") - } - - if(verbose){ cat(" ", outName, "\n") } - stars::st_rasterize(spatial_result[val], - file=file.path(outputPath, outName), - template=template, - driver=driver, - options="compress=LZW") - } - if(verbose){ cat("Finished writing grids\n") } - } - - # if(verbose){ cat("Finished!\n") } return(merged_result) } - + \ No newline at end of file diff --git a/R/calculate_pixel_footstats.R b/R/calculate_pixel_footstats.R index 83cfbe0..9fc472d 100644 --- a/R/calculate_pixel_footstats.R +++ b/R/calculate_pixel_footstats.R @@ -8,27 +8,37 @@ #' multiple spatial types, including \code{sf} and \code{sp}, or a filepath #' string to a file, or a list where each member provides a spatial object or #' a filepath string. -#' @param metrics character vector. Names of footprint statistics in the form of -#' "fs_area_mean", etc. Other options include \code{ALL} or \code{NODIST} to -#' calculate all available metrics and all except nearest neighbour distances, -#' respectively. +#' @param what list of strings naming the columns or built-in geometry measures to +#' calculate for each footprint. Other options include \code{'all'} or +#' \code{'nodist'} to calculate all available characteristics and all except +#' nearest-neighbour distance metrics. +#' @param how list of strings naming functions to be used to calculate summary +#' statistics. The functions can be built-in functions (e.g. "mean","sd"), or +#' user-defined function names. #' @param focalRadius numeric. Distance in meters for a buffer around each #' template pixel. Creates a focal processing window for metrics. -#' @param minArea numeric. Minimum footprint area to filter \code{X}. -#' @param maxArea numeric. Maximum footprint area to filter \code{X}. +#' @param controlZone (optional) named list. Setting controls passed on to +#' \code{\link[foot]{zonalIndex}}. Elements can include \code{zoneName} and +#' \code{method}. #' @param controlUnits (optional) named list. Elements can include #' \code{areaUnit}, \code{perimUnit}, and \code{distUnit}. The values for #' these items should be strings that can be coerced into a \code{units} #' object. -#' @param clip (optional). Logical. Should polygons which span pixel zones be -#' clipped? Default is \code{FALSE}. +#' @param controlDist (optional) named list to override default +#' options for distance calculations. Elements can include \code{maxSearch} +#' and \code{method}. Ignored if \code{metrics} does not include a distance +#' calculation. See \code{\link[foot]{fs_nndist}}. +#' @param filter (optional) named list with \code{minArea} and \code{maxArea}. +#' These are numeric values to filter footprints prior to processing. Default +#' values are \code{NULL} and do not filter any records. #' @param template (optional). When creating a gridded output, a supplied #' \code{stars} or \code{raster} dataset to align the data. #' @param parallel logical. Should a parallel backend be used to process the -#' tiles. +#' tiles. Default is \code{TRUE}. #' @param nCores number of CPU cores to use if \code{parallel} is \code{TRUE}. +#' Default is 1 less than the available CPUs. #' @param tileSize number of pixels per side of a tile. Can be a vector of -#' length 2 (rows, column pixels). Ignored if n provided. Default is 1000. +#' length 2 (rows, column pixels). Default is \code{c(500, 500)}. #' @param outputPath (optional). When creating a gridded output, a path for the #' location of the output. Default is the temp directory. #' @param outputTag (optional). A character string that will be added to the @@ -36,7 +46,7 @@ #' @param tries (optional). The number of attempts to write a tile to the output #' file. Default is 100. #' @param verbose logical. Should progress messages be printed. Default -#' \code{False}. +#' \code{TRUE}. #' #' @details \code{calculate_bigfoot} provides a wrapper for a workflow to #' process vector polygons of structures to create a gridded output summary of @@ -53,14 +63,13 @@ #' @return Invisible. Returns a vector of paths to the output files. #' #' @examples -#' data(kampala) +#' data("kampala", package="foot") #' buildings <- kampala$buildings #' templateGrid <- kampala$mastergrid #' #' calculate_bigfoot(X=buildings, -#' metrics=c("shape_mean", -#' "count", -#' "perim_total"), +#' what=list(list("shape"), list("perimeter")), +#' how=list(list("mean"), list("sum")), #' controlUnits=list(areaUnit="m^2"), #' minArea=50, # footprints must be larger than 50 m^2 #' maxArea=1000, # footprints must be smaller than 1000 m^2 @@ -74,6 +83,8 @@ #' outGrid <- raster::raster(file.path(tempdir(), "kampala_count.tif")) #' raster::plot(outGrid) #' +#' @seealso \link[foot]{calculate_footstats} +#' #' @import doParallel #' @import parallel #' @import foreach @@ -87,49 +98,64 @@ #' #' @export calculate_bigfoot <- function(X, - metrics='all', + what='all', how='all', focalRadius=0, - minArea=NULL, - maxArea=NULL, - controlUnits=NULL, - clip=FALSE, + controlZone=list(zoneName="zoneID", + method="centroid"), + controlUnits=list(areaUnit="m^2", + perimUnit="m", + distUnit="m"), + controlDist=list(maxSearch=100, + method="centroid", + unit=controlUnits$distUnit), + filter=list(minArea=NULL, + maxArea=NULL), template=NULL, tileSize=c(500, 500), parallel=TRUE, nCores=max(1, parallel::detectCores()-1), - outputPath=tempdir(), + outputPath=getwd(), outputTag=NULL, tries=100, - verbose=FALSE) UseMethod("calculate_bigfoot") + verbose=TRUE) UseMethod("calculate_bigfoot") #' @name calculate_bigfoot #' @export calculate_bigfoot.sf <- function(X, - metrics='all', + what='all', how='all', focalRadius=0, - minArea=NULL, - maxArea=NULL, - controlUnits=NULL, - clip=FALSE, + controlZone=list(zoneName="zoneID", + method="centroid"), + controlUnits=list(areaUnit="m^2", + perimUnit="m", + distUnit="m"), + controlDist=list(maxSearch=100, + method="centroid", + unit=controlUnits$distUnit), + filter=list(minArea=NULL, + maxArea=NULL), template=NULL, tileSize=c(500, 500), parallel=TRUE, nCores=max(1, parallel::detectCores()-1), - outputPath=tempdir(), + outputPath=getwd(), outputTag=NULL, tries=100, - verbose=FALSE){ + verbose=TRUE){ if(any(!sf::st_geometry_type(X) %in% c("POLYGON", "MULTIPOLYGON") )){ message("Footprint statistics require polygon shapes.") stop() } - result <- calc_fs_px_internal(X, metrics, focalRadius, - minArea, maxArea, controlUnits, clip, - template, tileSize, parallel, nCores, - outputPath, outputTag, tries, verbose) + result <- calc_fs_px_internal(X, what, how, + focalRadius, filter, + controlZone, controlUnits, controlDist, + template, tileSize, + parallel, nCores, + outputPath, outputTag, + tries, verbose) invisible(result) } @@ -138,20 +164,26 @@ calculate_bigfoot.sf <- function(X, #' @name calculate_bigfoot #' @export calculate_bigfoot.sp <- function(X, - metrics='all', + what='all', how='all', focalRadius=0, - minArea=NULL, - maxArea=NULL, - controlUnits=NULL, - clip=FALSE, + controlZone=list(zoneName="zoneID", + method="centroid"), + controlUnits=list(areaUnit="m^2", + perimUnit="m", + distUnit="m"), + controlDist=list(maxSearch=100, + method="centroid", + unit=controlUnits$distUnit), + filter=list(minArea=NULL, + maxArea=NULL), template=NULL, tileSize=c(500, 500), parallel=TRUE, nCores=max(1, parallel::detectCores()-1), - outputPath=tempdir(), + outputPath=getwd(), outputTag=NULL, tries=100, - verbose=FALSE){ + verbose=TRUE){ # convert to sf X <- sf::st_as_sf(X) @@ -161,10 +193,13 @@ calculate_bigfoot.sp <- function(X, stop() } - result <- calc_fs_px_internal(X, metrics, focalRadius, - minArea, maxArea, controlUnits, clip, - template, tileSize, parallel, nCores, - outputPath, outputTag, tries, verbose) + result <- calc_fs_px_internal(X, what, how, + focalRadius, filter, + controlZone, controlUnits, controlDist, + template, tileSize, + parallel, nCores, + outputPath, outputTag, + tries, verbose) invisible(result) } @@ -173,47 +208,48 @@ calculate_bigfoot.sp <- function(X, #' @name calculate_bigfoot #' @export calculate_bigfoot.character <- function(X, - metrics='all', + what='all', how='all', focalRadius=0, - minArea=NULL, - maxArea=NULL, - controlUnits=NULL, - clip=FALSE, + controlZone=list(zoneName="zoneID", + method="centroid"), + controlUnits=list(areaUnit="m^2", + perimUnit="m", + distUnit="m"), + controlDist=list(maxSearch=100, + method="centroid", + unit=controlUnits$distUnit), + filter=list(minArea=NULL, + maxArea=NULL), template=NULL, tileSize=c(500, 500), parallel=TRUE, nCores=max(1, parallel::detectCores()-1), - outputPath=tempdir(), + outputPath=getwd(), outputTag=NULL, tries=100, - verbose=FALSE){ + verbose=TRUE){ - result <- calc_fs_px_internal(X, metrics, focalRadius, - minArea, maxArea, controlUnits, clip, - template, tileSize, parallel, nCores, - outputPath, outputTag, tries, verbose) + result <- calc_fs_px_internal(X, what, how, + focalRadius, filter, + controlZone, controlUnits, controlDist, + template, tileSize, + parallel, nCores, + outputPath, outputTag, + tries, verbose) invisible(result) } # internal function for managing processes -calc_fs_px_internal <- function(X, - metrics, - focalRadius, - minArea, - maxArea, - controlUnits, - clip, - template, - tileSize, - parallel, - nCores, - outputPath, - outputTag, - tries, - verbose){ - +calc_fs_px_internal <- function(X, what, how, + focalRadius, filter, + controlZone, controlUnits, controlDist, + template, tileSize, + parallel, nCores, + outputPath, outputTag, + tries, verbose){ + if(is.null(template)){ stop("Template raster or grid required.") } else if(is.character(template)){ @@ -227,16 +263,35 @@ calc_fs_px_internal <- function(X, } else{ outputTag <- "" } - + # get full list of metrics - metrics <- get_fs_metrics(short_names=metrics, group=metrics) + if(is.null(how)) stop("Please provide a summary function name.") + if(verbose){ cat("Selecting metrics \n") } + if("all" %in% what){ + argsDF <- list_fs(what="all", how=how) + } else if("nodist" %in% what){ + argsDF <- list_fs(what="nodist", how=how) + } else{ + argsX <- crossargs(what, how) + argsDF <- do.call(rbind, argsX) + argsDF <- unique(argsDF) + } + + # set defaults for controls + if(verbose){ cat("Setting control values. \n") } + # defaults for zonal indexing + providedZone <- controlZone + controlZone <- list(zoneName="zoneID", method="centroid") + controlZone <- controlZone[order(names(controlZone))] + # update with provide values + if(!is.null(providedZone)){ + providedZone <- providedZone[order(names(providedZone))] + controlZone[names(controlZone) %in% names(providedZone)] <- providedZone + } # get full set of units - sort alphabetically to make matches providedUnits <- controlUnits - - controlUnits <- list(areaUnit=get_fs_units("fs_area_mean"), - perimUnit=get_fs_units("fs_perim_mean"), - distUnit=get_fs_units("fs_nndist_mean")) + controlUnits <- list(areaUnit="m^2", perimUnit="m", distUnit="m") controlUnits <- controlUnits[order(names(controlUnits))] # update with provide values if(!is.null(providedUnits)){ @@ -244,33 +299,49 @@ calc_fs_px_internal <- function(X, controlUnits[names(controlUnits) %in% names(providedUnits)] <- providedUnits } + # defaults for distance measures + providedDist <- controlUnits + controlDist=list(maxSearch=100, + method="centroid", + unit=controlUnits$distUnit) + controlDist <- controlDist[order(names(controlDist))] + # update with provide values + if(!is.null(providedDist)){ + providedDist <- providedDist[order(names(providedDist))] + controlDist[names(controlDist) %in% names(providedDist)] <- providedDist + } + + if(verbose){ cat("Creating template output grids \n") } # create empty output grids to match template - outTemplate <- stars::st_as_stars(matrix(NA, + outTemplate <- stars::st_as_stars(matrix(NA_real_, nrow=nrow(template), ncol=ncol(template)), dimensions=stars::st_dimensions(template)) - allOutPath <- vector("character", length=length(metrics)) - for(i in seq_along(metrics)){ - m <- metrics[[i]] - mTag <- sub("fs_", "", m, fixed=T) # drop function label + allOutPath <- vector("character", length=length(nrow(argsDF))) + for(i in 1:nrow(argsDF)){ + params <- unlist(argsDF[i, "cols"]) + calc_func <- unlist(argsDF[i, "funs"]) + mTag <- paste(paste(params, collapse="_"), calc_func, sep="_") if(focalRadius > 0){ mTag <- paste(mTag, focalRadius, sep="_") } outName <- file.path(outputPath, paste0(outputTag, mTag, ".tif")) - stars::write_stars(outTemplate, - outName) # default is float32 + tmp <- stars::write_stars(outTemplate, + outName) # default is float32 allOutPath[[i]] <- outName } + rm(tmp, outTemplate) # print(allOutPath) # tiles for processing + if(verbose){ cat("Creating list of processing tiles \n") } tiles <- gridTiles(template, px=tileSize) if(focalRadius > 0){ - # convert focal radius pixels for extraction (approximation) + # convert focal radius to pixels for extraction (approximation) # assuming pixels are equal dimensions x,y if(sf::st_is_longlat(template)){ # approximate meters pxM <- 111111*abs(stars::st_dimensions(template)[[1]]$delta) @@ -286,22 +357,20 @@ calc_fs_px_internal <- function(X, # processing loop if(parallel){ # create cluster - if(verbose){ cat("Setting up cluster\n")} + if(verbose){ cat("Setting up cluster...\n")} if(.Platform$OS.type == "unix"){ cl <- parallel::makeCluster(spec=nCores, type="FORK") } else{ cl <- parallel::makeCluster(spec=nCores, type="PSOCK") parallel::clusterExport(cl, varlist=c("X", - # "tiles", - # "tilesBuff", "template", - "metrics", + "what","how", + "controlZone", "controlUnits", + "controlDist", "focalRadius", - "minArea", - "maxArea", - "clip", + "filter", "allOutPath", "tries"), envir=environment()) @@ -315,18 +384,21 @@ calc_fs_px_internal <- function(X, foreach::foreach(job=iterators::iter(tiles, by="row"), jobBuff=iterators::iter(tilesBuff, by="row"), - .export="process_tile" + .inorder=FALSE + # .verbose=TRUE + # .export="process_tile" ) %dopar% { + on.exit({ rm(list=ls()); gc() }) mgTile <- stars::st_as_stars(template[,job$xl:job$xu, job$yl:job$yu]) mgBuffTile <- stars::st_as_stars(template[,jobBuff$xl:jobBuff$xu, jobBuff$yl:jobBuff$yu]) process_tile(mgTile, mgBuffTile, - X, metrics, - focalRadius, minArea, maxArea, - controlUnits, - clip, + X, what, how, + focalRadius, + controlZone, controlUnits, constrolDist, allOutPath, tries, + filter, verbose=FALSE) NULL @@ -346,13 +418,12 @@ calc_fs_px_internal <- function(X, jobBuff$yl:jobBuff$yu]) process_tile(mgTile, mgBuffTile, - X, metrics, + X, what, how, focalRadius, - minArea, maxArea, - controlUnits, - clip, - allOutPath, + controlZone, controlUnits, constrolDist, + allOutPath, tries, + filter, verbose) } # end for loop on tiles } @@ -365,15 +436,18 @@ calc_fs_px_internal <- function(X, # helper function for processing tiles process_tile <- function(mgTile, mgBuffTile, - X, metrics, + X, what, how, focalRadius, - minArea, maxArea, controlUnits, clip, + controlZone, controlUnits, constrolDist, allOutPath, tries, + filter, verbose=FALSE){ + # clean-up + on.exit({ rm(list=ls()); gc() }) # blank tile for the results - naTile <- stars::st_as_stars(matrix(NA, + naTile <- stars::st_as_stars(matrix(NA_real_, nrow=nrow(mgTile), ncol=ncol(mgTile)), dimensions=stars::st_dimensions(mgTile)) @@ -398,163 +472,104 @@ process_tile <- function(mgTile, mgBuffTile, Xsub <- Xsub[!sf::st_is_empty(Xsub), , drop=F] # simplify if(any(sf::st_geometry_type(Xsub) %in% c("MULTIPOLYGON"))){ - Xsub <- sf::st_cast(Xsub, "POLYGON") + suppressWarnings(Xsub <- sf::st_cast(Xsub, "POLYGON")) } - # check for records + # processing if(nrow(Xsub) > 0){ - # if no clipping, speed up processing to avoid duplicated calculations after - # zone index which can duplicate features. - if(clip==FALSE){ - # pre-calculate unit geometry measures - if(any(grepl("area", metrics, fixed=T)) | - any(grepl("compact", metrics, fixed=T)) | - !is.null(minArea) | !is.null(maxArea)){ - if(!"fs_area" %in% names(Xsub)){ - if(verbose){ cat("Pre-calculating footprint areas \n") } - Xsub[["fs_area"]] <- fs_area(Xsub, unit=controlUnits$areaUnit) - } - } - - if(any(grepl("perim", metrics, fixed=T)) | - any(grepl("compact", metrics, fixed=T))){ - if(!"fs_perim" %in% names(Xsub)){ - if(verbose){ cat("Pre-calculating footprint perimeters \n")} - Xsub[["fs_perim"]] <- fs_perimeter(Xsub, unit=controlUnits$perimUnit) - } + # read proxy to grid and convert to polygon object + if(verbose){ cat("Reading template grid \n") } + mgPoly <- sf::st_as_sf(stars::st_as_stars(mgTile)) + # check for valid processing locations + if(nrow(mgPoly) > 0){ # NA pixels not converted + if(!controlZone$zoneName %in% colnames(mgPoly)){ + mgPoly[[controlZone$zoneName]] <- 1:nrow(mgPoly) } - - if(any(grepl("nndist", metrics, fixed=T))){ - if(!"fs_nndist" %in% names(Xsub)){ - if(verbose){ cat("Pre-calculating nearest neighbour distances \n") } - Xsub[["fs_nndist"]] <- fs_nndist(Xsub, unit=controlUnits$distUnit) + # buffer for focal statistics + if(focalRadius > 0){ + if(verbose){ cat("Buffering processing sites \n") } + if(sf::st_is_longlat(mgPoly)){ + # find UTM zone of the tile's centroid + aoi <- sf::st_as_sfc(sf::st_bbox(mgPoly)) + suppressWarnings(zn <- suggestUTMzone(sf::st_coordinates(sf::st_centroid(aoi)))) + mgPolyArea <- sf::st_transform(mgPoly, crs=zn) + # circular buffer around centre point + suppressWarnings(mgPolyArea <- sf::st_buffer(sf::st_centroid(mgPolyArea), + dist=focalRadius)) + mgPolyArea <- sf::st_transform(mgPolyArea, crs=sf::st_crs(mgPoly)) + } else{ + mgPolyArea <- sf::st_buffer(mgPoly, dist=focalRadius) } + } else{ # pixel resolution + mgPolyArea <- mgPoly } - - if(any(grepl("angle", metrics, fixed=T))){ - if(!"fs_angle" %in% names(Xsub)){ - if(verbose){ cat("Pre-calculating angles \n") } - Xsub[["fs_angle"]] <- fs_mbr(Xsub) - } - } - - # filter records - if(!is.null(minArea)){ - if(verbose) { cat(paste0("Filtering features larger than ", minArea," \n")) } - Xsub <- subset(Xsub, fs_area > units::as_units(minArea, - controlUnits$areaUnit)) - } - if(!is.null(maxArea)){ - if(verbose) { cat(paste0("Filtering features smaller than ", maxArea," \n")) } - Xsub <- subset(Xsub, fs_area < units::as_units(maxArea, - controlUnits$areaUnit)) + + # footprint statistics within the tile + tileResults <- calculate_footstats(Xsub, + zone=mgPolyArea, + what=what, + how=how, + controlZone=controlZone, + controlUnits=controlUnits, + controlDist=controlDist, + filter=filter, + verbose=verbose) + # clean-up + rm(Xsub) + + # check for errors in the return + if(is.null(tileResults)){ + return(NULL) } - # no need to re-filter if not clipping - minArea <- maxArea <- NULL - } - - if(nrow(Xsub) > 0){ - # read proxy to grid and convert to polygon object - if(verbose){ cat("Reading template grid \n") } - mgPoly <- sf::st_as_sf(stars::st_as_stars(mgTile)) - # check for valid processing locations - if(nrow(mgPoly) > 0){ # NA pixels not converted - mgPoly$id <- 1:nrow(mgPoly) - # buffer for focal statistics + # store results tile calculations + mgPoly <- merge(mgPoly, + tileResults, + by=controlZone$zoneName) + # output loop + if(verbose){ cat("Writing output tiles \n") } + for(n in names(tileResults)[!names(tileResults) %in% "index"]){ + units(mgPoly[[n]]) <- NULL + + tmpName <- paste0("tempRas_", Sys.getpid(),".tif") + resArea <- stars::st_rasterize(mgPoly[n], + template=naTile, + file=file.path(tempdir(), + tmpName)) + # update tile offset to nest in template + d <- stars::st_dimensions(resArea) + tD <- stars::st_dimensions(mgTile) + d[["x"]]$from <- tD[["x"]]$from # job$xl + d[["x"]]$to <- tD[["x"]]$to # job$xu + d[["y"]]$from <- tD[["y"]]$from # job$yl + d[["y"]]$to <- tD[["y"]]$to # job$yu + + resArea <- structure(resArea, dimensions=d) + + # get file name + n <- sub("fs_", "", n, fixed=T) + nsplit <- strsplit(n, "_", fixed=T)[[1]] + n <- ifelse(length(nsplit)==3, + paste(nsplit[1], nsplit[3], sep="_"), + paste(nsplit, collapse="_")) if(focalRadius > 0){ - if(verbose){ cat("Buffering processing sites \n") } - if(sf::st_is_longlat(mgPoly)){ - # find UTM zone of the tile's centroid - aoi <- sf::st_as_sfc(sf::st_bbox(mgPoly)) - zn <- suggestUTMzone(sf::st_coordinates(sf::st_centroid(aoi))) - mgPolyArea <- sf::st_transform(mgPoly, crs=zn) - # circular buffer around centre point - mgPolyArea <- sf::st_buffer(sf::st_centroid(mgPolyArea), - dist=focalRadius) - mgPolyArea <- sf::st_transform(mgPolyArea, crs=sf::st_crs(mgPoly)) - } else{ - mgPolyArea <- sf::st_buffer(mgPoly, dist=focalRadius) - } - } else{ # pixel resolution - mgPolyArea <- mgPoly + n <- paste(n, focalRadius, sep="_") } - # # get index to pixels - # if(verbose){ cat("Generating zonal index \n") } - # Xsub <- zonalIndex(Xsub, zone=mgPolyArea, clip=clip, returnObject=TRUE) - # # drop non-intersecting buildings - # Xsub <- subset(Xsub, !is.na(zoneID)) - # if(is.null(Xsub) | nrow(Xsub) == 0){ - # return(NULL) - # } - # footprint statistics within the tile - tileResults <- calculate_footstats(Xsub, - # index="zoneID", - index=mgPolyArea, - metrics=metrics, - minArea=minArea, - maxArea=maxArea, - controlUnits=controlUnits, - clip=clip, - gridded=FALSE, - verbose=verbose) - # clean-up - rm(Xsub) - - # check for errors in the return - if(is.null(tileResults)){ - return(NULL) + path <- which(grepl(n, allOutPath, fixed=T)) + # print(allOutPath[[path]]) + if(length(path)==1){ + lck <- filelock::lock(file.path(tempdir(), paste0(path, ".lock"))) + write_tile(outGrid=resArea, + outName=allOutPath[[path]], + tries=tries, + update=TRUE) + filelock::unlock(lck) } - # store results tile calculations - mgPoly <- merge(mgPoly, - tileResults, - by.x="id", by.y="index") - # output loop - if(verbose){ cat("Writing output tiles \n") } - for(n in names(tileResults)[!names(tileResults) %in% "index"]){ - units(mgPoly[[n]]) <- NULL - - tmpName <- paste0("tempRas_", Sys.getpid(),".tif") - resArea <- stars::st_rasterize(mgPoly[n], - template=naTile, - file=file.path(tempdir(), - tmpName)) - # update tile offset to nest in template - d <- stars::st_dimensions(resArea) - tD <- stars::st_dimensions(mgTile) - d[["x"]]$from <- tD[["x"]]$from # job$xl - d[["x"]]$to <- tD[["x"]]$to # job$xu - d[["y"]]$from <- tD[["y"]]$from # job$yl - d[["y"]]$to <- tD[["y"]]$to # job$yu - - resArea <- structure(resArea, dimensions=d) - - # get file name - n <- sub("fs_", "", n, fixed=T) - nsplit <- strsplit(n, "_", fixed=T)[[1]] - n <- ifelse(length(nsplit)==3, - paste(nsplit[1], nsplit[3], sep="_"), - paste(nsplit, collapse="_")) - if(focalRadius > 0){ - n <- paste(n, focalRadius, sep="_") - } - - path <- which(grepl(n, allOutPath, fixed=T)) - # print(allOutPath[[path]]) - if(length(path)==1){ - lck <- filelock::lock(file.path(tempdir(), paste0(path, ".lock"))) - write_tile(outGrid=resArea, - outName=allOutPath[[path]], - tries=tries, - update=TRUE) - filelock::unlock(lck) - } - } # end output loop - if(verbose){ cat("Finished writing grids\n") } - } # end found mastergrid tiles - } # end if buildings found after filter - } # end if buildings found in tile -} + } # end output loop + if(verbose){ cat("Finished writing grids\n") } + } # end found mastergrid tiles + } # end if buildings found after filter +} # end if buildings found in tile # helper function for writing tiles @@ -573,5 +588,6 @@ write_tile <- function(outGrid, outName, tries=100, update=FALSE){ tryCount <- tryCount + 1 Sys.sleep(1) } - if(tryCount > tryThreshold) stop("Writing failed after 100 tries, exiting.") + if(tryCount > tryThreshold) stop(paste0("Writing failed after ", + tries, " tries, exiting.")) } diff --git a/R/filter.R b/R/filter.R deleted file mode 100644 index 64e31a7..0000000 --- a/R/filter.R +++ /dev/null @@ -1,31 +0,0 @@ -#' Circular Filter -#' -#' Filters for gridded data -#' -#' @description Make an approximately circular filter inside of an 'n x n' -#' matrix. -#' @param size The length of the side of a square matrix. The circular window -#' has a radius of size/2. -#' @return Matrix with values inside the circle set to 1, the center set to 0, -#' and corners as NA. make weights window -#' -#' @examples -#' f <- make_circular_filter(11) -#' print(f) -#' class(f) -#' -#' based on: https://stackoverflow.com/questions/54742340/r-extract-a-circle-from-a-matrix -#' @name circularFilter -#' @export -make_circular_filter <- function(size=5){ - g <- expand.grid(1:size, 1:size) - ctr <- c(size/2, size/2) + .5 - - g$d2 <- sqrt((g$Var1-ctr[2])^2 + (g$Var2-ctr[1])^2) - g$inside <- g$d2 <= size/2 - - w <- matrix(NA, size, size) - w[as.matrix(g[g$inside, c("Var1","Var2")])] <- 1 - w[ctr[1], ctr[2]] <- 0 - return(w) -} \ No newline at end of file diff --git a/R/foot.R b/R/foot.R index a1b68c3..9e70161 100644 --- a/R/foot.R +++ b/R/foot.R @@ -1,27 +1,33 @@ -#' foot: A package for calculating building footprint shape metrics -#' -#' The foot package provides functions to calculate summary statistics -#' of geometric measurements of building footprint polygons. Footprints -#' shapes representing buildings are becoming more widely available by -#' being detected and extracted from very high resolution satellite. Such -#' datasets are spatially detailed but often are unlabelled. However, -#' the size, shape, and distribution of buildings can suggest possible -#' land uses or differences in structure use, socieconomic status, etc. -#' -#' The measurements in \code{foot} include: area, perimeter, nearest-neighbour -#' distance, angle of rotation for a bounding rectange, as well as a binary +#' foot: An R package for calculating building footprint shape metrics +#' +#' The \code{foot} package provides functions to calculate summary statistics of +#' geometric measurements of building footprint polygons. Footprint shapes +#' representing buildings are becoming more widely available by being detected +#' and extracted from very high resolution satellite imagery. Such datasets are +#' spatially detailed but often are unlabelled. However, the size, shape, and +#' distribution of buildings can suggest possible land uses or differences in +#' structure use, socio-economic status, etc. +#' +#' The \code{foot} package is designed to provide a set of consistent and +#' flexible tools for processing 2D vector representations of buildings. The +#' functionality includes basic geometry and morphology measures, distance and +#' clustering metrics. These calculations are supported with helper functions +#' for spatial intersections and tiled reading/writing of data. +#' +#' The measurements in \code{foot} include: area, perimeter, nearest-neighbour +#' distance, angle of rotation for a bounding rectangle, as well as a binary #' indicator of structure presence and a count of structures. #' -#' These measures can be summarised as a mean, standard deivation, coefficient -#' of variation, the nearest neighbour index of clustering, or a (normalised) -#' entropy measure for the angles, and can be output as a table or as a -#' gridded dataset. +#' These measures can be summarised as a mean, standard deviation, coefficient +#' of variation, the nearest neighbour index of clustering, or a (normalised) +#' entropy measure for the angles. The output can be formatted as a data table +#' or as a gridded dataset. #' -#' @section Helper functions: -#' While each measurement function can be accessed as a standalone function, -#' the \code{foot} package provides convenience functions to wrap common -#' analysis steps together in a helper function, taking a list of measures -#' and parameters and returning a collected output. +#' @section Helper functions: The \code{foot} package provides convenience +#' functions (\code{\link[foot]{calculate_footstats}} and +#' \code{\link[foot]{calculate_bigfoot}}) to wrap common analysis steps +#' together, taking a list of measurements and parameters and returning a +#' collected output. #' #' In addition to bulk processing helper functions, there are additional #' utility functions supplied with the package to support identifying @@ -29,7 +35,8 @@ #' spatial data and providing efficient I/O and parallel processing. #' #' @section Credits: -#' This work was undertaken by members of WorldPop at the University of Southampton. +#' This work was undertaken by members of the WorldPop Research Group at the +#' University of Southampton(\url{https://www.worldpop.org/}). #' #' @docType package #' @name foot diff --git a/R/fs_NNdist_mean.R b/R/fs_NNdist_mean.R deleted file mode 100644 index bc69e7f..0000000 --- a/R/fs_NNdist_mean.R +++ /dev/null @@ -1,83 +0,0 @@ -#' Nearest neighbour mean distance -#' -#' @description Calculate and summarise selected metrics of building -#' footprint polygons within zones. -#' @inheritParams fs_area_mean -#' @return \code{data.table} of zonal indices and values. -#' -#' @import data.table -#' -#' @aliases fs_nndist_mean -#' @rdname fs_nndist_mean -#' -#' @export -fs_nndist_mean <- function(X, index=NULL, unit=NULL, col=NULL) UseMethod("fs_nndist_mean") - - -#' @name fs_nndist_mean -#' @export -fs_nndist_mean.sp <- function(X, index=NULL, unit=NULL, col=NULL){ - X <- sf::st_as_sf(X) - - result <- fs_nndist_mean(X, index, unit, col) - return(result) -} - - -#' @name fs_nndist_mean -#' @export -fs_nndist_mean.sf <- function(X, index=NULL, unit=NULL, col=NULL){ - if(!is.null(col)){ - if(!col %in% names(X)){ - message("Error: column name not found.") - stop() - } else{ - names(X)[which(names(X)==col)] <- "fs_nndist" - result <- fs_nndist_mean_calc(X, index, unit) - } - } else{ - if(is.na(sf::st_crs(X))){ - warning("Objects have no spatial projection. Units ignored.") - unit <- NULL - - } else{ - if(is.null(unit)){ - unit <- "m" - } - } - - X[["fs_nndist"]] <- fs_nndist(X, unit=unit) # using default search radius - result <- fs_nndist_mean_calc(X, index, unit) - } - return(result) -} - - -fs_nndist_mean_calc <- function(X, index=NULL, unit=NULL){ - if(!"fs_nndist" %in% names(X)){ - X[["fs_nndist"]] <- fs_nndist(X, unit=unit) # default search radius - } - - if(is.null(index)){ - warning("No index found, treating as one group.") - index <- rep(1, nrow(X)) - } else{ - if(length(index)==1){ - if((is.numeric(index) & index <= ncol(X)) | - (is.character(index) & index %in% names(X))){ - index <- X[[index]] - } - } else if(length(index) != nrow(X)){ - message("Invalid index") - stop() - } - } - - colNam <- paste0("fs_nndist_", unit, "_mean") - DT <- data.table::data.table(index=index, - area_calc=X[["fs_nndist"]]) - data.table::setkey(DT, index) - result <- DT[, setNames(.(mean(area_calc)), colNam), by=index] - - return(result) -} diff --git a/R/fs_NNdist_median.R b/R/fs_NNdist_median.R deleted file mode 100644 index fda94cf..0000000 --- a/R/fs_NNdist_median.R +++ /dev/null @@ -1,86 +0,0 @@ -#' Nearest neighbour median distance -#' -#' @description Calculate and summarise selected metrics of building -#' footprint polygons within zones. -#' @inheritParams fs_area_mean -#' @return \code{data.table} of zonal indices and values. -#' -#' @import data.table -#' -#' @aliases fs_nndist_median -#' @rdname fs_nndist_median -#' -#' @export -fs_nndist_median <- function(X, - index=NULL, - unit=NULL, - col=NULL) UseMethod("fs_nndist_median") - - -#' @name fs_nndist_median -#' @export -fs_nndist_median.sp <- function(X, index=NULL, unit=NULL, col=NULL){ - X <- sf::st_as_sf(X) - - result <- fs_nndist_median(X, index, unit, col) - return(result) -} - - -#' @name fs_nndist_median -#' @export -fs_nndist_median.sf <- function(X, index=NULL, unit=NULL, col=NULL){ - if(!is.null(col)){ - if(!col %in% names(X)){ - message("Error: column name not found.") - stop() - } else{ - names(X)[which(names(X)==col)] <- "fs_nndist" - result <- fs_nndist_median_calc(X, index, unit) - } - } else{ - if(is.na(sf::st_crs(X))){ - warning("Objects have no spatial projection. Units ignored.") - unit <- NULL - - } else{ - if(is.null(unit)){ - unit <- "m" - } - } - - X[["fs_nndist"]] <- fs_nndist(X, unit=unit) # using default search radius - result <- fs_nndist_median_calc(X, index, unit) - } - return(result) -} - - -fs_nndist_median_calc <- function(X, index=NULL, unit=NULL){ - if(!"fs_nndist" %in% names(X)){ - X[["fs_nndist"]] <- fs_nndist(X, unit=unit) # default search radius - } - - if(is.null(index)){ - warning("No index found, treating as one group.") - index <- rep(1, nrow(X)) - } else{ - if(length(index)==1){ - if((is.numeric(index) & index <= ncol(X)) | - (is.character(index) & index %in% names(X))){ - index <- X[[index]] - } - } else if(length(index) != nrow(X)){ - message("Invalid index") - stop() - } - } - - colNam <- paste0("fs_nndist_", unit, "_median") - DT <- data.table::data.table(index=index, - area_calc=X[["fs_nndist"]]) - data.table::setkey(DT, index) - result <- DT[, setNames(.(median(area_calc)), colNam), by=index] - - return(result) -} diff --git a/R/fs_NNdist_sd.R b/R/fs_NNdist_sd.R deleted file mode 100644 index 1127ff1..0000000 --- a/R/fs_NNdist_sd.R +++ /dev/null @@ -1,83 +0,0 @@ -#' Nearest neighbour mean distance -#' -#' @description Calculate and summarise selected metrics of building -#' footprint polygons within zones. -#' @inheritParams fs_area_mean -#' @return \code{data.table} of zonal indices and values. -#' -#' @import data.table -#' -#' @aliases fs_nndist_sd -#' @rdname fs_nndist_sd -#' -#' @export -fs_nndist_sd <- function(X, index=NULL, unit=NULL, col=NULL) UseMethod("fs_nndist_sd") - - -#' @name fs_nndist_sd -#' @export -fs_nndist_sd.sp <- function(X, index=NULL, unit=NULL, col=NULL){ - X <- sf::st_as_sf(X) - - result <- fs_nndist_sd(X, index, unit, col) - return(result) -} - - -#' @name fs_nndist_sd -#' @export -fs_nndist_sd.sf <- function(X, index=NULL, unit=NULL, col=NULL){ - if(!is.null(col)){ - if(!col %in% names(X)){ - message("Error: column name not found.") - stop() - } else{ - names(X)[which(names(X)==col)] <- "fs_nndist" - result <- fs_nndist_sd_calc(X, index, unit) - } - } else{ - if(is.na(sf::st_crs(X))){ - warning("Objects have no spatial projection. Units ignored.") - unit <- NULL - - } else{ - if(is.null(unit)){ - unit <- "m" - } - } - - X[["fs_nndist"]] <- fs_nndist(X, unit=unit) - result <- fs_nndist_sd_calc(X, index, unit) - } - return(result) -} - - -fs_nndist_sd_calc <- function(X, index, unit=NULL){ - if(!"fs_nndist" %in% names(X)){ - X[["fs_nndist"]] <- fs_nndist(X, unit=unit) - } - - if(is.null(index)){ - warning("No index found, treating as one group.") - index <- rep(1, nrow(X)) - } else{ - if(length(index)==1){ - if((is.numeric(index) & index <= ncol(X)) | - (is.character(index) & index %in% names(X))){ - index <- X[[index]] - } - } else if(length(index) != nrow(X)){ - message("Invalid index") - stop() - } - } - - colNam <- paste0("fs_nndist_", unit, "_sd") - DT <- data.table::data.table(index=index, - area_calc=X[["fs_nndist"]]) - data.table::setkey(DT, index) - result <- DT[, setNames(.(sd(area_calc)), colNam), by=index] - - return(result) -} diff --git a/R/fs_NNindex.R b/R/fs_NNindex.R index 512edbc..2638542 100644 --- a/R/fs_NNindex.R +++ b/R/fs_NNindex.R @@ -3,24 +3,33 @@ #' @description Calculate and summarise selected metrics of building footprint #' polygons within zones. #' @param X Spatial object with building footprints or their centroid locations. -#' @param index A spatial polygon object of \code{sf} or \code{sp} type. If +#' @param zone A spatial polygon object of \code{sf} or \code{sp} type. If #' omitted all observations in \code{X} are assumed to be within one zone and #' the area of the minimum bounding circle is used for the nearest neighbour #' index. -#' @param unit character or \code{units} object to define distance. Default is -#' NULL -#' @param col column name or index within \code{X} with pre-calculated distance -#' measures. +#' @param zoneField (Optional) Column name of unique identifiers in \code{zone} +#' to use. If omitted, the 'zoneID' will be numbered \code{1:nrow(zone)}. +#' @param unit character or \code{units} object to define distance. Default +#' \code{NULL} will attempt to coerce units to meters. #' @return \code{data.table} of zonal indices and values #' -#' @details The nearest neighbour index (NNI) is a measure of clustering. It -#' compares the observed mean neighbour distances with a hypothetical maximum -#' of dispersed observations given the area of the zone. Note that NNI is -#' sensitive to changes in the zone area. +#' @details The nearest neighbour index (NNI) is a measure of spatial +#' clustering. It compares the observed mean neighbour distances with a +#' hypothetical maximum of dispersed observations given the area of the zone. +#' Note that NNI is sensitive to changes in the zone area. #' -#' \deqn{ NNI_z = \frac{\bar{NND_z}}{(0.5 * \sqrt{\frac{A_z}{n_z}}})}, -#' where z is the zone, A is the area, NND is the mean nearest neighbour -#' distance, and n is the count. +#' \deqn{ NNI_z = \frac{\bar{NND_z}}{(0.5 * \sqrt{\frac{A_z}{n_z}}})}, where z +#' is the zone, A is the area, NND is the mean nearest neighbour distance, and +#' n is the count. The value of NNI can range from 0 (fully disperse) to 2.15 +#' (clustered), with values of 1 indicating spatial randomness. +#' +#' The function uses \code{fs_nndist} to calculate the distance between +#' centroids of the building footprints within the same spatial zone indicated +#' by \code{zone}. +#' +#' Note that this function is provided as a standalone calculation. The +#' summary measure can be executed from within \code{calculate_footstats} by +#' specifying \code{what='nndist'} and \code{how='nnindex'}. #' #' @import data.table #' @@ -28,107 +37,128 @@ #' @rdname fs_nnindex #' #' @export -fs_nnindex <- function(X, index=NULL, unit=NULL, col=NULL) UseMethod("fs_nnindex") +fs_nnindex <- function(X, zone=NULL, + zoneField=NULL, unit=NULL) UseMethod("fs_nnindex") #' @name fs_nnindex #' @export -fs_nnindex.sp <- function(X, index=NULL, unit=NULL, col=NULL){ +fs_nnindex.sp <- function(X, zone=NULL, + zoneField=NULL, unit=NULL){ X <- sf::st_as_sf(X) - result <- fs_nnindex(X, index, unit, col) + result <- fs_nnindex(X, zone, zoneField, unit) return(result) } #' @name fs_nnindex #' @export -fs_nnindex.sf <- function(X, index=NULL, unit=NULL, col=NULL){ - if(!is.null(col)){ - if(!col %in% names(X)){ - message("Error: column name not found.") - stop() - } else{ - names(X)[which(names(X)==col)] <- "fs_nndist" - result <- fs_nnindex_calc(X, index, unit) +fs_nnindex.sf <- function(X, zone=NULL, + zoneField=NULL, unit=NULL){ + + if(is.na(sf::st_crs(X))){ + warning("Footprints have no spatial projection. Units ignored.") + unit <- NULL + } else{ + if(is.null(unit)){ + unit <- "m" } - } else{ # distance column not supplied - # if(any(!sf::st_geometry_type(X) %in% c("POLYGON", "MULTIPOLYGON") )){ - # message("Area requires polygon shapes.") - # stop() - # } - if(is.na(sf::st_crs(X))){ - warning("Footprints have no spatial projection. Units ignored.") - unit <- NULL - } else{ - if(is.null(unit)){ - unit <- "m" - } + } + + result <- fs_nnindex_calc(X, zone, zoneField, unit) + return(result) +} + + +#' @name fs_nnindex +#' @export +fs_nnindex.sfc <- function(X, zone=NULL, + zoneField=NULL, unit=NULL){ + + # conversion + X <- sf::st_as_sf(X) + + if(is.na(sf::st_crs(X))){ + warning("Footprints have no spatial projection. Units ignored.") + unit <- NULL + } else{ + if(is.null(unit)){ + unit <- "m" } - - # X[["fs_nndist"]] <- fs_nndist(X, unit=unit) - result <- fs_nnindex_calc(X, index, unit) } + + result <- fs_nnindex(X, zone, zoneField, unit) return(result) } -fs_nnindex_calc <- function(X, index, unit=NULL){ - # if(!"fs_nndist" %in% names(X)){ - # X[["fs_nndist"]] <- fs_nndist(X, unit) - # } - # +fs_nnindex_calc <- function(X, zone, zoneField=NULL, unit=NULL){ + if(is.null(zoneField)){ + zoneField <- "zoneID" + } + if(is.null(unit)){ + unit <- "m" + } # need spatial zones - if(is.null(index)){ + if(is.null(zone)){ warning("No index found, treating as one group.") - index <- rep(1, nrow(X)) + zone <- rep(1, nrow(X)) indexZones <- sf::st_sf(index=1, - #geometry=sf::st_as_sfc(sf::st_bbox(X)), - geometry=sf::st_as_sfc(lwgeom::st_minimum_bounding_circle(X)), + geometry=sf::st_geometry(lwgeom::st_minimum_bounding_circle(X)), crs=sf::st_crs(X)) - X[['index']] <- 1 - zonalArea <- data.table::data.table(index=indexZones$index, + X[[zoneField]] <- 1 + zonalArea <- data.table::data.table(idx=indexZones$index, zoneArea=fs_area(indexZones, unit=paste0(unit, "^2"))) } else{ - if(!inherits(index, "sf")){ - if(inherits(index, "Spatial")){ - index <- sf::st_as_sf(index) + if(!inherits(zone, "sf")){ + if(inherits(zone, "Spatial")){ + zone <- sf::st_as_sf(zone) } else{ - message("Invalid index. Spatial units requried.") - stop() + stop("Invalid zone. Spatial units required") } } - index$index <- 1:nrow(index) + if(!zoneField %in% colnames(zone)){ + zone[[zoneField]] <- 1:nrow(zone) + } - X <- zonalIndex(X, index, returnObject=TRUE) + X <- zonalIndex(X, zone=zone, zoneField=zoneField, + method="centroid", returnObject=TRUE) - zonalArea <- data.table::data.table(index=index$index, - zoneArea=fs_area(index, + zonalArea <- data.table::data.table(idx=zone[[zoneField]], + zoneArea=fs_area(zone, unit=paste0(unit, "^2"))) } + data.table::setnames(zonalArea, "idx", zoneField) + data.table::setkeyv(zonalArea, zoneField) # get NN distance only within each zone # use centroid points rather than polygon edge distances - X <- sf::st_set_geometry(X, sf::st_geometry(sf::st_centroid(X))) xDT <- data.table::data.table(X) meanDT <- xDT[, list(dist=mean(fs_nndist(sf::st_as_sf(.SD), - maxSearch=NULL, + maxSearch=NULL, + method='centroid', unit=unit)) ), - by="zoneID"] - + by=zoneField] + mCol <- paste0("fs_nndist_", unit, "_mean") - data.table::setnames(meanDT, c("index", mCol)) - # meanDT <- fs_nndist_mean(X, index=X$zoneID, unit=unit, col="fs_nndist") + data.table::setnames(meanDT, "dist", mCol) - countDT <- fs_count(X, index=X$zoneID) + data.table::setkeyv(meanDT, zoneField) + + countDT <- fs_count(X, index=zoneField) + data.table::setkeyv(countDT, zoneField) cCol <- "fs_count" - DT <- merge(meanDT, countDT, by="index") - DT <- merge(DT, zonalArea, by="index") + DT <- meanDT[countDT] + n <- setdiff(names(zonalArea), key(zonalArea)) + DT[zonalArea, (n) := mget(paste0("i.", n))] - DT[, fs_nnindex := get(mCol) / (0.5 * sqrt(zoneArea / get(cCol))), by=index] + DT[, fs_nnindex := get(mCol) / (0.5 * sqrt(zoneArea / get(cCol))), by=zoneField] units(DT$fs_nnindex) <- NULL - return(DT[, list(index, fs_nnindex)]) + keep <- c(zoneField, "fs_nnindex") + return(DT[, ..keep]) + # return(DT[, list(index, fs_nnindex)]) } diff --git a/R/fs_angle_entropy.R b/R/fs_angle_entropy.R index 8df2f87..e520b1c 100644 --- a/R/fs_angle_entropy.R +++ b/R/fs_angle_entropy.R @@ -3,27 +3,45 @@ #' @description Calculate the entropy of rotation angles for building footprint #' polygons within zones. #' -#' @inheritParams fs_area_mean +#' @param X Spatial object with building footprint polygons +#' @param index A string identifying a column within \code{X} which provides a +#' zonal index for summarising values. Alternatively a vector of indices can +#' be provided. If omitted, all observations in \code{X} are assumed to be +#' within one zone. +#' @param unit character or \code{units} object to define area. Default is +#' \code{NULL} which will use the units of the spatial reference system +#' @param col column name within \code{X} with pre-calculated area measures #' @param normalize A logical value indicating whether to normalize the entropy. #' Default is \code{TRUE}. #' #' @details This measure uses the angle of the minimum rotated rectangle -#' enclosing each footprint poygon. Entropy is an information criteria +#' enclosing each footprint polygon. Entropy is an information criteria #' measure. When summarising the angles of footprints, higher entropy values #' may suggest less formally planned or zoned areas. The entropy calculation #' uses the common Shannon's Entropy. The normalization step produces an -#' indicator for how much a zone departs from a grid. +#' indicator for how much a zone departs from a grid. This metric is based on +#' work by Boeing (2019). +#' +#' Note that this function is provided as a standalone calculation for +#' convenience. The same summary measure can be executed within +#' \code{calculate_footstats} by specifying \code{what='angle'} and +#' \code{how='entropy'}. #' #' @return \code{data.table} of zonal indices and values. #' +#' @references Boeing, Geoff (2019). "Urban spatial order: Street network +#' orientation, configuration, and entropy." Applied Network Science, 4(67), +#' \url{https://doi.org/10.1007/s41109-019-0189-1}. +#' #' @examples -#' data(kampala) +#' data("kampala", package="foot") #' b <- kampala$buildings +#' #' # assign random groups #' idx <- sample(1:10, nrow(b), replace=T) #' -#' angles <- fs_angle_entropy(b, index=idx, normalize=F) -#' angle_norm <- fs_angle_entropy(b, index=idx, normalize=T) +#' angles <- fs_angle_entropy(b, index=idx, normalize=FALSE) +#' angle_norm <- fs_angle_entropy(b, index=idx, normalize=TRUE) #' #' @import data.table #' @@ -31,7 +49,9 @@ #' @rdname fs_angle_entropy #' #' @export -fs_angle_entropy <- function(X, index=NULL, col=NULL, normalize=TRUE) UseMethod("fs_angle_entropy") +fs_angle_entropy <- function(X, index=NULL, + col=NULL, + normalize=TRUE) UseMethod("fs_angle_entropy") #' @name fs_angle_entropy @@ -68,49 +88,65 @@ fs_angle_entropy.sf <- function(X, index=NULL, col=NULL, normalize=TRUE){ } +#' @name fs_angle_entropy +#' @export +fs_angle_entropy.sfc <- function(X, index=NULL, col=NULL, normalize=TRUE){ + X <- sf::st_as_sf(X) + return(fs_angle_entropy(X, index, col, noramlize)) +} + + fs_angle_entropy_calc <- function(X, index, normalize=TRUE){ if(!"fs_angle" %in% names(X)){ X[["fs_angle"]] <- sapply(sf::st_geometry(X), fs_mbr) } + indexCol <- "zoneID" # default + if(is.null(index)){ - warning("No index found, treating as one group.") + message("No index found, treating as one group.") index <- rep(1, nrow(X)) } else{ - if(length(index)==1){ - if((is.numeric(index) & index <= ncol(X)) | - (is.character(index) & index %in% names(X))){ - index <- X[[index]] + if(is.character(index)){ + if(length(index)==1){ + if(nrow(X)>1){ # it must be a column name + if(!index %in% colnames(X)){ + stop("Index column not found in footprints.") + } else{ + indexCol <- index + index <- X[[indexCol]] + } + } # potential issue if 1 row X and 1 column name - won't affect calcs + } else if(length(index != nrow(X))){ + stop("Invalid length of zonal index.") + } + } else if(is.numeric(index)){ + if(length(index) != nrow(X)){ + stop("Invalid length of zonal index.") } - } else if(length(index) != nrow(X)){ - message("Invalid index") - stop() } } - # abins <- cut(0:360, seq(5, 355, 10), labels=F) + 1 - # abins[is.na(abins)] <- 1 - colNam <- "fs_angle_entropy" - DT <- data.table::data.table(index=c(index, index), + DT <- data.table::data.table(idxCol=c(index, index), area_calc=c(X[["fs_angle"]], (X[["fs_angle"]] + 180) %% 360) ) + data.table::setnames(DT, "idxCol", indexCol) + DT[, bin := cut(area_calc, seq(5, 355, 10), labels=F) + 1] DT[is.na(bin), bin := 1] - data.table::setkey(DT, index) + data.table::setkeyv(DT, indexCol) result <- DT[, list(entropy = -1 * sum(prop.table(table(bin)) * log(prop.table(table(bin)))) ), - by=index] + by=indexCol] if(normalize){ # based on Boeing (2019) hmax <- 3.584 hg <- 1.386 - result[, entropy := 1 - ((entropy-hg) / (hmax - hg))^2, by=index] } - data.table::setnames(result, "entropy", colNam) - return(result) + return(result[]) # [] trick needed to print on return because used ':='. known data.table bug } diff --git a/R/fs_area_cv.R b/R/fs_area_cv.R deleted file mode 100644 index 6db7fa7..0000000 --- a/R/fs_area_cv.R +++ /dev/null @@ -1,96 +0,0 @@ -#' Building area coefficient of variation (CV) -#' -#' @description Calculate and summarise selected metrics of building -#' footprint polygons within zones. -#' @inheritParams fs_area_mean -#' @return \code{data.table} of zonal indices and values -#' -#' @import data.table -#' -#' @aliases fs_area_cv -#' @rdname fs_area_cv -#' -#' @export -fs_area_cv <- function(X, index=NULL, unit=NULL, col=NULL) UseMethod("fs_area_cv") - - -#' @name fs_area_cv -#' @export -fs_area_cv.sp <- function(X, index=NULL, unit=NULL, col=NULL){ - X <- sf::st_as_sf(X) - - result <- fs_area_cv(X, index, unit, col) - return(result) -} - - -#' @name fs_area_cv -#' @export -fs_area_cv.sf <- function(X, index=NULL, unit=NULL, col=NULL){ - if(!is.null(col)){ - if(!col %in% names(X)){ - message("Error: column name not found.") - stop() - } else{ - names(X)[which(names(X)==col)] <- "fs_area" - result <- fs_area_cv_calc(X, index, unit) - } - } else{ # area column supplied - if(any(!sf::st_geometry_type(X) %in% c("POLYGON", "MULTIPOLYGON") )){ - message("Area requires polygon shapes.") - stop() - } - - if(is.na(sf::st_crs(X))){ - warning("Polygons have no spatial projection. Units ignored.") - unit <- NULL - } else{ - if(is.null(unit)){ - unit <- "ha" - } - } - - X[["fs_area"]] <- fs_area(X, unit) - result <- fs_area_cv_calc(X, index, unit) - } - return(result) -} - - -fs_area_cv_calc <- function(X, index, unit=NULL){ - if(!"fs_area" %in% names(X)){ - X[["fs_area"]] <- fs_area(X, unit) - } - - if(is.null(index)){ - warning("No index found, treating as one group.") - index <- rep(1, nrow(X)) - } - # else{ - # if(length(index)==1){ - # if((is.numeric(index) & index <= ncol(X)) | - # (is.character(index) & index %in% names(X))){ - # index <- X[[index]] - # } - # } else if(length(index) != nrow(X)){ - # message("Invalid index") - # stop() - # } - # } - - meanDT <- fs_area_mean(X, index=index, unit=unit, col="fs_area") - sdDT <- fs_area_sd(X, index=index, unit=unit, col="fs_area") - - mCol <- paste0("fs_area_", unit, "_mean") - sdCol <- paste0("fs_area_", unit, "_sd") - - DT <- merge(meanDT, sdDT, by="index") - DT[, area_calc := get(sdCol) / get(mCol), by=index] - DT[, area_calc := units::drop_units(area_calc)] - - colNam <- "fs_area_cv" - data.table::setkey(DT, index) - data.table::setnames(DT, "area_calc", colNam) - - return(DT[, list(index, fs_area_cv)]) -} diff --git a/R/fs_area_max.R b/R/fs_area_max.R deleted file mode 100644 index 2035ec7..0000000 --- a/R/fs_area_max.R +++ /dev/null @@ -1,93 +0,0 @@ -#' Building area maximum calculation -#' -#' @description Calculate and summarise selected metrics of building -#' footprint polygons within zones. -#' #' @inheritParams fs_area_mean -#' @return \code{data.table} of zonal indices and values -#' -#' @import data.table -#' -#' @aliases fs_area_max -#' @rdname fs_area_max -#' -#' @export -fs_area_max <- function(X, - index=NULL, - unit=NULL, - col=NULL) UseMethod("fs_area_max") - - -#' @name fs_area_max -#' @export -fs_area_max.sp <- function(X, index=NULL, unit=NULL, col=NULL){ - X <- sf::st_as_sf(X) - - result <- fs_area_max(X, index, unit, col) - return(result) -} - - -#' @name fs_area_max -#' @export -fs_area_max.sf <- function(X, index=NULL, unit=NULL, col=NULL){ - if(!is.null(col)){ - if(!col %in% names(X)){ - message("Error: column name not found.") - stop() - } else{ - names(X)[which(names(X)==col)] <- "fs_area" - result <- fs_area_max_calc(X, index, unit) - } - } else{ - if(any(!sf::st_geometry_type(X) %in% c("POLYGON", "MULTIPOLYGON") )){ - message("Area requires polygon shapes.") - stop() - } - - if(is.na(sf::st_crs(X))){ - warning("Polygons have no spatial projection. Units ignored.") - unit <- NULL - - } else{ - if(is.null(unit)){ - unit <- "m" - } - } - - X[["fs_area"]] <- fs_area(X, unit) - result <- fs_area_max_calc(X, index, unit) - } - return(result) -} - - -fs_area_max_calc <- function(X, index=NULL, unit=NULL){ - if(!"fs_area" %in% names(X)){ - X[["fs_area"]] <- fs_area(X, unit) - } else{ - unit <- st_crs(X)$units - } - - if(is.null(index)){ - warning("No index found, treating as one group.") - index <- rep(1, nrow(X)) - } else{ - if(length(index)==1){ - if((is.numeric(index) & index <= ncol(X)) | - (is.character(index) & index %in% names(X))){ - index <- X[[index]] - } - } else if(length(index) != nrow(X)){ - message("Invalid index") - stop() - } - } - - colNam <- paste0("fs_area_", unit, "_max") - DT <- data.table::data.table(index=index, - area_calc=X[["fs_area"]]) - data.table::setkey(DT, index) - result <- DT[, setNames(.(max(area_calc)), colNam), by=index] - - return(result) -} diff --git a/R/fs_area_mean.R b/R/fs_area_mean.R deleted file mode 100644 index c874c24..0000000 --- a/R/fs_area_mean.R +++ /dev/null @@ -1,96 +0,0 @@ -#' Building area mean calculation -#' -#' @description Calculate and summarise selected metrics of building -#' footprint polygons within zones. -#' -#' @param X Spatial object with building footprint polygons -#' @param index A character or numeric value identifying a column within \code{X} -#' which provides a zonal index for summarising values. Alternatively a vector of -#' indices can be provided. If omitted all observations in \code{X} are assumed -#' to be within one zone. -#' @param unit character or \code{units} object to define area. -#' Default is \code{NULL} which will use the units of the spatial reference system -#' @param col column name within \code{X} with pre-calculated area measures -#' @return \code{data.table} of zonal indices and values -#' -#' @import data.table -#' -#' @aliases fs_area_mean -#' @rdname fs_area_mean -#' -#' @export -fs_area_mean <- function(X, index=NULL, unit=NULL, col=NULL) UseMethod("fs_area_mean") - - -#' @name fs_area_mean -#' @export -fs_area_mean.sp <- function(X, index=NULL, unit=NULL, col=NULL){ - X <- sf::st_as_sf(X) - - result <- fs_area_mean(X, index, unit, col) - return(result) -} - - -#' @name fs_area_mean -#' @export -fs_area_mean.sf <- function(X, index=NULL, unit=NULL, col=NULL){ - if(!is.null(col)){ - if(!col %in% names(X)){ - message("Error: column name not found.") - stop() - } else{ - names(X)[which(names(X)==col)] <- "fs_area" - result <- fs_area_mean_calc(X, index, unit) - } - } else{ - if(any(!sf::st_geometry_type(X) %in% c("POLYGON", "MULTIPOLYGON") )){ - message("Area requires polygon shapes.") - stop() - } - - if(is.na(sf::st_crs(X))){ - warning("Polygons have no spatial projection. Units ignored.") - unit <- NULL - - } else{ - if(is.null(unit)){ - unit <- "ha" - } - } - - X[["fs_area"]] <- fs_area(X, unit) - result <- fs_area_mean_calc(X, index, unit) - } - return(result) -} - - -fs_area_mean_calc <- function(X, index=NULL, unit=NULL){ - if(!"fs_area" %in% names(X)){ - X[["fs_area"]] <- fs_area(X, unit) - } - - if(is.null(index)){ - warning("No index found, treating as one group.") - index <- rep(1, nrow(X)) - } else{ - if(length(index)==1){ - if((is.numeric(index) & index <= ncol(X)) | - (is.character(index) & index %in% names(X))){ - index <- X[[index]] - } - } else if(length(index) != nrow(X)){ - message("Invalid index") - stop() - } - } - - colNam <- paste0("fs_area_", unit, "_mean") - DT <- data.table::data.table(index=index, - area_calc=X[["fs_area"]]) - data.table::setkey(DT, index) - result <- DT[, setNames(.(mean(area_calc)), colNam), by=index] - - return(result) -} diff --git a/R/fs_area_median.R b/R/fs_area_median.R deleted file mode 100644 index 4aa4f04..0000000 --- a/R/fs_area_median.R +++ /dev/null @@ -1,88 +0,0 @@ -#' Building perimeter mean calculation -#' -#' @description Calculate and summarise selected metrics of building -#' footprint polygons within zones. -#' @inheritParams fs_area_mean -#' @return \code{data.table} of zonal indices and values -#' -#' @import data.table -#' -#' @aliases fs_area_median -#' @rdname fs_area_median -#' -#' @export -fs_area_median <- function(X, index=NULL, unit=NULL, col=NULL) UseMethod("fs_area_median") - - -#' @name fs_area_median -#' @export -fs_area_median.sp <- function(X, index=NULL, unit=NULL, col=NULL){ - X <- sf::st_as_sf(X) - - result <- fs_area_median(X, index, unit, col) - return(result) -} - - -#' @name fs_area_median -#' @export -fs_area_median.sf <- function(X, index=NULL, unit=NULL, col=NULL){ - if(!is.null(col)){ - if(!col %in% names(X)){ - message("Error: column name not found.") - stop() - } else{ - names(X)[which(names(X)==col)] <- "fs_area" - result <- fs_area_median_calc(X, index, unit) - } - } else{ - if(any(!sf::st_geometry_type(X) %in% c("POLYGON", "MULTIPOLYGON") )){ - message("Area requires polygon shapes.") - stop() - } - - if(is.na(sf::st_crs(X))){ - warning("Polygons have no spatial projection. Units ignored.") - unit <- NULL - - } else{ - if(is.null(unit)){ - unit <- "ha" - } - } - - X[["fs_area"]] <- fs_area(X, unit) - result <- fs_area_median_calc(X, index, unit) - } - return(result) -} - - -fs_area_median_calc <- function(X, index=NULL, unit=NULL){ - if(!"fs_area" %in% names(X)){ - X[["fs_area"]] <- fs_area(X, unit) - } - - if(is.null(index)){ - warning("No index found, treating as one group.") - index <- rep(1, nrow(X)) - } else{ - if(length(index)==1){ - if((is.numeric(index) & index <= ncol(X)) | - (is.character(index) & index %in% names(X))){ - index <- X[[index]] - } - } else if(length(index) != nrow(X)){ - message("Invalid index") - stop() - } - } - - colNam <- paste0("fs_area_", unit, "_median") - DT <- data.table::data.table(index=index, - area_calc=X[["fs_area"]]) - data.table::setkey(DT, index) - result <- DT[, setNames(.(median(area_calc)), colNam), by=index] - - return(result) -} diff --git a/R/fs_area_min.R b/R/fs_area_min.R deleted file mode 100644 index 931bcf9..0000000 --- a/R/fs_area_min.R +++ /dev/null @@ -1,93 +0,0 @@ -#' Building area minimum calculation -#' -#' @description Calculate and summarise selected metrics of building -#' footprint polygons within zones. -#' #' @inheritParams fs_area_mean -#' @return \code{data.table} of zonal indices and values -#' -#' @import data.table -#' -#' @aliases fs_area_min -#' @rdname fs_area_min -#' -#' @export -fs_area_min <- function(X, - index=NULL, - unit=NULL, - col=NULL) UseMethod("fs_area_min") - - -#' @name fs_area_min -#' @export -fs_area_min.sp <- function(X, index=NULL, unit=NULL, col=NULL){ - X <- sf::st_as_sf(X) - - result <- fs_area_min(X, index, unit, col) - return(result) -} - - -#' @name fs_area_min -#' @export -fs_area_min.sf <- function(X, index=NULL, unit=NULL, col=NULL){ - if(!is.null(col)){ - if(!col %in% names(X)){ - message("Error: column name not found.") - stop() - } else{ - names(X)[which(names(X)==col)] <- "fs_area" - result <- fs_area_min_calc(X, index, unit) - } - } else{ - if(any(!sf::st_geometry_type(X) %in% c("POLYGON", "MULTIPOLYGON") )){ - message("Area requires polygon shapes.") - stop() - } - - if(is.na(sf::st_crs(X))){ - warning("Polygons have no spatial projection. Units ignored.") - unit <- NULL - - } else{ - if(is.null(unit)){ - unit <- "m" - } - } - - X[["fs_area"]] <- fs_area(X, unit) - result <- fs_area_min_calc(X, index, unit) - } - return(result) -} - - -fs_area_min_calc <- function(X, index=NULL, unit=NULL){ - if(!"fs_area" %in% names(X)){ - X[["fs_area"]] <- fs_area(X, unit) - } else{ - unit <- st_crs(X)$units - } - - if(is.null(index)){ - warning("No index found, treating as one group.") - index <- rep(1, nrow(X)) - } else{ - if(length(index)==1){ - if((is.numeric(index) & index <= ncol(X)) | - (is.character(index) & index %in% names(X))){ - index <- X[[index]] - } - } else if(length(index) != nrow(X)){ - message("Invalid index") - stop() - } - } - - colNam <- paste0("fs_area_", unit, "_min") - DT <- data.table::data.table(index=index, - area_calc=X[["fs_area"]]) - data.table::setkey(DT, index) - result <- DT[, setNames(.(min(area_calc)), colNam), by=index] - - return(result) -} diff --git a/R/fs_area_sd.R b/R/fs_area_sd.R deleted file mode 100644 index 2b0175a..0000000 --- a/R/fs_area_sd.R +++ /dev/null @@ -1,88 +0,0 @@ -#' Building area standard deviation calculation -#' -#' @description Calculate and summarise selected metrics of building -#' footprint polygons within zones. -#' @inheritParams fs_area_mean -#' @return \code{data.table} of zonal indices and values -#' -#' @import data.table -#' -#' @aliases fs_area_sd -#' @rdname fs_area_sd -#' -#' @export -fs_area_sd <- function(X, index=NULL, unit=NULL, col=NULL) UseMethod("fs_area_sd") - - -#' @name fs_area_sd -#' @export -fs_area_sd.sp <- function(X, index=NULL, unit=NULL, col=NULL){ - X <- sf::st_as_sf(X) - - result <- fs_area_sd(X, index, unit, col) - return(result) -} - - -#' @name fs_area_sd -#' @export -fs_area_sd.sf <- function(X, index=NULL, unit=NULL, col=NULL){ - if(!is.null(col)){ - if(!col %in% names(X)){ - message("Error: column name not found.") - stop() - } else{ - names(X)[which(names(X)==col)] <- "fs_area" - result <- fs_area_sd_calc(X, index, unit) - } - } else{ - if(any(!sf::st_geometry_type(X) %in% c("POLYGON", "MULTIPOLYGON") )){ - message("Area requires polygon shapes.") - stop() - } - - if(is.na(sf::st_crs(X))){ - warning("Polygons have no spatial projection. Units ignored.") - unit <- NULL - - } else{ - if(is.null(unit)){ - unit <- "ha" - } - } - - X[["fs_area"]] <- fs_area(X, unit) - result <- fs_area_sd_calc(X, index, unit) - } - return(result) -} - - -fs_area_sd_calc <- function(X, index, unit=NULL){ - if(!"fs_area" %in% names(X)){ - X[["fs_area"]] <- fs_area(X, unit) - } - - if(is.null(index)){ - warning("No index found, treating as one group.") - index <- rep(1, nrow(X)) - } else{ - if(length(index)==1){ - if((is.numeric(index) & index <= ncol(X)) | - (is.character(index) & index %in% names(X))){ - index <- X[[index]] - } - } else if(length(index) != nrow(X)){ - message("Invalid index") - stop() - } - } - - colNam <- paste0("fs_area_", unit, "_sd") - DT <- data.table::data.table(index=index, - area_calc=X[["fs_area"]]) - data.table::setkey(DT, index) - result <- DT[, setNames(.(sd(area_calc)), colNam), by=index] - - return(result) -} diff --git a/R/fs_area_total.R b/R/fs_area_total.R deleted file mode 100644 index 5ad1189..0000000 --- a/R/fs_area_total.R +++ /dev/null @@ -1,88 +0,0 @@ -#' Building area total calculation -#' -#' @description Calculate and summarise selected metrics of building -#' footprint polygons within zones. -#' @inheritParams fs_area_mean -#' @return \code{data.table} of zonal indices and values -#' -#' @import data.table -#' -#' @aliases fs_area_total -#' @rdname fs_area_total -#' -#' @export -fs_area_total <- function(X, index=NULL, unit=NULL, col=NULL) UseMethod("fs_area_total") - - -#' @name fs_area_total -#' @export -fs_area_total.sp <- function(X, index=NULL, unit=NULL, col=NULL){ - X <- sf::st_as_sf(X) - - result <- fs_area_total(X, index, unit, col) - return(result) -} - - -#' @name fs_area_total -#' @export -fs_area_total.sf <- function(X, index=NULL, unit=NULL, col=NULL){ - if(any(!sf::st_geometry_type(X) %in% c("POLYGON", "MULTIPOLYGON") )){ - message("Area requires polygon shapes.") - stop() - } - - if(is.na(sf::st_crs(X))){ - warning("Polygons have no spatial projection. Units ignored.") - unit <- NULL - - } else{ - if(is.null(unit)){ - unit <- "ha" - } - } - - if(!is.null(col)){ - if(!col %in% names(X)){ - message("Error: column name not found.") - stop() - } else{ - names(X)[which(names(X)==col)] <- "fs_area" - result <- fs_area_total_calc(X, index, unit) - } - } else{ - X[["fs_area"]] <- fs_area(X, unit) - result <- fs_area_total_calc(X, index, unit) - } - return(result) -} - - -fs_area_total_calc <- function(X, index, unit=NULL){ - if(!"fs_area" %in% names(X)){ - X[["fs_area"]] <- fs_area(X, unit) - } - - if(is.null(index)){ - warning("No index found, treating as one group.") - index <- rep(1, nrow(X)) - } else{ - if(length(index)==1){ - if((is.numeric(index) & index <= ncol(X)) | - (is.character(index) & index %in% names(X))){ - index <- X[[index]] - } - } else if(length(index) != nrow(X)){ - message("Invalid index") - stop() - } - } - - colNam <- paste0("fs_area_", unit, "_total") - DT <- data.table::data.table(index=index, - area_calc=X[["fs_area"]]) - data.table::setkey(DT, index) - result <- DT[, setNames(.(sum(area_calc)), colNam), by=index] - - return(result) -} diff --git a/R/fs_compact_mean.R b/R/fs_compact_mean.R deleted file mode 100644 index 096264a..0000000 --- a/R/fs_compact_mean.R +++ /dev/null @@ -1,114 +0,0 @@ -#' Building compactness measures -#' -#' @description Calculate and summarise selected metrics of building footprint -#' polygons within zones. -#' @inheritParams fs_area_mean -#' @return \code{data.table} of zonal indices and values -#' -#' @details The compactness measure is the Polsby-Popper test. This is -#' summarised as the mean for all footprints in the zone identified by -#' \code{index}. -#' -#' \deqn{ PP_z = \frac{4$\pi$ * A_z}{P_z^2} }, -#' where P and A are the perimeter and area of zone 'z', respectively. -#' -#' @source Polsby, Daniel D., and Robert D. Popper. 1991. “The Third Criterion: -#' Compactness as a procedural safeguard against partisan gerrymandering.” -#' Yale Law & Policy Review 9 (2): 301–353. -#' -#' @import data.table -#' -#' @aliases fs_compact_mean -#' @rdname fs_compact_mean -#' -#' @export -fs_compact_mean <- function(X, - index=NULL, - unit=NULL, - col=NULL) UseMethod("fs_compact_mean") - - -#' @name fs_compact_mean -#' @export -fs_compact_mean.sp <- function(X, index=NULL, unit=NULL, col=NULL){ - X <- sf::st_as_sf(X) - - result <- fs_compact_mean(X, index, unit, col) - return(result) -} - - -#' @name fs_compact_mean -#' @export -fs_compact_mean.sf <- function(X, index=NULL, unit=NULL, col=NULL){ - if(!is.null(col)){ - # warning("Ignoring supplied column.") - if(!col %in% names(X)){ - message("Error: column name not found.") - stop() - } else{ - names(X)[which(names(X)==col)] <- "fs_compact" - result <- fs_compact_mean_calc(X, index, unit) - } - } else{ # column not supplied - if(any(!sf::st_geometry_type(X) %in% c("POLYGON", "MULTIPOLYGON") )){ - message("Area requires polygon shapes.") - stop() - } - - if(is.na(sf::st_crs(X))){ - warning("Polygons have no spatial projection. Units ignored.") - unit <- NULL - } else{ - if(is.null(unit)){ - unit <- "m^2" - } - } - - X[["fs_compact"]] <- fs_compact(X) - result <- fs_compact_mean_calc(X, index, unit) - } - return(result) -} - - -fs_compact_mean_calc <- function(X, index, unit=NULL){ - if(!"fs_compact" %in% names(X)){ - if(!"fs_area" %in% names(X)){ - X[["fs_area"]] <- fs_area(X, "m^2") - } else{ - units(X[["fs_area"]]) <- "m^2" - } - - if(!"fs_perimeter" %in% names(X)){ - X[["fs_perimeter"]] <- fs_perimeter(X, "m") - } else{ - units(X[["fs_perimeter"]]) <- "m" - } - - X[["fs_compact"]] <- fs_compact(X) - } - - if(is.null(index)){ - warning("No index found, treating as one group.") - index <- rep(1, nrow(X)) - } else{ - if(length(index)==1){ - if((is.numeric(index) & index <= ncol(X)) | - (is.character(index) & index %in% names(X))){ - index <- X[[index]] - } - } else if(length(index) != nrow(X)){ - message("Invalid index") - stop() - } - } - - colNam <- "fs_compact_mean" - DT <- data.table::data.table(index=index, - area_calc=X[["fs_compact"]]) - data.table::setkey(DT, index) - result <- DT[, setNames(.(mean(area_calc)), colNam), by=index] - - return(result) -} diff --git a/R/fs_compact_median.R b/R/fs_compact_median.R deleted file mode 100644 index 1a5200c..0000000 --- a/R/fs_compact_median.R +++ /dev/null @@ -1,114 +0,0 @@ -#' Building compactness measures -#' -#' @description Calculate and summarise selected metrics of building footprint -#' polygons within zones. -#' @inheritParams fs_area_mean -#' @return \code{data.table} of zonal indices and values -#' -#' @details The compactness measure is the Polsby-Popper test. This is -#' summarised as the mean for all footprints in the zone identified by -#' \code{index}. -#' -#' \deqn{ PP_z = \frac{4$\pi$ * A_z}{P_z^2} }, -#' where P and A are the perimeter and area of zone 'z', respectively. -#' -#' @source Polsby, Daniel D., and Robert D. Popper. 1991. “The Third Criterion: -#' Compactness as a procedural safeguard against partisan gerrymandering.” -#' Yale Law & Policy Review 9 (2): 301–353. -#' -#' @import data.table -#' -#' @aliases fs_compact_median -#' @rdname fs_compact_median -#' -#' @export -fs_compact_median <- function(X, - index=NULL, - unit=NULL, - col=NULL) UseMethod("fs_compact_median") - - -#' @name fs_compact_median -#' @export -fs_compact_median.sp <- function(X, index=NULL, unit=NULL, col=NULL){ - X <- sf::st_as_sf(X) - - result <- fs_compact_median(X, index, unit, col) - return(result) -} - - -#' @name fs_compact_median -#' @export -fs_compact_median.sf <- function(X, index=NULL, unit=NULL, col=NULL){ - if(!is.null(col)){ - # warning("Ignoring supplied column.") - if(!col %in% names(X)){ - message("Error: column name not found.") - stop() - } else{ - names(X)[which(names(X)==col)] <- "fs_compact" - result <- fs_compact_median_calc(X, index, unit) - } - } else{ # column not supplied - if(any(!sf::st_geometry_type(X) %in% c("POLYGON", "MULTIPOLYGON") )){ - message("Area requires polygon shapes.") - stop() - } - - if(is.na(sf::st_crs(X))){ - warning("Polygons have no spatial projection. Units ignored.") - unit <- NULL - } else{ - if(is.null(unit)){ - unit <- "m^2" - } - } - - X[["fs_compact"]] <- fs_compact(X) - result <- fs_compact_median_calc(X, index, unit) - } - return(result) -} - - -fs_compact_median_calc <- function(X, index, unit=NULL){ - if(!"fs_compact" %in% names(X)){ - if(!"fs_area" %in% names(X)){ - X[["fs_area"]] <- fs_area(X, "m^2") - } else{ - units(X[["fs_area"]]) <- "m^2" - } - - if(!"fs_perimeter" %in% names(X)){ - X[["fs_perimeter"]] <- fs_perimeter(X, "m") - } else{ - units(X[["fs_perimeter"]]) <- "m" - } - - X[["fs_compact"]] <- fs_compact(X) - } - - if(is.null(index)){ - warning("No index found, treating as one group.") - index <- rep(1, nrow(X)) - } else{ - if(length(index)==1){ - if((is.numeric(index) & index <= ncol(X)) | - (is.character(index) & index %in% names(X))){ - index <- X[[index]] - } - } else if(length(index) != nrow(X)){ - message("Invalid index") - stop() - } - } - - colNam <- "fs_compact_median" - DT <- data.table::data.table(index=index, - area_calc=X[["fs_compact"]]) - data.table::setkey(DT, index) - result <- DT[, setNames(.(median(area_calc)), colNam), by=index] - - return(result) -} diff --git a/R/fs_functions.R b/R/fs_functions.R new file mode 100644 index 0000000..4618897 --- /dev/null +++ b/R/fs_functions.R @@ -0,0 +1,88 @@ +#' Built-in 'foot' functions +#' @description In addition to generic \code{R} functions (e.g. 'mean', 'max', +#' etc.), these functions are designed to provide simple access to common +#' calculations. +#' @param x numeric vector of values to summarize. +#' @return numeric value +#' @details These functions are designed to be used within +#' \code{\link[foot]{calculate_footstats}} which processes a \code{data.table} +#' by group ID. Therefore all functions take a vector of values and return a +#' single summary statistic. These functions are not likely to be used on +#' their own. +#' @name fs_functions +NULL +#> NULL + +#' @rdname fs_functions +binary <- function(x){ + return(ifelse(is.null(x), 0, 1)) +} + + +#' @rdname fs_functions +count <- function(x){ + return(length(x)) +} + + +#' @rdname fs_functions +cv <- function(x){ + res <- sd(x) / mean(x) + return(units::set_units(res, NULL)) +} + + +#' @rdname fs_functions +entropy <- function(x){ + bins <- cut(x, seq(5, 355, 10), labels=F) + 1 + bins[is.na(bins)] <- 1 + calc_ent <- -1 * sum(prop.table(table(bins)) * log(prop.table(table(bins)))) + + # normalizing step (see Boeing (2019)) + hmax <- 3.584 + hg <- 1.386 + + calc_ent <- 1 - ((calc_ent-hg) / (hmax - hg))^2 + return(calc_ent) +} + + +#' @rdname fs_functions +majority <- function(x){ + return(names(which.max(table(x)))) +} + + +# creating a function factory to use the pre-made function foot::fs_nnindex +#' Generate a nearest neighbour index function +#' @description Creates a new instance of the \code{fs_nnindex} function and +#' initialises it with zone and unit information. +#' @param zone A spatial polygon object of \code{sf} or \code{sp} type. If +#' omitted all observations in \code{X} are assumed to be within one zone and +#' the area of the minimum bounding circle is used for the nearest neighbour +#' index. +#' @param zoneField (Optional) Column name of unique identifiers in \code{zone} +#' to use. If omitted, the 'zoneID' will be numbered \code{1:nrow(zone)}. +#' @param unit character or \code{units} object to define distance. Default will +#' attempt to coerce units to meters. +#' @details This is a function factory. It creates a partial function in order +#' to allow \code{fs_nnindex} to be used by the internal loop of +#' \code{calculate_footstats}. This function will generally not be used on its +#' own. +#' @name gen_nnindex +gen_nnindex <- function(zone, zoneField=NULL, unit="m"){ + force(zone) + force(zoneField) + force(unit) + + function(x){ + if(length(x) == 1){ + return(0) + } else{ + res <- fs_nnindex(sf::st_as_sf(x), zone, zoneField, unit) + return(res[["fs_nnindex"]]) + } + } +} + + diff --git a/R/fs_perim_cv.R b/R/fs_perim_cv.R deleted file mode 100644 index c41b1c1..0000000 --- a/R/fs_perim_cv.R +++ /dev/null @@ -1,99 +0,0 @@ -#' Building perimeter coefficient of variation (CV) -#' -#' @description Calculate and summarise selected metrics of building -#' footprint polygons within zones. -#' #' @inheritParams fs_area_mean -#' @return \code{data.table} of zonal indices and values -#' -#' @import data.table -#' -#' @aliases fs_perim_cv -#' @rdname fs_perim_cv -#' -#' @export -fs_perim_cv <- function(X, index=NULL, unit=NULL, col=NULL) UseMethod("fs_perim_cv") - - -#' @name fs_perim_cv -#' @export -fs_perim_cv.sp <- function(X, index=NULL, unit=NULL, col=NULL){ - X <- sf::st_as_sf(X) - - result <- fs_perim_cv(X, index, unit, col) - return(result) -} - - -#' @name fs_perim_cv -#' @export -fs_perim_cv.sf <- function(X, index=NULL, unit=NULL, col=NULL){ - if(!is.null(col)){ - if(!col %in% names(X)){ - message("Error: column name not found.") - stop() - } else{ - names(X)[which(names(X)==col)] <- "fs_perim" - result <- fs_perim_cv_calc(X, index, unit) - } - } else{ - if(any(!sf::st_geometry_type(X) %in% c("POLYGON", "MULTIPOLYGON") )){ - message("Perimeter requires polygon shapes.") - stop() - } - - if(is.na(sf::st_crs(X))){ - warning("Polygons have no spatial projection. Units ignored.") - unit <- NULL - - } else{ - if(is.null(unit)){ - unit <- "m" - } - } - - X[["fs_perim"]] <- fs_perimeter(X, unit) - result <- fs_perim_cv_calc(X, index, unit) - } - return(result) -} - - -fs_perim_cv_calc <- function(X, index=NULL, unit=NULL){ - if(!"fs_perim" %in% names(X)){ - X[["fs_perim"]] <- fs_perimeter(X, unit) - } else{ - unit <- st_crs(X)$units - } - - if(is.null(index)){ - warning("No index found, treating as one group.") - index <- rep(1, nrow(X)) - } - # else{ - # if(length(index)==1){ - # if((is.numeric(index) & index <= ncol(X)) | - # (is.character(index) & index %in% names(X))){ - # index <- X[[index]] - # } - # } else if(length(index) != nrow(X)){ - # message("Invalid index") - # stop() - # } - # } - - meanDT <- fs_perim_mean(X, index=index, unit=unit, col="fs_perim") - sdDT <- fs_perim_sd(X, index=index, unit=unit, col="fs_perim") - - mCol <- paste0("fs_perim_", unit, "_mean") - sdCol <- paste0("fs_perim_", unit, "_sd") - - DT <- merge(meanDT, sdDT, by="index") - DT[, area_calc := get(sdCol) / get(mCol), by=index] - DT[, area_calc := units::drop_units(area_calc)] - - colNam <- "fs_perim_cv" - data.table::setkey(DT, index) - data.table::setnames(DT, "area_calc", colNam) - - return(DT[, list(index, fs_perim_cv)]) -} diff --git a/R/fs_perim_max.R b/R/fs_perim_max.R deleted file mode 100644 index d348774..0000000 --- a/R/fs_perim_max.R +++ /dev/null @@ -1,93 +0,0 @@ -#' Building perimeter maximum calculation -#' -#' @description Calculate and summarise selected metrics of building -#' footprint polygons within zones. -#' #' @inheritParams fs_area_mean -#' @return \code{data.table} of zonal indices and values -#' -#' @import data.table -#' -#' @aliases fs_perim_max -#' @rdname fs_perim_max -#' -#' @export -fs_perim_max <- function(X, - index=NULL, - unit=NULL, - col=NULL) UseMethod("fs_perim_max") - - -#' @name fs_perim_max -#' @export -fs_perim_max.sp <- function(X, index=NULL, unit=NULL, col=NULL){ - X <- sf::st_as_sf(X) - - result <- fs_perim_max(X, index, unit, col) - return(result) -} - - -#' @name fs_perim_max -#' @export -fs_perim_max.sf <- function(X, index=NULL, unit=NULL, col=NULL){ - if(!is.null(col)){ - if(!col %in% names(X)){ - message("Error: column name not found.") - stop() - } else{ - names(X)[which(names(X)==col)] <- "fs_perim" - result <- fs_perim_max_calc(X, index, unit) - } - } else{ - if(any(!sf::st_geometry_type(X) %in% c("POLYGON", "MULTIPOLYGON") )){ - message("Area requires polygon shapes.") - stop() - } - - if(is.na(sf::st_crs(X))){ - warning("Polygons have no spatial projection. Units ignored.") - unit <- NULL - - } else{ - if(is.null(unit)){ - unit <- "m" - } - } - - X[["fs_perim"]] <- fs_perim(X, unit) - result <- fs_perim_max_calc(X, index, unit) - } - return(result) -} - - -fs_perim_max_calc <- function(X, index=NULL, unit=NULL){ - if(!"fs_perim" %in% names(X)){ - X[["fs_perim"]] <- fs_perim(X, unit) - } else{ - unit <- st_crs(X)$units - } - - if(is.null(index)){ - warning("No index found, treating as one group.") - index <- rep(1, nrow(X)) - } else{ - if(length(index)==1){ - if((is.numeric(index) & index <= ncol(X)) | - (is.character(index) & index %in% names(X))){ - index <- X[[index]] - } - } else if(length(index) != nrow(X)){ - message("Invalid index") - stop() - } - } - - colNam <- paste0("fs_perim_", unit, "_max") - DT <- data.table::data.table(index=index, - area_calc=X[["fs_perim"]]) - data.table::setkey(DT, index) - result <- DT[, setNames(.(max(area_calc)), colNam), by=index] - - return(result) -} diff --git a/R/fs_perim_mean.R b/R/fs_perim_mean.R deleted file mode 100644 index e51129e..0000000 --- a/R/fs_perim_mean.R +++ /dev/null @@ -1,93 +0,0 @@ -#' Building perimeter mean calculation -#' -#' @description Calculate and summarise selected metrics of building -#' footprint polygons within zones. -#' #' @inheritParams fs_area_mean -#' @return \code{data.table} of zonal indices and values -#' -#' @import data.table -#' -#' @aliases fs_perim_mean -#' @rdname fs_perim_mean -#' -#' @export -fs_perim_mean <- function(X, - index=NULL, - unit=NULL, - col=NULL) UseMethod("fs_perim_mean") - - -#' @name fs_perim_mean -#' @export -fs_perim_mean.sp <- function(X, index=NULL, unit=NULL, col=NULL){ - X <- sf::st_as_sf(X) - - result <- fs_perim_mean(X, index, unit, col) - return(result) -} - - -#' @name fs_perim_mean -#' @export -fs_perim_mean.sf <- function(X, index=NULL, unit=NULL, col=NULL){ - if(!is.null(col)){ - if(!col %in% names(X)){ - message("Error: column name not found.") - stop() - } else{ - names(X)[which(names(X)==col)] <- "fs_perim" - result <- fs_perim_mean_calc(X, index, unit) - } - } else{ - if(any(!sf::st_geometry_type(X) %in% c("POLYGON", "MULTIPOLYGON") )){ - message("Perimeter requires polygon shapes.") - stop() - } - - if(is.na(sf::st_crs(X))){ - warning("Polygons have no spatial projection. Units ignored.") - unit <- NULL - - } else{ - if(is.null(unit)){ - unit <- "m" - } - } - - X[["fs_perim"]] <- fs_perimeter(X, unit) - result <- fs_perim_mean_calc(X, index, unit) - } - return(result) -} - - -fs_perim_mean_calc <- function(X, index=NULL, unit=NULL){ - if(!"fs_perim" %in% names(X)){ - X[["fs_perim"]] <- fs_perimeter(X, unit) - } else{ - unit <- st_crs(X)$units - } - - if(is.null(index)){ - warning("No index found, treating as one group.") - index <- rep(1, nrow(X)) - } else{ - if(length(index)==1){ - if((is.numeric(index) & index <= ncol(X)) | - (is.character(index) & index %in% names(X))){ - index <- X[[index]] - } - } else if(length(index) != nrow(X)){ - message("Invalid index") - stop() - } - } - - colNam <- paste0("fs_perim_", unit, "_mean") - DT <- data.table::data.table(index=index, - area_calc=X[["fs_perim"]]) - data.table::setkey(DT, index) - result <- DT[, setNames(.(mean(area_calc)), colNam), by=index] - - return(result) -} diff --git a/R/fs_perim_median.R b/R/fs_perim_median.R deleted file mode 100644 index a8edcef..0000000 --- a/R/fs_perim_median.R +++ /dev/null @@ -1,93 +0,0 @@ -#' Building perimeter median calculation -#' -#' @description Calculate and summarise selected metrics of building -#' footprint polygons within zones. -#' #' @inheritParams fs_area_mean -#' @return \code{data.table} of zonal indices and values -#' -#' @import data.table -#' -#' @aliases fs_perim_median -#' @rdname fs_perim_median -#' -#' @export -fs_perim_median <- function(X, - index=NULL, - unit=NULL, - col=NULL) UseMethod("fs_perim_median") - - -#' @name fs_perim_median -#' @export -fs_perim_median.sp <- function(X, index=NULL, unit=NULL, col=NULL){ - X <- sf::st_as_sf(X) - - result <- fs_perim_median(X, index, unit, col) - return(result) -} - - -#' @name fs_perim_median -#' @export -fs_perim_median.sf <- function(X, index=NULL, unit=NULL, col=NULL){ - if(!is.null(col)){ - if(!col %in% names(X)){ - message("Error: column name not found.") - stop() - } else{ - names(X)[which(names(X)==col)] <- "fs_perim" - result <- fs_perim_median_calc(X, index, unit) - } - } else{ - if(any(!sf::st_geometry_type(X) %in% c("POLYGON", "MULTIPOLYGON") )){ - message("Perimeter requires polygon shapes.") - stop() - } - - if(is.na(sf::st_crs(X))){ - warning("Polygons have no spatial projection. Units ignored.") - unit <- NULL - - } else{ - if(is.null(unit)){ - unit <- "m" - } - } - - X[["fs_perim"]] <- fs_perimeter(X, unit) - result <- fs_perim_median_calc(X, index, unit) - } - return(result) -} - - -fs_perim_median_calc <- function(X, index=NULL, unit=NULL){ - if(!"fs_perim" %in% names(X)){ - X[["fs_perim"]] <- fs_perimeter(X, unit) - } else{ - unit <- st_crs(X)$units - } - - if(is.null(index)){ - warning("No index found, treating as one group.") - index <- rep(1, nrow(X)) - } else{ - if(length(index)==1){ - if((is.numeric(index) & index <= ncol(X)) | - (is.character(index) & index %in% names(X))){ - index <- X[[index]] - } - } else if(length(index) != nrow(X)){ - message("Invalid index") - stop() - } - } - - colNam <- paste0("fs_perim_", unit, "_median") - DT <- data.table::data.table(index=index, - area_calc=X[["fs_perim"]]) - data.table::setkey(DT, index) - result <- DT[, setNames(.(median(area_calc)), colNam), by=index] - - return(result) -} diff --git a/R/fs_perim_min.R b/R/fs_perim_min.R deleted file mode 100644 index ebcf478..0000000 --- a/R/fs_perim_min.R +++ /dev/null @@ -1,93 +0,0 @@ -#' Building perimeter minimum calculation -#' -#' @description Calculate and summarise selected metrics of building -#' footprint polygons within zones. -#' #' @inheritParams fs_area_mean -#' @return \code{data.table} of zonal indices and values -#' -#' @import data.table -#' -#' @aliases fs_perim_min -#' @rdname fs_perim_min -#' -#' @export -fs_perim_min <- function(X, - index=NULL, - unit=NULL, - col=NULL) UseMethod("fs_perim_min") - - -#' @name fs_perim_min -#' @export -fs_perim_min.sp <- function(X, index=NULL, unit=NULL, col=NULL){ - X <- sf::st_as_sf(X) - - result <- fs_perim_min(X, index, unit, col) - return(result) -} - - -#' @name fs_perim_min -#' @export -fs_perim_min.sf <- function(X, index=NULL, unit=NULL, col=NULL){ - if(!is.null(col)){ - if(!col %in% names(X)){ - message("Error: column name not found.") - stop() - } else{ - names(X)[which(names(X)==col)] <- "fs_perim" - result <- fs_perim_min_calc(X, index, unit) - } - } else{ - if(any(!sf::st_geometry_type(X) %in% c("POLYGON", "MULTIPOLYGON") )){ - message("Area requires polygon shapes.") - stop() - } - - if(is.na(sf::st_crs(X))){ - warning("Polygons have no spatial projection. Units ignored.") - unit <- NULL - - } else{ - if(is.null(unit)){ - unit <- "m" - } - } - - X[["fs_perim"]] <- fs_perim(X, unit) - result <- fs_perim_min_calc(X, index, unit) - } - return(result) -} - - -fs_perim_min_calc <- function(X, index=NULL, unit=NULL){ - if(!"fs_perim" %in% names(X)){ - X[["fs_perim"]] <- fs_perim(X, unit) - } else{ - unit <- st_crs(X)$units - } - - if(is.null(index)){ - warning("No index found, treating as one group.") - index <- rep(1, nrow(X)) - } else{ - if(length(index)==1){ - if((is.numeric(index) & index <= ncol(X)) | - (is.character(index) & index %in% names(X))){ - index <- X[[index]] - } - } else if(length(index) != nrow(X)){ - message("Invalid index") - stop() - } - } - - colNam <- paste0("fs_perim_", unit, "_min") - DT <- data.table::data.table(index=index, - area_calc=X[["fs_perim"]]) - data.table::setkey(DT, index) - result <- DT[, setNames(.(min(area_calc)), colNam), by=index] - - return(result) -} diff --git a/R/fs_perim_sd.R b/R/fs_perim_sd.R deleted file mode 100644 index 82fc430..0000000 --- a/R/fs_perim_sd.R +++ /dev/null @@ -1,90 +0,0 @@ -#' Building perimeter standard deviation calculation -#' -#' @description Calculate and summarise selected metrics of building -#' footprint polygons within zones. -#' #' @inheritParams fs_area_mean -#' @return \code{data.table} of zonal indices and values -#' -#' @import data.table -#' -#' @aliases fs_perim_sd -#' @rdname fs_perim_sd -#' -#' @export -fs_perim_sd <- function(X, index=NULL, unit=NULL, col=NULL) UseMethod("fs_perim_sd") - - -#' @name fs_perim_sd -#' @export -fs_perim_sd.sp <- function(X, index=NULL, unit=NULL, col=NULL){ - X <- sf::st_as_sf(X) - - result <- fs_perim_sd(X, index, unit, col) - return(result) -} - - -#' @name fs_perim_sd -#' @export -fs_perim_sd.sf <- function(X, index=NULL, unit=NULL, col=NULL){ - if(!is.null(col)){ - if(!col %in% names(X)){ - message("Error: column name not found.") - stop() - } else{ - names(X)[which(names(X)==col)] <- "fs_perim" - result <- fs_perim_sd_calc(X, index, unit) - } - } else{ - if(any(!sf::st_geometry_type(X) %in% c("POLYGON", "MULTIPOLYGON") )){ - message("Perimeter requires polygon shapes.") - stop() - } - - if(is.na(sf::st_crs(X))){ - warning("Polygons have no spatial projection. Units ignored.") - unit <- NULL - - } else{ - if(is.null(unit)){ - unit <- "m" - } - } - - X[["fs_perim"]] <- fs_perimeter(X, unit) - result <- fs_perim_sd_calc(X, index, unit) - } - return(result) -} - - -fs_perim_sd_calc <- function(X, index=NULL, unit=NULL){ - if(!"fs_perim" %in% names(X)){ - X[["fs_perim"]] <- fs_perimeter(X, unit) - } else{ - unit <- st_crs(X)$units - } - - if(is.null(index)){ - warning("No index found, treating as one group.") - index <- rep(1, nrow(X)) - } else{ - if(length(index)==1){ - if((is.numeric(index) & index <= ncol(X)) | - (is.character(index) & index %in% names(X))){ - index <- X[[index]] - } - } else if(length(index) != nrow(X)){ - message("Invalid index") - stop() - } - } - - colNam <- paste0("fs_perim_", unit, "_sd") - DT <- data.table::data.table(index=index, - area_calc=X[["fs_perim"]]) - data.table::setkey(DT, index) - result <- DT[, setNames(.(sd(area_calc)), colNam), by=index] - - return(result) -} diff --git a/R/fs_perim_total.R b/R/fs_perim_total.R deleted file mode 100644 index 75095e0..0000000 --- a/R/fs_perim_total.R +++ /dev/null @@ -1,90 +0,0 @@ -#' Building perimeter total calculation -#' -#' @description Calculate and summarise selected metrics of building -#' footprint polygons within zones. -#' #' @inheritParams fs_area_mean -#' @return \code{data.table} of zonal indices and values -#' -#' @import data.table -#' -#' @aliases fs_perim_total -#' @rdname fs_perim_total -#' -#' @export -fs_perim_total <- function(X, index=NULL, unit=NULL, col=NULL) UseMethod("fs_perim_total") - - -#' @name fs_perim_total -#' @export -fs_perim_total.sp <- function(X, index=NULL, unit=NULL, col=NULL){ - X <- sf::st_as_sf(X) - - result <- fs_perim_total(X, index, unit, col) - return(result) -} - - -#' @name fs_perim_total -#' @export -fs_perim_total.sf <- function(X, index=NULL, unit=NULL, col=NULL){ - if(any(!sf::st_geometry_type(X) %in% c("POLYGON", "MULTIPOLYGON") )){ - message("Perimeter requires polygon shapes.") - stop() - } - - if(is.na(sf::st_crs(X))){ - warning("Polygons have no spatial projection. Units ignored.") - unit <- NULL - - } else{ - if(is.null(unit)){ - unit <- "m" - } - } - - if(!is.null(col)){ - if(!col %in% names(X)){ - message("Error: column name not found.") - stop() - } else{ - names(X)[which(names(X)==col)] <- "fs_perim" - result <- fs_perim_total_calc(X, index, unit) - } - } else{ - X[["fs_perim"]] <- fs_perimeter(X, unit) - result <- fs_perim_total_calc(X, index, unit) - } - return(result) -} - - -fs_perim_total_calc <- function(X, index=NULL, unit=NULL){ - if(!"fs_perim" %in% names(X)){ - X[["fs_perim"]] <- fs_perimeter(X, unit) - } else{ - unit <- st_crs(X)$units - } - - if(is.null(index)){ - warning("No index found, treating as one group.") - index <- rep(1, nrow(X)) - } else{ - if(length(index)==1){ - if((is.numeric(index) & index <= ncol(X)) | - (is.character(index) & index %in% names(X))){ - index <- X[[index]] - } - } else if(length(index) != nrow(X)){ - message("Invalid index") - stop() - } - } - - colNam <- paste0("fs_perim_", unit, "_total") - DT <- data.table::data.table(index=index, - area_calc=X[["fs_perim"]]) - data.table::setkey(DT, index) - result <- DT[, setNames(.(sum(area_calc)), colNam), by=index] - - return(result) -} diff --git a/R/fs_settled_binary.R b/R/fs_settled_binary.R deleted file mode 100644 index 60f773a..0000000 --- a/R/fs_settled_binary.R +++ /dev/null @@ -1,73 +0,0 @@ -#' Binary indicator of settled status -#' -#' @description Calculate and summarise selected metrics of building -#' footprint polygons within zones. -#' @inheritParams fs_area_mean -#' @return \code{data.table} of zonal indices and values. -#' -#' @import data.table -#' -#' @aliases fs_settled -#' @rdname fs_settled -#' -#' @export -fs_settled <- function(X, index=NULL, col=NULL) UseMethod("fs_settled") - - -#' @name fs_settled -#' @export -fs_settled.sp <- function(X, index=NULL, col=NULL){ - X <- sf::st_as_sf(X) - - result <- fs_settled(X, index, col) - return(result) -} - - -#' @name fs_settled -#' @export -fs_settled.sf <- function(X, index=NULL, col=NULL){ - if(!is.null(col)){ - if(!col %in% names(X)){ - message("Error: column name not found.") - stop() - } else{ - names(X)[which(names(X)==col)] <- "fs_settled" - result <- fs_settled_calc(X, index) - } - } else{ - X[["fs_settled"]] <- 1 - result <- fs_settled_calc(X, index) - } - return(result) -} - - -fs_settled_calc <- function(X, index){ - if(!"fs_settled" %in% names(X)){ - X[["fs_settled"]] <- 1 - } - - if(is.null(index)){ - warning("No index found, treating as one group.") - index <- rep(1, nrow(X)) - } else{ - if(length(index)==1){ - if((is.numeric(index) & index <= ncol(X)) | - (is.character(index) & index %in% names(X))){ - index <- X[[index]] - } - } else if(length(index) != nrow(X)){ - message("Invalid index") - stop() - } - } - - colNam <- "fs_settled" # changed name from _binary - DT <- data.table::data.table(index=index, - settled=X[["fs_settled"]]) - data.table::setkey(DT, index) - result <- DT[, setNames(.(max(settled)), colNam), by=index] - - return(result) -} diff --git a/R/fs_settled_count.R b/R/fs_settled_count.R index dba88e4..acb7231 100644 --- a/R/fs_settled_count.R +++ b/R/fs_settled_count.R @@ -2,9 +2,21 @@ #' #' @description Calculate and summarise selected metrics of building #' footprints within zones. -#' @inheritParams fs_area_mean +#' @param X Spatial object with building footprint polygons +#' @param index A string identifying a column within \code{X} which provides a +#' zonal index for summarising values. Alternatively a vector of indices can +#' be provided. If omitted, all observations in \code{X} are assumed to be +#' within one zone. +#' @param unit character or \code{units} object to define area. Default is +#' \code{NULL} which will use the units of the spatial reference system +#' @param col column name within \code{X} with pre-calculated area measures #' @return \code{data.table} of zonal indices and values. #' +#' @details Note that this function is provided as a standalone calculation for +#' convenience. The same summary measure can be executed within +#' \code{calculate_footstats} by specifying \code{what='settled'} and +#' \code{how='count'}. +#' #' @import data.table #' #' @aliases fs_count @@ -48,26 +60,38 @@ fs_count_calc <- function(X, index){ X[["fs_count"]] <- 1 } + indexCol <- "index" # default + if(is.null(index)){ - warning("No index found, treating as one group.") + message("No index found, treating as one group.") index <- rep(1, nrow(X)) } else{ - if(length(index)==1){ - if((is.numeric(index) & index <= ncol(X)) | - (is.character(index) & index %in% names(X))){ - index <- X[[index]] + if(is.character(index)){ + if(length(index)==1){ + if(nrow(X)>1){ # it must be a column name + if(!index %in% colnames(X)){ + stop("Index column not found in footprints.") + } else{ + indexCol <- index + index <- X[[indexCol]] + } + } # potential issue if 1 row X and 1 column name - won't affect calcs + } else if(length(index != nrow(X))){ + stop("Invalid length of zonal index.") + } + } else if(is.numeric(index)){ + if(length(index) != nrow(X)){ + stop("Invalid length of zonal index.") } - } else if(length(index) != nrow(X)){ - message("Invalid index") - stop() } } colNam <- "fs_count" - DT <- data.table::data.table(index=index, - settled=X[["fs_count"]]) - data.table::setkey(DT, index) - result <- DT[, setNames(.(sum(settled)), colNam), by=index] # can provide a weighted count if Col - + DT <- data.table::data.table(idxCol=index, + area_calc=X[["fs_count"]]) + data.table::setnames(DT, "idxCol", indexCol) + data.table::setkeyv(DT, indexCol) + result <- DT[, setNames(.(sum(area_calc)), colNam), by=indexCol] + return(result) } diff --git a/R/fs_shape_mean.R b/R/fs_shape_mean.R deleted file mode 100644 index cc999b4..0000000 --- a/R/fs_shape_mean.R +++ /dev/null @@ -1,105 +0,0 @@ -#' Building shape measure -#' -#' @description Calculate and summarise selected metrics of building footprint -#' polygons within zones. -#' @inheritParams fs_area_mean -#' @return \code{data.table} of zonal indices and values -#' -#' @details The shape index is calculated as the ratio of footprint polygon's -#' area to the area of minimum bounding circle. The mbc is calculated using -#' \code{fs_mbc} and \code{lwgeom}. Values closer 1 suggest more rounded -#' shapes. -#' -#' The function first looks for pre-calculated values of area in a field called -#' \code{fs_area}. If not present, the areas are calculated in m^2. -#' -#' @import data.table -#' -#' @aliases fs_shape_mean -#' @rdname fs_shape_mean -#' -#' @export -fs_shape_mean <- function(X, - index=NULL, - unit=NULL, - col=NULL) UseMethod("fs_shape_mean") - - -#' @name fs_shape_mean -#' @export -fs_shape_mean.sp <- function(X, index=NULL, unit=NULL, col=NULL){ - X <- sf::st_as_sf(X) - - result <- fs_shape_mean(X, index, unit, col) - return(result) -} - - -#' @name fs_shape_mean -#' @export -fs_shape_mean.sf <- function(X, index=NULL, unit=NULL, col=NULL){ - if(!is.null(col)){ - # warning("Ignoring supplied column.") - if(!col %in% names(X)){ - message("Error: column name not found.") - stop() - } else{ - names(X)[which(names(X)==col)] <- "fs_shape" - result <- fs_shape_mean_calc(X, index, unit) - } - } else{ # column not supplied - if(any(!sf::st_geometry_type(X) %in% c("POLYGON", "MULTIPOLYGON") )){ - message("Area requires polygon shapes.") - stop() - } - - if(is.na(sf::st_crs(X))){ - warning("Polygons have no spatial projection. Units ignored.") - unit <- NULL - } else{ - if(is.null(unit)){ - unit <- "m^2" - } - } - - X[["fs_shape"]] <- fs_shape(X) - result <- fs_shape_mean_calc(X, index, unit) - } - return(result) -} - - -fs_shape_mean_calc <- function(X, index, unit=NULL){ - if(!"fs_shape" %in% names(X)){ - if(!"fs_area" %in% names(X)){ - X[["fs_area"]] <- fs_area(X, "m^2") - } else{ - units(X[["fs_area"]]) <- "m^2" - } - - X[["fs_shape"]] <- fs_shape(X) - } - - if(is.null(index)){ - warning("No index found, treating as one group.") - index <- rep(1, nrow(X)) - } else{ - if(length(index)==1){ - if((is.numeric(index) & index <= ncol(X)) | - (is.character(index) & index %in% names(X))){ - index <- X[[index]] - } - } else if(length(index) != nrow(X)){ - message("Invalid index") - stop() - } - } - - colNam <- "fs_shape_mean" - DT <- data.table::data.table(index=index, - area_calc=X[["fs_shape"]]) - data.table::setkey(DT, index) - result <- DT[, setNames(.(mean(area_calc)), colNam), by=index] - - return(result) -} diff --git a/R/fs_shape_median.R b/R/fs_shape_median.R deleted file mode 100644 index bfdd6e1..0000000 --- a/R/fs_shape_median.R +++ /dev/null @@ -1,105 +0,0 @@ -#' Building shape measure -#' -#' @description Calculate and summarise selected metrics of building footprint -#' polygons within zones. -#' @inheritParams fs_area_mean -#' @return \code{data.table} of zonal indices and values -#' -#' @details The shape index is calculated as the ratio of footprint polygon's -#' area to the area of minimum bounding circle. The mbc is calculated using -#' \code{fs_mbc} and \code{lwgeom}. Values closer 1 suggest more rounded -#' shapes. -#' -#' The function first looks for pre-calculated values of area in a field called -#' \code{fs_area}. If not present, the areas are calculated in m^2. -#' -#' @import data.table -#' -#' @aliases fs_shape_median -#' @rdname fs_shape_median -#' -#' @export -fs_shape_median <- function(X, - index=NULL, - unit=NULL, - col=NULL) UseMethod("fs_shape_median") - - -#' @name fs_shape_median -#' @export -fs_shape_median.sp <- function(X, index=NULL, unit=NULL, col=NULL){ - X <- sf::st_as_sf(X) - - result <- fs_shape_median(X, index, unit, col) - return(result) -} - - -#' @name fs_shape_median -#' @export -fs_shape_median.sf <- function(X, index=NULL, unit=NULL, col=NULL){ - if(!is.null(col)){ - # warning("Ignoring supplied column.") - if(!col %in% names(X)){ - message("Error: column name not found.") - stop() - } else{ - names(X)[which(names(X)==col)] <- "fs_shape" - result <- fs_shape_median_calc(X, index, unit) - } - } else{ # column not supplied - if(any(!sf::st_geometry_type(X) %in% c("POLYGON", "MULTIPOLYGON") )){ - message("Area requires polygon shapes.") - stop() - } - - if(is.na(sf::st_crs(X))){ - warning("Polygons have no spatial projection. Units ignored.") - unit <- NULL - } else{ - if(is.null(unit)){ - unit <- "m^2" - } - } - - X[["fs_shape"]] <- fs_shape(X) - result <- fs_shape_median_calc(X, index, unit) - } - return(result) -} - - -fs_shape_median_calc <- function(X, index, unit=NULL){ - if(!"fs_shape" %in% names(X)){ - if(!"fs_area" %in% names(X)){ - X[["fs_area"]] <- fs_area(X, "m^2") - } else{ - units(X[["fs_area"]]) <- "m^2" - } - - X[["fs_shape"]] <- fs_shape(X) - } - - if(is.null(index)){ - warning("No index found, treating as one group.") - index <- rep(1, nrow(X)) - } else{ - if(length(index)==1){ - if((is.numeric(index) & index <= ncol(X)) | - (is.character(index) & index %in% names(X))){ - index <- X[[index]] - } - } else if(length(index) != nrow(X)){ - message("Invalid index") - stop() - } - } - - colNam <- "fs_shape_median" - DT <- data.table::data.table(index=index, - area_calc=X[["fs_shape"]]) - data.table::setkey(DT, index) - result <- DT[, setNames(.(median(area_calc)), colNam), by=index] - - return(result) -} diff --git a/R/get_adjacent.R b/R/get_adjacent.R deleted file mode 100644 index 7560a0f..0000000 --- a/R/get_adjacent.R +++ /dev/null @@ -1,130 +0,0 @@ -#' Raster adjacencies -#' -#' @description Find adjacent grid cells in a regular raster dataset. -#' -#' @param r A gridded, raster object on which to search. -#' @param cells A vector of cell numbers in \code{r} to search for adjacencies. -#' @param directions The dimension to define neighbouring cells by contiguity. -#' Accepted options include: 8, 4, "queen", "bishop", "rook" or a matrix. See -#' details. Default is 8 which is a queen's contiguity. -#' @param include logical. Should the cell number of interest should be considered -#' adjacent to itself? Dfault is \code{FALSE}. -#' @param dataTable logical. Should the processing and return value use \code{data.table}? -#' @return A table with cells numbers ("from") and their adjacent ("to"). -#' -#' @details The \code{directions} parameter defines neighbouring cells. This parameter -#' follows typical contiguity measures for lattice data (e.g. "queen" or "rook"), or -#' more complex adjacencies can be found by supplying a matrix. To use a matrix to define -#' adjacencies, the center value should be set to zero, all adjacent cells should be 1 and -#' any cells to ignore should be set to \code{NA}. -#' -#' @seealso \code{\link{make_circular_filter}} -#' -#' @import data.table -#' @aliases adjacentCells -#' @rdname adjacentCells -#' - - -#' @name adjacentCells -#' @export -adjacentCells <- function(r, cells, directions=8, include=FALSE, dataTable=FALSE){ - if(missing(r) | missing(cells)){ - stop("Must provide a raster and cell numbers.") - } - - if(is.matrix(directions)){ - w <- directions - - } else if(directions==8 | directions=="queen"){ - w <- matrix(c(1,1,1, 1,0,1, 1,1,1), - nrow=3, ncol=3) - - } else if(directions==4 | directions=="rook"){ - w <- matrix(c(NA, 1, NA, 1, 0, 1, NA, 1, NA), - nrow=3, ncol=3) - - } else if(directions=="bishop"){ - w <- matrix(c(1, NA, 1, NA, 0, NA, 1, NA, 1), - nrow=3, ncol=3) - - }else{ - stop("Invalid direction for adjacencies.") - } - - ctr <- which(w==0, arr.ind=T) - - cNgb <- rep(1:ncol(w), each=nrow(w)) - ctr[,2] - rNgb <- rep(nrow(w):1, ncol(w)) - (nrow(w)-ctr[,1]+1) - - cNgb <- cNgb * w - rNgb <- rNgb * w - - # if no self-matching - if(!include){ - cNgb[which(w != 1)] <- NA - rNgb[which(w != 1)] <- NA - } - - cNgb <- stats::na.omit(as.vector(cNgb)) - rNgb <- stats::na.omit(as.vector(rNgb)) - - # construct neighbouring cell indices - numNgb <- length(cNgb) - - if(numNgb*length(cells) > 1e7 | dataTable==TRUE){ - adj <- data.table::data.table(fromCid=rep(cells, each=numNgb)) - adj[, fromR:=trunc((fromCid-1) / ncol(r)) + 1] - adj[, fromC:=as.integer(fromCid - ((fromR-1) * ncol(r)))] - - adj[, toC:=fromC + cNgb] - adj[, toR:=fromR + rNgb] - adj[, toCid:=toC + ((toR-1) * ncol(r))] - adj[, toCid:=ifelse( (toC<1 | toC>ncol(r)) | (toR<1 | toR>nrow(r)), NA, toCid )] - - adj[, c("toC","toR","fromR","fromC"):=NULL] # remove columns in place - adj <- na.omit(adj) - - # # alternate for REALLY big lists of cells. - # adj <- data.table::data.table(fromCid=cells) - # adj[, fromR:=trunc((fromCid-1) / ncol(r)) + 1] - # adj[, fromC:=as.integer(fromCid - ((fromR-1) * ncol(r)))] - # - # for(i in 1:numNgb){ - # cN <- cNgb[i] - # rN <- rNgb[i] - # - # adj[, toC:=fromC + cN] - # adj[, toR:=fromR + rN] - # - # adj[, toCid:=toC + ((toR-1) * ncol(r))] - # - # # set(adj, (toC<1 | toC>ncol(r)) | (toR<1 | toR>nrow(r)), toCid, NA) - # adj[, toCid:=ifelse( (toC<1 | toC>ncol(r)) | (toR<1 | toR>nrow(r)), NA, toCid )] - # adj[, toR:=ifelse( toR<1 | toR>nrow(r), NA, toR )] - # - # adj[, c("toC","toR","toCid"):=NULL] # remove columns in place - # gc() - # } - - } else{ # matrix - fromCid <- rep(cells, each=numNgb) - fromR <- trunc((fromCid-1) / ncol(r)) + 1 - fromC <- as.integer(fromCid - ((fromR-1) * ncol(r))) - - toC <- fromC + cNgb - toR <- fromR + rNgb - - toCid <- toC + ((toR-1) * ncol(r)) - toCid[(toC<1 | toC>ncol(r)) | (toR<1 | toR>nrow(r))] <- NA - - adj <- cbind(fromCid, toCid) - adj <- adj[!is.na(adj[,2]),] - } - - return(adj) -} - - - - diff --git a/R/get_distance.R b/R/get_distance.R index ec860bb..93ebfe5 100644 --- a/R/get_distance.R +++ b/R/get_distance.R @@ -16,39 +16,76 @@ suggestUTMzone <- function(pt){ #' @title Nearest neighbour distance calculation #' #' @description Helper function to provide a distance calculation between -#' spatial ojects. The distance to the first nearest neighbour found -#' (optionally within a maximum search radisu) is returned. +#' spatial objects. The distance to the first nearest neighbour found +#' (optionally within a maximum search radius) is returned. #' #' @param X Spatial object of \code{sf} type, typically polygons or points. #' @param Y (Optional) Spatial object to measure distances to. -#' @param maxSearch Maximum search radius around \code{X} to search. Distance in -#' meters. Default is 100. +#' @param maxSearch Maximum radius around \code{X} to search. Distance in +#' meters. Default is 100. To ignore the search limit, set to `NULL`. +#' @param method Either \code{'poly'} or \code{'centroid'} to assign a geometry +#' method. See details. Default is 'poly'. #' @param unit Character abbreviation for the units to return from the distance #' calculation. -#' + #' @details If \code{Y} is omitted the nearest neighbour distances are found #' within \code{X}. Otherwise, the distance for each object in \code{X} to its -#' nearest neighbour in \code{Y} is returned. Providing a maximum search -#' radius is strongly advised to speed up the calcluation. +#' nearest neighbour in \code{Y} is returned. +#' +#' Use \code{method} to adjust which geometry of \code{X} and \code{Y} is used +#' for distance calculations. When \code{method='poly'} distances are measured +#' between polygon edges. When \code{method='centroid'}, the centroids of +#' building footprints are used instead. Centroid-based distance calculations +#' are faster. +#' +#' Providing a maximum search radius is strongly advised to speed up the +#' calculation. +#' +#' @examples +#' data("kampala", package="foot") +#' +#' # get sample of buildings +#' buildings <- kampala$buildings +#' buildings <- buildings[sample(1:nrow(buildings), size=100, replace=F),] +#' clusters <- kampala$clusters +#' +#' # calculate distance between buildings in m +#' fs_nndist(buildings, unit="m") +#' +#' # calculate unrestricted distance +#' # between buildings and another set of points +#' fs_nndist(buildings, sf::st_centroid(clusters), maxSearch=NULL) +#' +#' # use footprint centroids +#' fs_nndist(buildings, method='centroid', unit='m') #' #' @name fs_nndist #' @export -fs_nndist <- function(X, Y, maxSearch=100, unit="m"){ +fs_nndist <- function(X, Y, maxSearch=100, method='poly', unit="m"){ + if(missing(X)) stop("Must provide building footrpints") # row index uid <- 1:nrow(X) + if(method[1]=='centroid'){ + suppressWarnings(X <- sf::st_centroid(X)) + } + if(missing(Y)){ searchObj <- X } else{ searchObj <- Y + + if(method[1]=='centroid'){ + suppressWarnins(searchObj <- sf::st_centroid(searchObj)) + } } if(sf::st_crs(X) != sf::st_crs(searchObj)){ - stop("Coordinate reference systems do not match for zone indexing.") + stop("Coordinate reference systems do not match in distance calculation.") } # initial search of intersecting objects (distance = 0) - intersects1 <- sf::st_intersects(X, searchObj) + intersects1 <- suppressMessages(sf::st_intersects(X, searchObj)) DT <- data.table::data.table(uid=uid, intersects=lapply(intersects1, function(i) uid[i])) @@ -58,7 +95,10 @@ fs_nndist <- function(X, Y, maxSearch=100, unit="m"){ # buffer search if(!is.null(maxSearch)){ if(sf::st_is_longlat(searchObj)){ - zn <- suggestUTMzone(sf::st_coordinates(sf::st_centroid(sf::st_as_sfc(sf::st_bbox(X))))) + zn <- suggestUTMzone( + suppressWarnings(sf::st_coordinates( + sf::st_centroid(sf::st_as_sfc(sf::st_bbox(X))))) + ) searchBuffer <- sf::st_transform(searchObj, zn) searchBuffer <- sf::st_buffer(searchBuffer, maxSearch) @@ -67,12 +107,11 @@ fs_nndist <- function(X, Y, maxSearch=100, unit="m"){ searchBuffer <- sf::st_buffer(searchObj, maxSearch) } # get objects within search buffer - intersects2 <- sf::st_intersects(X, searchBuffer) + intersects2 <- suppressMessages(sf::st_intersects(X, searchBuffer)) # restricted distance calculation DT[, intersects := lapply(intersects2, function(i) i)] DT[is.na(dist), - # dist := sort(sf::st_distance(X$geometry[uid], searchObj$geometry[unlist(intersects)]))[2], #, tolerance=1 dist := sort(sf::st_distance(sf::st_geometry(X)[uid], sf::st_geometry(searchObj)[unlist(intersects)]))[2], #, tolerance=1 by = uid] diff --git a/R/get_geometry.R b/R/get_geometry.R index d005c6a..65557cd 100644 --- a/R/get_geometry.R +++ b/R/get_geometry.R @@ -71,13 +71,11 @@ fs_compact <- function(X){ if(!inherits(X, 'sf')){ X <- sf::st_as_sf(X) } - - if(!"fs_area" %in% names(X)){ - if(any(!sf::st_geometry_type(X) %in% c("POLYGON", "MULTIPOLYGON"))){ + + if(any(!sf::st_geometry_type(X) %in% c("POLYGON", "MULTIPOLYGON"))){ stop("Area requires polygons") - } else{ + } else{ X[["fs_area"]] <- fs_area(X, unit="m^2") - } } if(!"fs_perimeter" %in% names(X)){ @@ -99,6 +97,8 @@ fs_compact <- function(X){ #' #' @description Calculates a measure of shape or "roundness". #' @param X polygons of building footprints of type \code{sf}. +#' @param unit string indicating unit of measure. Passed to +#' \code{units::set_units}. #' @return numeric vector with shape index values ranging from 0 to 1 or each #' item in \code{X}. #' @@ -110,9 +110,18 @@ fs_compact <- function(X){ #' The function first looks for pre-calculated values of area in a field called #' \code{fs_area}. If not present, the areas are calculated in m^2. #' +#' @examples +#' data("kampala", package="foot") +#' buildings = kampala$buildings +#' +#' fs_shape(buildings) +#' +#' # how the calculation is done - first observation +#' fs_area(buildings[1,]) / fs_area(fs_mbc(buildings[1,])) +#' #' @name fs_shape #' @export -fs_shape <- function(X){ +fs_shape <- function(X, unit=NULL){ if(!inherits(X, "sf")){ X <- sf::st_as_sf(X) } @@ -121,17 +130,21 @@ fs_shape <- function(X){ stop("Shape index requires polygons") } + if(is.null(unit)){ + unit <- "m^2" + } + if(!"fs_area" %in% names(X)){ - X[["fs_area"]] <- fs_area(X, unit="m^2") + X[["fs_area"]] <- fs_area(X, unit=unit) } else{ - units(X$fs_area) <- units::as_units("m^2") + units(X$fs_area) <- units::as_units(unit) } DT <- data.table::data.table(sf::st_drop_geometry(X), geom=sf::st_geometry(X)) DT[, mbc := fs_mbc(geom)] - DT[, mbcA := fs_area(mbc, unit="m^2")] + DT[, mbcA := fs_area(mbc, unit=unit)] # calculate index DT[, shapeIdx := fs_area / mbcA] units(DT$shapeIdx) <- NULL @@ -148,7 +161,7 @@ fs_shape <- function(X){ #' #' @param X polygons of building footprints in type \code{sf}. #' @param returnShape logical. Should the function return the \code{sf} polygon -#' of the rotated bounding rectange or should it retun the angle (in degrees). +#' of the rotated bounding rectangle or should it return the angle (in degrees). #' @return a numeric angle from 0 to 360 degrees or the rotated rectangle as a #' polygon of type \code{sf}. #' @details This function is currently not vectorized and processing is limited @@ -201,40 +214,6 @@ fs_mbr <- function(X, returnShape=FALSE){ } else{ return(unlist(resList)) } - - # if(sf::st_geometry_type(X) %in% c("POLYGON", "MULTIPOLYGON", "POINT", "MULTIPOINT")){ - # p <- sf::st_coordinates(X)[,1:2] - # } else if(class(X) != "Matrix"){ - # stop("Invalid coordinates.") - # } - # - # # Analyze the convex hull edges - # a <- chull(p) # Indexes of extremal points - # a <- c(a, a[1]) # Close the loop - # e <- p[a[-1],] - p[a[-length(a)], ] # Edge directions - # norms <- sqrt(rowSums(e^2)) # Edge lengths - # v <- e / norms # Unit edge directions - # w <- cbind(-v[,2], v[,1]) # Normal directions to the edges - # - # # Find the MBR - # vertices <- p[a, ] # Convex hull vertices - # x <- apply(vertices %*% t(v), 2, range) # Extremes along edges - # y <- apply(vertices %*% t(w), 2, range) # Extremes normal to edges - # areas <- (y[1,]-y[2,])*(x[1,]-x[2,]) # Areas - # k <- which.min(areas) # Index of the best edge (smallest area) - # - # # Form a rectangle from the extremes of the best edge - # R <- rbind(v[k,], w[k,]) - # # print((atan2(R[2,1], R[1,1]) * 180/pi) %% 360) - # mbr <- cbind(x[c(1,2,2,1,1),k], y[c(1,1,2,2,1),k]) %*% R - # - # if(returnShape){ - # return(sf::st_polygon(list(mbr))) - # - # } else{ - # angle <- (atan2(R[2,1], R[1,1]) * 180/pi) %% 360 - # return(angle) - # } } diff --git a/R/get_tile.R b/R/get_tile.R index 38b2a5e..f987459 100644 --- a/R/get_tile.R +++ b/R/get_tile.R @@ -1,6 +1,5 @@ #' gridTiles #' -#' @title gridTiles #' @description Split gridded data extents into subsets for parallel processing. #' @param X The gridded (\code{raster} or \code{stars}) object to find tiles #' @param n number of tiles. Can be a vector length 1 or of length 2 (rows, @@ -10,7 +9,7 @@ #' @param overlap number of pixels of overlap between internal tiles #' #' @details \code{gridTiles} provides a convenient way for splitting a gridded -#' dataset into subdatasets for cropping or processing individually. The +#' dataset into sub-datasets for cropping or processing individually. The #' splitting uses the extent of the total grid, potentially including noData #' cells. #' @@ -23,6 +22,15 @@ #' \item cropXsize number of columns in the tile #' \item cropYsize number of rows in the tile #' } +#' +#' @examples +#' data("kampala") +#' # example dataset +#' g <- kampala$mastergrid +#' +#' gridTiles(g) +#' gridTiles(g, n=2) +#' gridTiles(g, px=c(5, 25)) #' #' @aliases gridTiles #' @rdname gridTiles @@ -38,7 +46,7 @@ gridTiles.stars <- function(X, n=NULL, px=c(1000, 1000), overlap=0){ if(!is.null(n)){ if(length(n) == 1){ pxX <- pxY <- max(ceiling(stars::st_dimensions(X)$y$to / n), - ceiling(tars::st_dimensions(X)$x$to / n)) + ceiling(stars::st_dimensions(X)$x$to / n)) } else{ pxY <- ceiling(stars::st_dimensions(X)$y$to / n[1]) pxX <- ceiling(stars::st_dimensions(X)$x$to / n[2]) diff --git a/R/get_zonal_index.R b/R/get_zonal_index.R index ccd6fab..f63d5eb 100644 --- a/R/get_zonal_index.R +++ b/R/get_zonal_index.R @@ -6,14 +6,45 @@ #' @param X Spatial data (or path to file) with building footprint polygons #' @param zone Spatial data (or path to file) with polygon zones or a spatial #' grid ("raster") -#' @param zoneField (Optional) Unique identifier for each zone +#' @param zoneField (Optional) Column name of unique identifiers in \code{zone} +#' to use. If omitted, the 'zoneID' will be numbered \code{1:nrow(zone)}. +#' @param method One of \code{'centroid', 'intersect', 'clip'} to determine how +#' footprints are allocated to zones. See details. Default is +#' \code{"centroid"}. #' @param returnObject Logical of whether to return an sf object of X with zonal -#' information. Default \code{TRUE}. -#' @param clip Logical of whether polygons of X which span multiple zones should -#' be clipped to the zone. If not clipped, then whole building footprints are -#' linked to each zone. Default \code{FALSE} +#' information. Default is \code{TRUE} which is generally preferred. #' @return 'sf' object with attributes of \code{X} plus the unique zone ID or a -#' \code{data.table} with the ID to the records in \code{X} and the zone IDs. +#' \code{data.table} with the row number to the record in \code{X} matched to +#' the zone IDs. +#' +#' @details Zone assignments for building footprints can be done using three +#' possible methods, set by the \code{method=} parameter. Defining by +#' 'centroid' allocates and entire building and its characteristics to the +#' zone(s) which its centroid intersects. Centroids are defined by +#' \code{\link[sf]{st_centroid}} which could be outside the polygon shape. +#' This is the default mode. Defining zones by 'intersect' uses the geometric +#' binary predicate from \code{\link[sf]{st_intersects}}. This method will +#' include a whole building and its characteristics into all zones that it +#' intersects. Therefore a building could appear to be "counted" twice. The +#' final approach, 'clip', uses \code{\link[sf]{st_intersection}} to split +#' footprints so that only that area of the polygon intersecting the zone is +#' include. This method is more time consuming because the geometries are +#' modified. +#' +#' @examples +#' data("kampala", package="foot") +#' +#' buildings <- kampala$buildings +#' clusters <- kampala$clusters +#' +#' # assign zones and return a new 'sf' object +#' zonalIndex(buildings, clusters) +#' +#' # assign all intersecting zones +#' zonalIndex(buildings, clusters, method="intersect") +#' +#' # return only a table of indices - note column names +#' zonalIndex(buildings, clusters, zoneField="Id", returnObject=FALSE) #' #' @import data.table #' @importFrom purrr map2 @@ -24,14 +55,15 @@ zonalIndex <- function(X, zone, zoneField=NULL, - returnObject=TRUE, - clip=FALSE) UseMethod("zonalIndex", zone) + method='centroid', + returnObject=TRUE) UseMethod("zonalIndex", zone) #' @name zonalIndex #' @export zonalIndex.sf <- function(X, zone, - zoneField=NULL, returnObject=TRUE, clip=FALSE){ + zoneField=NULL, method='centroid', + returnObject=TRUE){ if(any(class(X) == "sp") | any(class(X) == "stars")){ # convert to sf X <- sf::st_as_sf(X) @@ -39,12 +71,20 @@ zonalIndex.sf <- function(X, zone, stop("Object format not valid.") } - if(clip==TRUE & returnObject==FALSE){ - clip <- FALSE + if(is.list(method) & length(method) > 1){ + message("Using the first element of argument 'method'") + method <- method[[1]] + } + + if(!method %in% c("centroid","intersect","clip")){ + stop("Method must be one of 'centroid', 'intersect', or 'clip'") + } + + if(method=="clip" & returnObject==FALSE){ warning("Clipping only valid when returning a spatial object.") } - result <- get_zonal_index(X, zone, zoneField, returnObject, clip) + result <- get_zonal_index(X, zone, zoneField, method, returnObject) return(result) } @@ -52,7 +92,8 @@ zonalIndex.sf <- function(X, zone, #' @name zonalIndex #' @export zonalIndex.sfc <- function(X, zone, - zoneField=NULL, returnObject=TRUE, clip=FALSE){ + zoneField=NULL, method='centroid', + returnObject=TRUE){ if(any(class(X) == "sp") | any(class(X) == "stars")){ # convert to sf X <- sf::st_as_sf(X) @@ -60,12 +101,20 @@ zonalIndex.sfc <- function(X, zone, stop("Object format not valid.") } - if(clip==TRUE & returnObject==FALSE){ - clip <- FALSE + if(is.list(method) & length(method) > 1){ + message("Using the first element of argument 'method'") + method <- method[[1]] + } + + if(!method %in% c("centroid","intersect","clip")){ + stop("Method must be one of 'centroid', 'intersect', or 'clip'") + } + + if(method=="clip" & returnObject==FALSE){ warning("Clipping only valid when returning a spatial object.") } - result <- get_zonal_index(X, zone, zoneField, returnObject, clip) + result <- get_zonal_index(X, zone, zoneField, method, returnObject) return(result) } @@ -73,9 +122,10 @@ zonalIndex.sfc <- function(X, zone, #' @name zonalIndex #' @export zonalIndex.sp <- function(X, zone, - zoneField=NULL, returnObject=TRUE, clip=FALSE){ + zoneField=NULL, method='centroid', + returnObject=TRUE){ zone <- sf::st_as_sf(zone) - result <- zonalIndex(X, zone, zoneField, returnObject, clip) + result <- zonalIndex(X, zone, zoneField, method, returnObject) return(result) } @@ -83,9 +133,10 @@ zonalIndex.sp <- function(X, zone, #' @name zonalIndex #' @export zonalIndex.stars <- function(X, zone, - zoneField=NULL, returnObject=TRUE, clip=FALSE){ + zoneField=NULL, method='centroid', + returnObject=TRUE){ zone <- sf::st_as_sf(zone) - result <- zonalIndex(X, zone, zoneField, returnObject, clip) + result <- zonalIndex(X, zone, zoneField, method, returnObject) return(result) } @@ -93,9 +144,10 @@ zonalIndex.stars <- function(X, zone, #' @name zonalIndex #' @export zonalIndex.raster <- function(X, zone, - zoneField=NULL, returnObject=TRUE, clip=FALSE){ + zoneField=NULL, method='centroid', + returnObject=TRUE){ zone <- stars::st_as_stars(zone) - result <- zonalIndex(X, zone, zoneField, returnObject, clip) + result <- zonalIndex(X, zone, zoneField, method, returnObject) return(result) } @@ -103,15 +155,19 @@ zonalIndex.raster <- function(X, zone, #' @name zonalIndex #' @export zonalIndex.character <- function(X, zone, - zoneField=NULL, returnObject=TRUE, clip=FALSE){ + zoneField=NULL, method='centroid', + returnObject=TRUE){ zone <- sf::st_read(zone) - result <- zonalIndex(X, zone, zoneField, returnObject, clip) + result <- zonalIndex(X, zone, zoneField, method, returnObject) return(result) } get_zonal_index <- function(X, zone, - zoneField=NULL, returnObject=TRUE, clip=FALSE){ + zoneField=NULL, method='centroid', + returnObject=TRUE){ + # clean up + on.exit({ rm(list=ls()); gc() }) if(missing(X)){ stop("Missing footprint dataset") } else if (all(!class(X) %in% c("sf","sfc"))){ @@ -140,36 +196,56 @@ get_zonal_index <- function(X, zone, } else if(!zoneField %in% names(zone)){ stop("Field identifying zones not found") } + + if(method=='centroid'){ + # keep backup + polyGeo <- sf::st_geometry(X) + suppressWarnings(X <- sf::st_centroid(X)) + X$polyGeo <- polyGeo + } # intersects - binary predicate - ints <- sf::st_intersects(zone, X) + suppressMessages(ints <- sf::st_intersects(zone, X)) hits <- which(lengths(ints)>0) if(length(hits) > 0){ - geomDT <- data.table::data.table(xID=ints[hits]) - geomDT[, (zoneField) := hits] + geomDT <- list(xID=ints[hits]) + data.table::setDT(geomDT) + # geomDT <- data.table::data.table(xID=ints[hits]) + # geomDT[, (zoneField) := hits] + geomDT[, zID := hits] + data.table::setkey(geomDT, zID) + # get zone ID values + zDT <- data.table::data.table(zone) + zDT[, zID := 1:.N] + data.table::setkey(zDT, zID) + geomDT[zDT, (zoneField) := mget(zoneField)] + # expand list of intersected features intDT <- geomDT[, setNames(c(xID), "xID"), by=zoneField] data.table::setcolorder(intDT, c("xID", zoneField)) + data.table::setkey(intDT, xID) if(returnObject){ # join Xdt <- data.table::data.table(X) Xdt[, xID := 1:.N] + data.table::setkey(Xdt, xID) + intDT[Xdt, on='xID', colnames(X) := mget(colnames(X))] # drop xID col intDT[, xID := NULL] data.table::setcolorder(intDT, neworder=colnames(X)) - if(clip){ + if(method=='clip'){ xGeo <- attr(X, "sf_column") # get variable name # match up geometries intDT[, zGeom := sf::st_geometry(zone)[get(zoneField)]] # loop over - clipGeom <- purrr::map2(intDT[[xGeo]], + suppressMessages(clipGeom <- purrr::map2(intDT[[xGeo]], intDT[["zGeom"]], - sf::st_intersection) + sf::st_intersection)) # update geometry field intDT[, (xGeo) := sf::st_sfc(clipGeom, crs=sf::st_crs(X))] @@ -177,6 +253,12 @@ get_zonal_index <- function(X, zone, result <- sf::st_as_sf(intDT[, c(colnames(X), zoneField), with=F]) } else{ result <- sf::st_as_sf(intDT) + + if(method=='centroid'){ + # sf::st_geometry(result) <- polyGeo + sf::st_geometry(result) <- result$polyGeo + result <- result[,!names(result) %in% "polyGeo"] + } } } else{ @@ -184,81 +266,7 @@ get_zonal_index <- function(X, zone, } } else{ # no intersections result <- NULL + warning("No intersections found between footprints and zones.") } return(result) } - - - -## ARCHIVE VERSION -# get_zonal_index <- function(X, zone, zoneField=NULL, returnObject=TRUE, clip=FALSE){ -# if(missing(X)){ -# stop("Missing footprint dataset") -# } else if (all(!class(X) %in% c("sf","sfc"))){ -# X <- sf::st_as_sf(X) -# } -# -# if(is.na(sf::st_crs(X)) | is.na(sf::st_crs(zone))){ -# stop("Missing CRS.") -# } -# -# if(sf::st_crs(X) != sf::st_crs(zone)){ -# stop("Coordinate systems do not match") -# } -# -# if(any(sf::st_geometry_type(X) == "MULTIPOLYGON")){ -# X <- sf::st_cast(X, "POLYGON") -# } -# -# if(any(sf::st_geometry_type(zone) == "MULTIPOLYGON")){ -# zone <- sf::st_cast(sf::st_cast(sf::st_make_valid(zone), "MULTIPOLYGON"), "POLYGON") -# } -# -# if(is.null(zoneField)){ -# zone[["zoneID"]] <- 1:length(sf::st_geometry(zone)) -# zoneField <- "zoneID" -# } else if(!zoneField %in% names(zone)){ -# stop("Field identifying zones not found") -# } -# -# i <- sf::st_intersects(zone, X, sparse=TRUE) -# hits <- which(lengths(i)>0) -# # subset the objects to limit search space -# i <- i[hits] -# zone <- zone[hits,] -# # get the name of the geometry column -# zoneGeo <- attr(zone, "sf_column") -# -# if(returnObject){ -# if(clip){ -# intList <- suppressMessages(suppressWarnings( lapply(seq(hits), # TO-DO move to parallel -# FUN=function(j){ -# ints <- sf::st_intersection(zone[j, c(zoneField, zoneGeo)], -# X[i[[j]],] ) -# if(any(sf::st_geometry_type(ints)=="MULTIPOLYGON")){ -# ints <- sf::st_cast( -# sf::st_cast( -# sf::st_make_valid(ints), "MULTIPOLYGON"), "POLYGON") -# } -# return(ints) -# }) )) -# DT <- data.table::rbindlist(intList) -# data.table::setkeyv(DT, zoneField) -# data.table::setcolorder(DT, neworder=c(names(X), zoneField)) -# result <- sf::st_as_sf(DT) -# rm(DT) -# } else{ -# DT <- data.table::data.table(X[unlist(i),], -# zoneID=rep(zone[[zoneField]], lengths(i))) -# data.table::setkey(DT, zoneID) -# result <- sf::st_as_sf(DT) -# rm(DT) -# } -# } else{ -# result <- data.table::data.table(xID=unlist(i), -# zoneID=rep(zone[[zoneField]], lengths(i))) -# data.table::setkey(result, zoneID) -# } -# -# return(result) -# } diff --git a/R/list_fs.R b/R/list_fs.R index 1d483ef..9b412ed 100644 --- a/R/list_fs.R +++ b/R/list_fs.R @@ -1,100 +1,85 @@ #' List footprint summary metrics #' -#' @description Helper functions to list footprint metric names and units -#' @param short_names Character vector of short names to lookup \code{fs_} -#' function names. -#' @param group Character vector to return a group of metrics (e.g. "area"). -#' @param col_names Character vector to select columns from the table of -#' metrics. -#' @param metrics Character vector of \code{fs_} function names to look up the -#' default units. +#' @description Helper function to list footprint characteristic names and +#' built-in summary functions. +#' @param what Character vector of names of characteristics to look up (e.g. +#' "area"). Alternatively, list \code{'all'} or all characteristics except +#' nearest neighbour distances (\code{'nodist'}). +#' @param how Character vector of summary functions to look up (e.g. "mean") or +#' \code{'all'} available metrics. #' -#' @details These convenience function access the internal data in -#' \code{foot::fs_footprint_metrics}. They provide easy look-up access for the -#' function names, the default units of measurement, or to return columns. -#' Arguments are optional and if omitted the full set of metrics will be -#' returned. -#' @return Vector or data.frame with selected footprint metric function names -#' and/or units. -#' @rdname get_fs_metrics +#' @details Provides an easy look-up for the built-in function names, and +#' basic geometric characteristics used by \code{foot}. Supplying a "what" +#' characteristics finds all built-in functions. Conversely, supplying a "how" +#' function name returns are characteristics for which that summary is +#' available. Arguments are optional +#' and if both are omitted the full set of metrics will be returned. +#' @return Vector or \code{data.frame} with selected footprint metric function +#' names and/or units. +#' +#' @examples +#' # get the full list of all available +#' list_fs() +#' +#' # get all summary functions for "area" +#' list_fs(what="area") +#' +#' # get all characteristics relevant for entropy +#' list_fs(how="entropy") +#' +#' @rdname list_fs #' @export -get_fs_metrics <- function(short_names, group=NULL){ - if(missing(short_names) & is.null(group)){ - return(list_fs("name")) - } +list_fs <- function(what='all', how='all'){ + if(is.null(what) & is.null(how)){ stop("Both argument cannot be NULL.")} - which_rows <- NULL - if(!missing(short_names)){ - metrics <- tolower(short_names) - # clean back to short names - metrics <- gsub("fs_", "", metrics, fixed=T) - metrics <- gsub("_calc", "", metrics, fixed=T) - - if("all" %in% metrics) { - return(list_fs("name")) - } - - if("nodist" %in% metrics){ - rows <- list_fs(c("name", "group")) - return(rows[rows$group != "dist", "name"]) - } - - allmetrics <- list_fs(c("name", "short_name", "group")) - which_rows <- allmetrics[which(allmetrics$short_name %in% metrics), "name"] - } + baseWhats <- c("area","perimeter") + baseFuns <- c("sum","mean","median","sd","min","max","cv") + shapeWhats <- c("shape","compact") + shapeFuns <- c("mean","median","sd","min","max","cv") + settWhats <- c("settled") + settFuns <- c("binary","count") + angleWhats <- c("angle") + angleFuns <- c("mean","median","sd","min","max","cv","entropy") + distWhats <- c("nndist") + distFuns <- c("mean","median","sd","min","max","cv","nnindex") - if(!is.null(group)){ - group <- tolower(group) - which_rows <- c(which_rows, - allmetrics[which(allmetrics$group %in% group), "name"]) - } + allMetrics <- c( + crossargs(baseWhats, baseFuns), + crossargs(shapeWhats, shapeFuns), + crossargs(settWhats, settFuns), + crossargs(angleWhats, angleFuns), + crossargs(distWhats, distFuns) + ) + allMetrics <- do.call(rbind, allMetrics) - which_rows <- unique(which_rows) - if(length(which_rows) == 0){ - stop(paste0("Invalid metric names: ", metrics)) - } else{ - return(which_rows) + if(!is.null(what)){ + if("all" %in% what){ + what <- c(baseWhats, shapeWhats, settWhats, angleWhats, distWhats) + } else if("nodist" %in% what){ + what <- c(baseWhats, shapeWhats, settWhats, angleWhats) + } } -} - - -#' @rdname get_fs_metrics -#' @export -list_fs <- function(col_names=NULL){ - if(is.null(col_names)){ - return(foot::fs_footprint_metrics) - } else{ - if(!all(col_names %in% names(fs_footprint_metrics))){ - stop("Invalid column names.") - } else{ - fs_footprint_metrics[, col_names] + + if(!is.null(how)){ + if("all" %in% how){ + how <- c(baseFuns, shapeFuns, settFuns, angleFuns, distFuns) + } else if("nodist" %in% how){ + how <- c(baseFuns, shapeFuns, settFuns, angleFuns) } } -} - - -#' @rdname get_fs_metrics -#' @export -get_fs_units <- function(metrics=NULL){ - if(is.null(metrics)){ - return(list_fs("default_units")) - } else{ - allunits <- list_fs(c("name","default_units")) - return(allunits[which(allunits$name %in% metrics), "default_units"]) - } -} - - -#' @rdname get_fs_metrics -#' @export -get_fs_group <- function(metrics=NULL){ - if(is.null(metrics)){ - return(list_fs("group")) + + if(is.null(how)){ + allMetrics <- allMetrics[allMetrics$cols %in% what, "cols"] + } else if(is.null(what)){ + allMetrics <- allMetrics[allMetrics$funs %in% how, "cols"] } else{ - allunits <- list_fs(c("name","group")) - return(allunits[which(allunits$name %in% metrics), "group"]) + allMetrics <- allMetrics[allMetrics$cols %in% what & + allMetrics$funs %in% how, ] + + allMetrics <- allMetrics[order(allMetrics$cols, allMetrics$funs),] + rownames(allMetrics) <- 1:nrow(allMetrics) } + + return(unique(allMetrics)) } - - diff --git a/R/parallel.R b/R/parallel.R deleted file mode 100644 index 84c6ea4..0000000 --- a/R/parallel.R +++ /dev/null @@ -1,37 +0,0 @@ -# Paralise any simple features analysis. -# Source: https://www.spatialanalytics.co.nz/post/2018/04/01/fixing-st-par/ -st_parallel <- function(sf_df, sf_func, n_cores, ...){ - - # Create a vector to split the data set up by. - split_vector <- rep(1:n_cores, each = nrow(sf_df) / n_cores, length.out = nrow(sf_df)) - - # Perform GIS analysis - split_results <- split(sf_df, split_vector) %>% - parallel::mclapply(function(x) sf_func(x, ...), mc.cores = n_cores) - - - # Define the output_class. If length is greater than two, then grab the second variable. - output_class <- class(split_results[[1]]) - if (length(output_class) == 2){ - output_class <- output_class[2] - } - - # Combine results back together. Method of combining depends on the output from the function. - if (output_class == "matrix"){ - result <- do.call("rbind", split_results) - names(result) <- NULL - } else if (output_class == "sfc") { - result <- do.call("c", split_results) - result <- sf_func(result) # do.call combines the list but there are still n_cores of the geometry which had been split up. Running st_union or st_collect gathers them up into one, as is the expected output of these two functions. - } else if (output_class %in% c('list', 'sgbp') ){ - result <- do.call("c", split_results) - names(result) <- NULL - } else if (output_class == "data.frame" ){ - result <- do.call("rbind", split_results) - } else { - stop("Unknown class. st_parallel only accepts the following outputs at present: sfc, list, sf, matrix, sgbp.") - } - - # Return result - return(result) -} diff --git a/R/utils.R b/R/utils.R new file mode 100644 index 0000000..6e3c4df --- /dev/null +++ b/R/utils.R @@ -0,0 +1,38 @@ + +expargs <- function(a, b, argNames=c('cols','funs'), onlyUnique=TRUE){ + if(is.fs_varlist(a)) a <- list(a) + if(is.fs_varlist(b)) stop("Functions cannot contain a 'varlist'.") + df <- expand.grid(a, b, stringsAsFactors=F) + names(df) <- argNames + + if(onlyUnique){ + df <- unique(df) + rownames(df) <- 1:nrow(df) + } + return(df) +} + + +crossargs <- function(a, b, argNames=c('cols','funs'), ...){ + da <- depth(as.list(a)) + db <- depth(as.list(b)) + if(da != db) stop("Number of argument groups does not match.") + + if(da <= 1){ + args <- list(do.call(expargs, list(a, b, argNames))) + } else{ + args <- Map(expargs, a, b, + argNames=replicate(length(as.list(a)), argNames, simplify=F)) + } + return(args) +} + + +# https://stackoverflow.com/questions/13432863/determine-level-of-nesting-in-r +depth <- function(this, thisdepth=0){ + if(!is.list(this)){ + return(thisdepth) + } else{ + return(max(unlist(lapply(this, depth, thisdepth = thisdepth + 1)))) + } +} \ No newline at end of file diff --git a/R/varlist.R b/R/varlist.R new file mode 100644 index 0000000..6bb0bb2 --- /dev/null +++ b/R/varlist.R @@ -0,0 +1,24 @@ +#' @title Select variables +#' @description Functions to construct and check for lists of variables for +#' \code{foot}. +#' @param ... Variables to include together as parameters for summary functions. +#' +#' @details This helper function is used by \code{calculate_footstats} to select +#' multiple columns or building characteristics to be used in a summary +#' function. This facilitates using functions that take multiple arguments. +#' +#' @seealso \code{[calculate_footstats]} for examples of summary functions. +#' +#' @name fs_varlist +#' @export +fs_varlist <- function(...){ + l <- list(...) + class(l) <- "fs_varlist" + return(l) +} + +#' @param x object to be tested. +#' @rdname fs_varlist +#' @export +is.fs_varlist <- function(x){ inherits(x, "fs_varlist") } + diff --git a/R/write_grid.R b/R/write_grid.R deleted file mode 100644 index 69a9427..0000000 --- a/R/write_grid.R +++ /dev/null @@ -1,199 +0,0 @@ -#' Writing gridded data to binary files -#' -#' @description Write data to binary files and structure as native \code{raster} files. -#' @param filename character string path to desired output file. -#' @param dataType character string of \code{raster} data types. Default is \code{FLT8S} -#' @param template A filepath or a gridded data object to be used as a -#' template to define the output file's resolution and positioning. -#' @param interleave character string for band interleaving. Currently only 'BSQ' supported. -#' @param overwrite logical. Should the output file be overwritten if it already exists? -#' Default is \code{False}. -#' -#' @details These functions provide a lightweight interface to binary writing demonstrated -#' in the \code{\link[spatial.tools]{create_blank_raster}} package. The advantage of using binary files is -#' the opportunity to using memory mapping (\code{mmap}) to support parallel writing. -#' -#' @import mmap -#' -#' @name make_templateGrid -#' @export -make_templateGrid <- function(filename=NULL, datatype="FLT8S", - template=NULL, interleave="BSQ", overwrite=FALSE){ - - if(is.null(filename)){ - filename <- tempfile() - file.rename(filename, paste0(filename, '.gri')) - - } else if(!is.character(filename)){ - stop("Filename should be a character string.") - - }else { - filename <- sub("\\.[[:alnum:]]+$", "", filename) - filename <- paste0(filename, ".gri") - - if(file.exists(filename)){ - if(overwrite==FALSE){ - stop("File already exists. Set overwrite to TRUE or set new filename.") - } else{ - file.remove(filename) - } - } - } - - if(class(template) == "RasterLayer"){ - nrows <- raster::nrow(template) - ncols <- raster::ncol(template) - nlayers <- raster::nlayers(template) - - xmin <- raster::xmin(template) - xmax <- raster::xmax(template) - ymin <- raster::ymin(template) - ymax <- raster::ymax(template) - - proj <- sf::st_crs(template)$proj4string - - } else if(any(class(template) == "stars")){ - nrows <- stars::st_dimensions(template)$y$to - ncols <- stars::st_dimensions(template)$x$to - nlayers <- stars::st_dimensions(template)$band$to - if(is.null(nlayers)) nlayers <- 1 - - xmin <- sf::st_bbox(template)$xmin - xmax <- sf::st_bbox(template)$xmax - ymin <- sf::st_bbox(template)$ymin - ymax <- sf::st_bbox(template)$ymax - - proj <- sf::st_crs(template)$proj4string - - } else if(class(template) == "numeric"){ - nrows <- template[1] - ncols <- template[2] - nlayers <- if(length(template)==3) template[3] else 1 - - } else{ - stop("Unsupported template format.") - } - - ncells <- nrows * ncols * nlayers - bytes <- as.numeric(substr(datatype, 4, 4)) - - outf <- file(filename, "wb") - seek(outf, (ncells-1) * bytes) - writeBin(raw(bytes), outf) - close(outf) - - make_templateHeader(filename, - nrows, ncols, xmin, ymin, xmax, ymax, - proj, datatype, nlayers, interleave, - nodata=-9999) - - return(filename) -} - -#' @title Construct headers for gridded data -#' @description Creates a .grd file with information on resolution and spatial extent for -#' use with a binary file (.gri). -#' @inheritParams make_templateGrid -#' @param nrows number of rows in the data -#' @param ncols number of columns in the data -#' @param xmin,ymin,xmax,ymax geographic extent coordinates of the output data -#' @param proj Proj4String of a coordinate reference system. -#' @param nlayers number of layers (multiband) in the gridded data. Default is 1. -#' @param nodata Value to represent no data in the output. -#' -#' @details These gridded files are the native files used by the -#' \code{raster} package. The .gri (data) and .grd (header) files must have matching filenames. -#' -#' @name make_templateHeader -#' @export -# Based on: https://stackoverflow.com/questions/32910919/converting-band-interleaved-by-pixel-binary-files-into-raster-grids -make_templateHeader <- function(filename, - nrows, ncols, xmin, ymin, xmax, ymax, - proj, datatype, nlayers=1, interleave="BSQ", - nodata){ - - filename <- sub("\\.[[:alnum:]]+$", "", filename) - filename <- paste0(filename, ".grd") - - hdrText <- c("[georeference]", - paste0("nrows=", nrows), - paste0("ncols=", ncols), - paste0("xmin=", xmin), - paste0("ymin=", ymin), - paste0("xmax=", xmax), - paste0("ymax=", ymax), - paste0("projection=", proj), - "[data]", - paste0("datatype=", datatype), - paste0("byetorder=", .Platform$endian), - paste0("nbands=", nlayers), - paste0("bandorder=", interleave), - paste0("nodatavalue=", nodata), - "[description]", - "layername='") - - con <- file(filename, "w") - writeLines(hdrText, con) - close(con) - - invisible(filename) -} - -#' @title Fast writing of binary files -#' @description \code{write_imageBinary} uses memory mapping functions to write -#' gridded data to a binary output format. Binary outputs can be read natively -#' but some software, or with the addition of header files, be converted to -#' formats easily read by most software. -#' -#' @param data vector of -#' @param cellNumbers vector of cell indices to update with data values -#' @param filename character string of path to the output file -#' @param mapMode character string of the \code{raster} data type. -#' -#' @details The function uses \code{\link[mmap]{mmap}} create and update a -#' binary output file on the hard drive. -#' -#' The mapMode converts the \code{raster} data type strings to a \code{CType} used -#' by \code{mmap}. -#' \itemize{ -#' \item "LOG1S" \code{mmap::logi8()} -#' \item "INT1S" \code{mmap::int8()} -#' \item "INT1U" \code{mmap::uint8()} -#' \item "INT2S" \code{mmap::int16()} -#' \item "INT2U" \code{mmap::uint16()} -#' \item "INT4S" \code{mmap::int32()} -#' \item "INT4U" \code{NA} -#' \item "INT4S" \code{mmap::real32()} -#' \item "INT8S" \code{mmap::real64()} -#' } -#' -#' @name write_imageBinary -#' @export -write_imageBinary <- function(data, cellNumbers=1:length(data), filename, mapMode){ - if(missing(data)){ - stop("Data not found.") - } - - if(missing(filename)){ - stop("Missing output file.") - } - - rasterTypes=c("LOG1S","INT1S","INT1U","INT2S", - "INT2U","INT4S","INT4U","FLT4S","FLT8S") - mmType <- list(mmap::logi8(), mmap::int8(), mmap::uint8(), - mmap::int16(), mmap::uint16(), mmap::int32(), - NA, mmap::real32(), mmap::real64()) - - matchType <- match(mapMode, rasterTypes) - - if(is.na(matchType)){ - stop("Mmap C Type not found.") - } else{ - mapMode <- mmap::as.Ctype(mmType[[matchType]]) - } - - # writing step - mapOut <- mmap::mmap(filename, mapMode) - mapOut[cellNumbers] <- as.numeric(data) - mmap::munmap(mapOut) -} diff --git a/README.Rmd b/README.Rmd index f105a17..0e86588 100644 --- a/README.Rmd +++ b/README.Rmd @@ -1,11 +1,9 @@ --- output: github_document: - toc: true - toc_depth: 2 html_preview: false --- - + ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, @@ -20,154 +18,147 @@ output: # foot: An R package for processing building footprints *[WorldPop Research Group, University of Southampton](https://www.worldpop.org/)* +While remote sensing has long been used to monitor urbanisation patterns, in +recent years there has been an increasing availability in finer resolution +satellite data covering large areas of the globe. This very high resolution +imagery (often <1 m spatial resolution), combined with increased computing power +is producing new datasets on urban areas. In particular, machine learning +algorithms are being applied to detect, automatically extract, and map full sets +of building features in a scene. These automated methods add to the manually +digitised information such as from [OpenStreetMap](http://www.openstreetmap.org) +and the property datasets available from some city governments. + +Such building footprint datasets provide a new level of detail on urban areas, +particularly in places which might otherwise lack detailed maps on cities and +rapidly growing areas. Despite their spatial detail, building footprints +typically lack any other attribute information to help differentiate land uses +or other neighbourhood characteristics. However, the size, shape, orientation, +and clustering of structures produces a spatial pattern that can suggest areas +of different land use or activities. + The `foot` package is designed to provide a set of consistent and flexible tools -for processing 2D vector representations of buildings. The functionality -includes basic geometry and morphology measures, distance and clustering -metrics. These calculations are supported with helper functions for spatial -intersections and tiled reading/writing of data. Two main wrapper functions -provide standardised workflows for process building polygons to summarise -metrics within polygons or on a gridded surface. +for processing 2D vector representations of buildings and calculating urban +morphology measurements. The functionality includes basic geometry and +morphology measures, distance and clustering metrics. These calculations are +supported with helper functions for spatial intersections and tiled +reading/writing of data. ## Installation + The `foot` package can be installed directly from Github. ```r -devtools::install_github("wgpg/foot", auth_token="tkn", build_vignettes=TRUE) +devtools::install_github("wgpg/foot", build_vignettes=TRUE) ``` -where, "tkn" is an authentication token. This is required because `foot` is -still in a private repo. You can obtain a token from -[https://github.com/settings/tokens](https://github.com/settings/tokens) and -generating a personal token with 'repo' level access. This will be a long -character string. - -Alternatively, clone the repository and install using the code in using the code -in `./pkg_build.R` Note that building and running the code may require additional packages: -`stars`, `raster`, `sf`, `data.table`, `lwgeom`, `purrr`. et al. +`stars`, `raster`, `sf`, `data.table`, `lwgeom`, `mmap`. et al. ## Quick Start + A sample dataset of building footprints is provided: ``` # load the sample -data("kampala") +data("kampala", package="foot") # 2D vector building polygons kampala$buildings ``` -Read the vignette on basic usage `vignette(footsteps)`. For more advanced uses -and creating gridded data layers, see `vignette(bigfoot)`. +### Vignettes + +Vignettes are provided as an introduction to `foot`. The vignette on basic usage +is available from `vignette("footsteps", package="foot")`. The supplied datasets +can be used to replicate this vignette. For a discussion and example of creating +gridded data layers, see `vignette("bigfoot", package="foot")`. Finally, +techniques for using custom morphology metric functions with `foot` is +demonstrated in `vignette("cobbler", package="foot")`. These vignettes are also +available from this package website. ### Basic Usage + ```{r, message=FALSE, warning=FALSE} library(foot) + # load sample data -data("kampala") +data("kampala", package="foot") buildings <- kampala$buildings zones <- kampala$adminZones grid <- kampala$mastergrid ``` The `foot` package provides tools to calculate and summarise building morphology -measures at multiple scales. These include building-level measures. +measures at multiple scales. These include building-level geometry measures. ```{r message=FALSE, warning=FALSE, tidy=FALSE} # building-level metrics -buildings$built_area <- fs_area(buildings, - unit="m^2") +buildings$built_area <- calculate_footstats(buildings, what="area") head(buildings) ``` -As well as area-level summaries. +As well as area-level summaries within spatial zones. ```{r, message=FALSE, warning=FALSE, tidy=FALSE} # Area-level summary metrics -# index the buildings to zones +# Optionally, create an index for the buildings to zones building_zone <- zonalIndex(buildings, zones, zoneField = "Id", returnObject = TRUE) -# summarise within small areal units -admin_area <- fs_area_mean(building_zone, - index="Id", - unit="m^2") +# summarise metrics within small areal units +admin_area <- calculate_footstats(building_zone, + zone="Id", + what="area", how="mean") head(admin_area) ``` Or gridded summary outputs, with the options to include a circular focal window. ```{r message=FALSE, warning=FALSE, tidy=FALSE} -# calculated along a raster within a focal window +# calculated along a raster within a circular focal window gridded <- calculate_bigfoot(buildings, - metrics="area_mean", + what="area", how="mean", focalRadius=200, - template=grid) + template=grid, + outputPath=tempdir()) raster::plot(raster::raster(gridded)) plot(sf::st_geometry(buildings), add=TRUE) ``` - ### Outputs -Rasters (or tables): + +Rasters in GeoTiff format or data tables: 1. Binary settlement indicators 2. Counts of structures -3. Building area (total, mean, median, min, max, standard dev., coeff. var.) -4. Building perimeter (total, mean, median, min, max, standard dev., coeff. var.) -5. Nearest neighbour distance (mean, median, standard dev.) +3. Building area +4. Building perimeter +5. Nearest neighbour distance 6. Nearest neighbour index 7. Structure orientation angle (normalised entropy) -8. Compactness (Polsby-Popper) (mean, median) -9. Roundess (mean, median) +8. Compactness (Polsby-Popper) +9. Roundness -A full list of function names can be retrieved with `get_fs_metrics()`. +A full list of characteristics and summary function names can be retrieved with +`foot::list_fs()`. ## Contributions + Contributions are welcome. Raise or respond to an issue, or create a new branch to develop a feature/modification and submit a pull request. -## Repository Structure -The repository is structured as an R package with an additional folder "wd" that -is a working directory for storing scripts, input data, and output data. - -**./pkg_build.R** -A script to build the R package and install it on your machine. Optionally, -builds vignettes. - -**./data/** -Folder containing internal data files and sample building footprints for the -vignettes. - -**./data-raw/** -Folder containing the script to create the internal data files. - -**./doc/** -A folder containing the markdown and scripts for the vignettes. -**./man/** -A folder containing function documentation created by Roxygen. **Do not edit -these files.** Instead, use Roxygen to document each function (see -`./R/footFun.R` for example) and build the documentation using -`devtools::document()` (see `./pkg_build.R` for example). +## Acknowledgements -**./Meta/** -A folder created as part of building vignettes. - -**./R/** -A folder containing functions for the R package. Each file contains a function -with Roxygen documentation for the function at the top of the script. - -**./wd/code/** -A folder containing scripts. See the example script `./wd/code/example_script.R` -for a template. Note that the vignette scripts and .html source have been copied -here for easy access for users who don't want to install/build the package. - -**./wd/in/** -A folder containing input data. This folder is included in the .gitignore for -the repository, so files you save here will not be uploaded to github or shared -with collaborators on the repository. - -**./wd/out/** -A folder containing outputs from the scripts. This folder is included in the -.gitignore for the repository, so files you save here will not be uploaded to -github or shared with collaborators on the repository. +```{r} +citation("foot") +``` +This work was undertaken by members of the WorldPop Research Group at the +University of Southampton (Chris Jochem, Edith Darín, Claire Dooley, Doug +Leasure) with support from Andy Tatem and Attila Lazar. Funding support comes +from the Bill and Melinda Gates Foundation and the United Kingdom Foreign, +Commonwealth & Development Office as part of the Geo-Referenced Infrastructure +and Demographic Data for Development project (GRID3) (OPP1182408). Project +partners in [GRID3](https://grid3.org/) include the WorldPop Research Group, the +United Nations Population Fund, the Flowminder Foundation, and the Center for +International Earth Science Information Network within the Earth Institute at +Columbia University. diff --git a/README.md b/README.md index b42adae..80bf8f7 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,4 @@ - - [foot: An R package for processing building - footprints](#foot-an-r-package-for-processing-building-footprints) - - [Installation](#installation) - - [Quick Start](#quick-start) - - [Contributions](#contributions) - - [Repository Structure](#repository-structure) - # foot: An R package for processing building footprints @@ -13,52 +6,72 @@ *[WorldPop Research Group, University of Southampton](https://www.worldpop.org/)* +While remote sensing has long been used to monitor urbanisation +patterns, in recent years there has been an increasing availability in +finer resolution satellite data covering large areas of the globe. This +very high resolution imagery (often \<1 m spatial resolution), combined +with increased computing power is producing new datasets on urban areas. +In particular, machine learning algorithms are being applied to detect, +automatically extract, and map full sets of building features in a +scene. These automated methods add to the manually digitised information +such as from [OpenStreetMap](http://www.openstreetmap.org) and the +property datasets available from some city governments. + +Such building footprint datasets provide a new level of detail on urban +areas, particularly in places which might otherwise lack detailed maps +on cities and rapidly growing areas. Despite their spatial detail, +building footprints typically lack any other attribute information to +help differentiate land uses or other neighbourhood characteristics. +However, the size, shape, orientation, and clustering of structures +produces a spatial pattern that can suggest areas of different land use +or activities. + The `foot` package is designed to provide a set of consistent and -flexible tools for processing 2D vector representations of buildings. -The functionality includes basic geometry and morphology measures, -distance and clustering metrics. These calculations are supported with -helper functions for spatial intersections and tiled reading/writing of -data. Two main wrapper functions provide standardised workflows for -process building polygons to summarise metrics within polygons or on a -gridded surface. +flexible tools for processing 2D vector representations of buildings and +calculating urban morphology measurements. The functionality includes +basic geometry and morphology measures, distance and clustering metrics. +These calculations are supported with helper functions for spatial +intersections and tiled reading/writing of data. ## Installation The `foot` package can be installed directly from Github. ``` r -devtools::install_github("wpgp/foot", auth_token="tkn", build_vignettes=TRUE) +devtools::install_github("wgpg/foot", build_vignettes=TRUE) ``` -where, “tkn” is an authentication token. This is required because `foot` -is still in a private repo. You can obtain a token from - and generating a personal token -with ‘repo’ level access. This will be a long character string. - -Alternatively, clone the repository and install using the code in `./pkg_build.R` - Note that building and running the code may require additional packages: -`stars`, `raster`, `sf`, `data.table`, `lwgeom`, `purrr`. et al. +`stars`, `raster`, `sf`, `data.table`, `lwgeom`, `mmap`. et al. ## Quick Start A sample dataset of building footprints is provided: # load the sample - data("kampala") + data("kampala", package="foot") # 2D vector building polygons kampala$buildings -Read the vignette on basic usage `vignette(footsteps)`. For more -advanced uses and creating gridded data layers, see `vignette(bigfoot)`. +### Vignettes + +Vignettes are provided as an introduction to `foot`. The vignette on +basic usage is available from `vignette("footsteps", package="foot")`. +The supplied datasets can be used to replicate this vignette. For a +discussion and example of creating gridded data layers, see +`vignette("bigfoot", package="foot")`. Finally, techniques for using +custom morphology metric functions with `foot` is demonstrated in +`vignette("cobbler", package="foot")`. These vignettes are also +available from this package website. ### Basic Usage ``` r library(foot) + # load sample data -data("kampala") +data("kampala", package="foot") buildings <- kampala$buildings zones <- kampala$adminZones grid <- kampala$mastergrid @@ -66,19 +79,22 @@ grid <- kampala$mastergrid The `foot` package provides tools to calculate and summarise building morphology measures at multiple scales. These include building-level -measures. +geometry measures. ``` r # building-level metrics -buildings$built_area <- fs_area(buildings, - unit="m^2") +buildings$built_area <- calculate_footstats(buildings, what="area") +#> Selecting metrics +#> Setting control values. +#> Pre-calculating areas +#> No summary functions found, returning metrics. head(buildings) #> Simple feature collection with 6 features and 2 fields #> geometry type: POLYGON #> dimension: XY #> bbox: xmin: 32.60765 ymin: 0.341117 xmax: 32.61288 ymax: 0.345773 #> geographic CRS: WGS 84 -#> FID_1 geometry built_area +#> FID_1 geometry area #> 1 130 POLYGON ((32.61282 0.341132... 22.00824 [m^2] #> 2 132 POLYGON ((32.61229 0.341693... 220.39011 [m^2] #> 3 133 POLYGON ((32.60817 0.342753... 38.95750 [m^2] @@ -87,39 +103,56 @@ buildings$built_area <- fs_area(buildings, #> 6 138 POLYGON ((32.60765 0.345604... 164.00931 [m^2] ``` -As well as area-level summaries. +As well as area-level summaries within spatial zones. ``` r # Area-level summary metrics -# index the buildings to zones +# Optionally, create an index for the buildings to zones building_zone <- zonalIndex(buildings, zones, zoneField = "Id", returnObject = TRUE) -# summarise within small areal units -admin_area <- fs_area_mean(building_zone, - index="Id", - unit="m^2") +# summarise metrics within small areal units +admin_area <- calculate_footstats(building_zone, + zone="Id", + what="area", how="mean") +#> Selecting metrics +#> Setting control values. +#> Creating zonal index +#> Pre-calculating areas +#> +#> Calculating 1 metrics ... +#> area mean +#> Finished calculating metrics. head(admin_area) -#> index fs_area_m^2_mean -#> 1: 1 402.5984 [m^2] -#> 2: 2 211.0534 [m^2] -#> 3: 3 525.0747 [m^2] -#> 4: 4 555.0931 [m^2] -#> 5: 5 568.7154 [m^2] -#> 6: 6 1021.9529 [m^2] +#> Id area_mean +#> 1: 1 402.5984 [m^2] +#> 2: 2 211.0534 [m^2] +#> 3: 3 525.0747 [m^2] +#> 4: 4 555.0931 [m^2] +#> 5: 5 568.7154 [m^2] +#> 6: 6 1021.9529 [m^2] ``` Or gridded summary outputs, with the options to include a circular focal window. ``` r -# calculated along a raster within a focal window +# calculated along a raster within a circular focal window gridded <- calculate_bigfoot(buildings, - metrics="area_mean", + what="area", how="mean", focalRadius=200, - template=grid) + template=grid, + outputPath=tempdir()) +#> Selecting metrics +#> Setting control values. +#> Creating template output grids +#> Creating list of processing tiles +#> Setting up cluster... +#> Begin parallel tile processing: 2020-10-21 16:14:52 +#> +#> Finished processing all tiles: 2020-10-21 16:14:56 raster::plot(raster::raster(gridded)) plot(sf::st_geometry(buildings), add=TRUE) @@ -129,71 +162,55 @@ gridded <- calculate_bigfoot(buildings, ### Outputs -Rasters (or tables): +Rasters in GeoTiff format or data tables: 1. Binary settlement indicators 2. Counts of structures -3. Building area (total, mean, median, min, max, standard dev., coeff. - var.) -4. Building perimeter (total, mean, median, min, max, standard dev., - coeff. var.) -5. Nearest neighbour distance (mean, median, standard dev.) +3. Building area +4. Building perimeter +5. Nearest neighbour distance 6. Nearest neighbour index 7. Structure orientation angle (normalised entropy) -8. Compactness (Polsby-Popper) (mean, median) -9. Roundess (mean, median) +8. Compactness (Polsby-Popper) +9. Roundness -A full list of function names can be retrieved with `get_fs_metrics()`. +A full list of characteristics and summary function names can be +retrieved with `foot::list_fs()`. ## Contributions Contributions are welcome. Raise or respond to an issue, or create a new branch to develop a feature/modification and submit a pull request. -## Repository Structure - -The repository is structured as an R package with an additional folder -“wd” that is a working directory for storing scripts, input data, and -output data. - -**./pkg\_build.R** -A script to build the R package and install it on your machine. -Optionally, builds vignettes. - -**./data/** Folder containing internal data files and sample building -footprints for the vignettes. +## Acknowledgements -**./data-raw/** Folder containing the script to create the internal data -files. - -**./doc/** A folder containing the markdown and scripts for the -vignettes. - -**./man/** -A folder containing function documentation created by Roxygen. **Do not -edit these files.** Instead, use Roxygen to document each function (see -`./R/footFun.R` for example) and build the documentation using -`devtools::document()` (see `./pkg_build.R` for example). - -**./Meta/** A folder created as part of building vignettes. - -**./R/** -A folder containing functions for the R package. Each file contains a -function with Roxygen documentation for the function at the top of the -script. - -**./wd/code/** -A folder containing scripts. See the example script -`./wd/code/example_script.R` for a template. Note that the vignette -scripts and .html source have been copied here for easy access for users -who don’t want to install/build the package. - -**./wd/in/** -A folder containing input data. This folder is included in the -.gitignore for the repository, so files you save here will not be -uploaded to github or shared with collaborators on the repository. +``` r +citation("foot") +#> +#> To cite package 'foot' in publications use: +#> +#> WorldPop Research Group, University of Southampton (2020). foot: An R package for processing building footprints morphometrics. R package version 0.5. +#> https://github.com/wpgp/foot +#> +#> A BibTeX entry for LaTeX users is +#> +#> @Manual{, +#> title = {foot: An R package for processing building footprints morphometrics}, +#> author = {{WorldPop Research Group, University of Southampton}}, +#> year = {2020}, +#> note = {R package version 0.5}, +#> url = {https://github.com/wpgp/foot}, +#> } +``` -**./wd/out/** -A folder containing outputs from the scripts. This folder is included in -the .gitignore for the repository, so files you save here will not be -uploaded to github or shared with collaborators on the repository. +This work was undertaken by members of the WorldPop Research Group at +the University of Southampton (Chris Jochem, Edith Darín, Claire Dooley, +Doug Leasure) with support from Andy Tatem and Attila Lazar. Funding +support comes from the Bill and Melinda Gates Foundation and the United +Kingdom Foreign, Commonwealth & Development Office as part of the +Geo-Referenced Infrastructure and Demographic Data for Development +project (GRID3) (OPP1182408). Project partners in +[GRID3](https://grid3.org/) include the WorldPop Research Group, the +United Nations Population Fund, the Flowminder Foundation, and the +Center for International Earth Science Information Network within the +Earth Institute at Columbia University. diff --git a/README.md.old b/README.md.old deleted file mode 100644 index 0fa3f86..0000000 --- a/README.md.old +++ /dev/null @@ -1,81 +0,0 @@ -# foot: An R package for processing building footprints -WorldPop Research Group, University of Southampton - -The `foot` package is designed to provide a set of consistent and flexible tools for processing 2D vector representations of buildings. The functionality includes basic geometry and morphology measures, distance and clustering metrics. These calculations are supported with helper functions for spatial intersections and tiled reading/writing of data. Two main wrapper functions provide standardised workflows for process building polygons to summarise metrics within polygons or on a gridded surface. - -### Quick Start - -1. Clone the repository to your computer (easiest with GitHub Desktop) - -2. Install the *foot* package using the code in `./pkg_build.R` - -3. Run the `vignette("footsteps")` to explore basic usage. - -4. Check out the vignette `bigfoot` for creating gridded summary layers. - -### Installation -Instead of cloning the repo, the *foot* package can be installed directly from Github. -``` -devtools::install_github("wgpg/foot", auth_token="tkn", build_vignettes=TRUE) -``` -where, "tkn" is an authentication token. This is required because *foot* is still in a private repo. You can obtain a token from and generating a personal token with 'repo' level access. This will be a long character string. - -Note that building and running the code may require additional packages: `stars`, `raster`, `sf`, `data.table`, `lwgeom`, `purrr`. - -### Contributions -Contributions are welcome. Raise or respond to an issue, or create a new branch to develop a feature/modification and submit a pull request. - -### Inputs -Building footprints (for example): - -//worldpop.files.soton.ac.uk/worldpop/Projects/WP517763_GRID3/DataIn/raw/DigitizeAfrica_building_footprints/ - -Sample dataset provided: -`data("kampala"); kampala$buildings` - -### Outputs -Rasters (or tables): - -1. Binary settlement indicators -2. Counts of structures -3. Building area (total, mean, median, standard dev., coeff. var.) -4. Building perimeter (total, mean, median, standard dev., coeff. var.) -5. Nearest neighbour distance (mean, median, standard dev.) -6. Nearest neighbour index -7. Structure orientation angle (normalised entropy) -8. Compactness (Polsby-Popper) (mean, median) -9. Roundess (mean, median) - -### Repository Structure -The repository is structured as an R package with an additional folder "wd" that is a working directory for storing scripts, input data, and output data. - -**./pkg_build.R** -A script to build the R package and install it on your machine. Optionally, builds vignettes. - -**./data/** -Folder containing internal data files and sample building footprints for the vignettes. - -**./data-raw/** -Folder containing the script to create the internal data files. - -**./doc/** -A folder containing the markdown and scripts for the vignettes. - -**./man/** -A folder containing function documentation created by Roxygen. **Do not edit these files.** Instead, use Roxygen to document each function (see `./R/footFun.R` for example) and build the documentation using `devtools::document()` (see `./pkg_build.R` for example). - -**./Meta/** -A folder created as part of building vignettes. - -**./R/** -A folder containing functions for the R package. Each file contains a function with Roxygen documentation for the function at the top of the script. - -**./wd/code/** -A folder containing scripts. See the example script `./wd/code/example_script.R` for a template. Note that the vignette scripts and .html source have been copied here for easy access for users who don't want to install/build the package. - -**./wd/in/** -A folder containing input data. This folder is included in the .gitignore for the repository, so files you save here will not be uploaded to github or shared with collaborators on the repository. - -**./wd/out/** -A folder containing outputs from the scripts. This folder is included in the .gitignore for the repository, so files you save here will not be uploaded to github or shared with collaborators on the repository. - diff --git a/_pkgdown.yml b/_pkgdown.yml new file mode 100644 index 0000000..947104c --- /dev/null +++ b/_pkgdown.yml @@ -0,0 +1,55 @@ +reference: + - title: Workflow wrapper functions + desc: ~ + contents: + - '`calculate_footstats`' + - '`calculate_bigfoot`' + - title: Built-in morphology metrics + desc: ~ + contents: + - '`fs_area`' + - '`fs_count`' + - '`fs_mbc`' + - '`fs_mbr`' + - '`fs_nndist`' + - '`fs_perimeter`' + - '`fs_compact`' + - '`fs_shape`' + - title: Built-in summary functions + desc: ~ + contents: + - '`fs_angle_entropy`' + - '`binary`' + - '`count`' + - '`cv`' + - '`entropy`' + - '`majority`' + - '`fs_nnindex`' + - title: Utility functions + desc: ~ + contents: + - '`list_fs`' + - '`fs_varlist`' + - '`gridTiles`' + - '`zonalIndex`' + - '`gen_nnindex`' + - title: Package files + desc: ~ + contents: + - '`foot`' + - '`kampala`' +navbar: + type: inverse + left: + - icon: fa-home + href: index.html + - text: "Reference" + href: reference/index.html + - text: "Articles" + menu: + - text: 'footsteps: Getting started' + href: articles/footsteps.html + - text: 'bigfoot: Gridded footprint calculations' + href: articles/bigfoot.html + - text: 'cobbler: Using custom summary functions' + href: articles/cobbler.html diff --git a/data-raw/fs_metrics_names.R b/data-raw/fs_metrics_names.R deleted file mode 100644 index 8817b4a..0000000 --- a/data-raw/fs_metrics_names.R +++ /dev/null @@ -1,32 +0,0 @@ -#' Data frame with lists of footprint metrics, names, and groups to look-up -#' -#' - -fs_footprint_metrics <- data.frame( - 'name' = c("fs_settled", "fs_count", - "fs_area_mean", "fs_area_median", "fs_area_sd", "fs_area_total", "fs_area_cv", "fs_area_min", "fs_area_max", - "fs_perim_mean", "fs_perim_median", "fs_perim_sd", "fs_perim_total", "fs_perim_cv", "fs_perim_min", "fs_perim_max", - "fs_nndist_mean", "fs_nndist_median", "fs_nndist_sd", "fs_nnindex", - "fs_angle_entropy", "fs_compact_mean", "fs_compact_median", "fs_shape_mean", "fs_shape_median"), - 'short_name' = c("settled", "count", - "area_mean", "area_median", "area_sd", "area_total", "area_cv", "area_min", "area_max", - "perim_mean", "perim_median", "perim_sd", "perim_total", "perim_cv", "perim_min", "perim_max", - "nndist_mean", "nndist_median", "nndist_sd", "nnindex", - "angle_entropy", "compact_mean", "compact_median", "shape_mean", "shape_median"), - 'group' = c("binary", "count", - "area", "area", "area", "area", "area", "area", "area", - "perim", "perim", "perim", "perim", "perim", "perim", "perim", - "dist", "dist", "dist", "dist", - "angle", "shape", "shape", "shape", "shape"), - 'default_units' = c("", "", - "ha", "ha", "ha", "ha", "", "ha", "ha", - "m", "m", "m", "m", "", "m", "m", - "m", "m", "m", "m", - "", "", "", "", ""), - stringsAsFactors=FALSE -) - -# save(fs_footprint_metrics, file="../R/sysdata.rda") - -usethis::use_data(fs_footprint_metrics, internal=FALSE, overwrite=TRUE) - diff --git a/data/fs_footprint_metrics.rda b/data/fs_footprint_metrics.rda deleted file mode 100644 index 73b5343..0000000 Binary files a/data/fs_footprint_metrics.rda and /dev/null differ diff --git a/docs/404.html b/docs/404.html new file mode 100644 index 0000000..2857646 --- /dev/null +++ b/docs/404.html @@ -0,0 +1,163 @@ + + + + + + + + +Page not found (404) • foot + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + +
+ +
+
+ + +Content not found. Please use links in the navbar. + +
+ + + +
+ + + +
+ + +
+

Site built with pkgdown 1.6.1.

+
+ +
+
+ + + + + + + + diff --git a/docs/LICENSE.html b/docs/LICENSE.html new file mode 100644 index 0000000..97eedea --- /dev/null +++ b/docs/LICENSE.html @@ -0,0 +1,406 @@ + + + + + + + + +NA • foot + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + +
+ +
+
+ + + +
                GNU GENERAL PUBLIC LICENSE
+                   Version 3, 29 June 2007
+

Copyright (C) 2007 Free Software Foundation, Inc. https://fsf.org/ Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.

+
                        Preamble
+

The GNU General Public License is a free, copyleft license for software and other kinds of works.

+

The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program–to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too.

+

When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things.

+

To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others.

+

For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.

+

Developers that use the GNU GPL protect your rights with two steps:

+

(1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it.

+

For the developers’ and authors’ protection, the GPL clearly explains that there is no warranty for this free software. For both users’ and authors’ sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions.

+

Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users’ freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users.

+

Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free.

+

The precise terms and conditions for copying, distribution and modification follow.

+
                   TERMS AND CONDITIONS
+
    +
  1. Definitions.
  2. +
+

“This License” refers to version 3 of the GNU General Public License.

+

“Copyright” also means copyright-like laws that apply to other kinds of works, such as semiconductor masks.

+

“The Program” refers to any copyrightable work licensed under this License. Each licensee is addressed as “you”. “Licensees” and “recipients” may be individuals or organizations.

+

To “modify” a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version” of the earlier work or a work “based on” the earlier work.

+

A “covered work” means either the unmodified Program or a work based on the Program.

+

To “propagate” a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well.

+

To “convey” a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying.

+

An interactive user interface displays “Appropriate Legal Notices” to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion.

+
    +
  1. Source Code.
  2. +
+

The “source code” for a work means the preferred form of the work for making modifications to it. “Object code” means any non-source form of a work.

+

A “Standard Interface” means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language.

+

The “System Libraries” of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Component”, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it.

+

The “Corresponding Source” for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work’s System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work.

+

The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source.

+

The Corresponding Source for a work in source code form is that same work.

+
    +
  1. Basic Permissions.
  2. +
+

All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law.

+

You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you.

+

Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary.

+
    +
  1. Protecting Users’ Legal Rights From Anti-Circumvention Law.
  2. +
+

No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures.

+

When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work’s users, your or third parties’ legal rights to forbid circumvention of technological measures.

+
    +
  1. Conveying Verbatim Copies.
  2. +
+

You may convey verbatim copies of the Program’s source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program.

+

You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee.

+
    +
  1. Conveying Modified Source Versions.
  2. +
+

You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions:

+
a) The work must carry prominent notices stating that you modified
+it, and giving a relevant date.
+
+b) The work must carry prominent notices stating that it is
+released under this License and any conditions added under section
+7.  This requirement modifies the requirement in section 4 to
+"keep intact all notices".
+
+c) You must license the entire work, as a whole, under this
+License to anyone who comes into possession of a copy.  This
+License will therefore apply, along with any applicable section 7
+additional terms, to the whole of the work, and all its parts,
+regardless of how they are packaged.  This License gives no
+permission to license the work in any other way, but it does not
+invalidate such permission if you have separately received it.
+
+d) If the work has interactive user interfaces, each must display
+Appropriate Legal Notices; however, if the Program has interactive
+interfaces that do not display Appropriate Legal Notices, your
+work need not make them do so.
+

A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate” if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation’s users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate.

+
    +
  1. Conveying Non-Source Forms.
  2. +
+

You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways:

+
a) Convey the object code in, or embodied in, a physical product
+(including a physical distribution medium), accompanied by the
+Corresponding Source fixed on a durable physical medium
+customarily used for software interchange.
+
+b) Convey the object code in, or embodied in, a physical product
+(including a physical distribution medium), accompanied by a
+written offer, valid for at least three years and valid for as
+long as you offer spare parts or customer support for that product
+model, to give anyone who possesses the object code either (1) a
+copy of the Corresponding Source for all the software in the
+product that is covered by this License, on a durable physical
+medium customarily used for software interchange, for a price no
+more than your reasonable cost of physically performing this
+conveying of source, or (2) access to copy the
+Corresponding Source from a network server at no charge.
+
+c) Convey individual copies of the object code with a copy of the
+written offer to provide the Corresponding Source.  This
+alternative is allowed only occasionally and noncommercially, and
+only if you received the object code with such an offer, in accord
+with subsection 6b.
+
+d) Convey the object code by offering access from a designated
+place (gratis or for a charge), and offer equivalent access to the
+Corresponding Source in the same way through the same place at no
+further charge.  You need not require recipients to copy the
+Corresponding Source along with the object code.  If the place to
+copy the object code is a network server, the Corresponding Source
+may be on a different server (operated by you or a third party)
+that supports equivalent copying facilities, provided you maintain
+clear directions next to the object code saying where to find the
+Corresponding Source.  Regardless of what server hosts the
+Corresponding Source, you remain obligated to ensure that it is
+available for as long as needed to satisfy these requirements.
+
+e) Convey the object code using peer-to-peer transmission, provided
+you inform other peers where the object code and Corresponding
+Source of the work are being offered to the general public at no
+charge under subsection 6d.
+

A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work.

+

A “User Product” is either (1) a “consumer product”, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used” refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product.

+

“Installation Information” for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made.

+

If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM).

+

The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network.

+

Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying.

+
    +
  1. Additional Terms.
  2. +
+

“Additional permissions” are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions.

+

When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission.

+

Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms:

+
a) Disclaiming warranty or limiting liability differently from the
+terms of sections 15 and 16 of this License; or
+
+b) Requiring preservation of specified reasonable legal notices or
+author attributions in that material or in the Appropriate Legal
+Notices displayed by works containing it; or
+
+c) Prohibiting misrepresentation of the origin of that material, or
+requiring that modified versions of such material be marked in
+reasonable ways as different from the original version; or
+
+d) Limiting the use for publicity purposes of names of licensors or
+authors of the material; or
+
+e) Declining to grant rights under trademark law for use of some
+trade names, trademarks, or service marks; or
+
+f) Requiring indemnification of licensors and authors of that
+material by anyone who conveys the material (or modified versions of
+it) with contractual assumptions of liability to the recipient, for
+any liability that these contractual assumptions directly impose on
+those licensors and authors.
+

All other non-permissive additional terms are considered “further restrictions” within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying.

+

If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms.

+

Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way.

+
    +
  1. Termination.
  2. +
+

You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11).

+

However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.

+

Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.

+

Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10.

+
    +
  1. Acceptance Not Required for Having Copies.
  2. +
+

You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so.

+
    +
  1. Automatic Licensing of Downstream Recipients.
  2. +
+

Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License.

+

An “entity transaction” is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party’s predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts.

+

You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it.

+
    +
  1. Patents.
  2. +
+

A “contributor” is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor’s “contributor version”.

+

A contributor’s “essential patent claims” are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control” includes the right to grant patent sublicenses in a manner consistent with the requirements of this License.

+

Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor’s essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version.

+

In the following three paragraphs, a “patent license” is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant” such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party.

+

If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying” means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient’s use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid.

+

If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it.

+

A patent license is “discriminatory” if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007.

+

Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law.

+
    +
  1. No Surrender of Others’ Freedom.
  2. +
+

If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program.

+
    +
  1. Use with the GNU Affero General Public License.
  2. +
+

Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such.

+
    +
  1. Revised Versions of this License.
  2. +
+

The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.

+

Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation.

+

If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy’s public statement of acceptance of a version permanently authorizes you to choose that version for the Program.

+

Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version.

+
    +
  1. Disclaimer of Warranty.
  2. +
+

THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

+
    +
  1. Limitation of Liability.
  2. +
+

IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

+
    +
  1. Interpretation of Sections 15 and 16.
  2. +
+

If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee.

+
                 END OF TERMS AND CONDITIONS
+
+        How to Apply These Terms to Your New Programs
+

If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.

+

To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found.

+
<one line to give the program's name and a brief idea of what it does.>
+Copyright (C) <year>  <name of author>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+

Also add information on how to contact you by electronic and paper mail.

+

If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:

+
<program>  Copyright (C) <year>  <name of author>
+This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+This is free software, and you are welcome to redistribute it
+under certain conditions; type `show c' for details.
+

The hypothetical commands show w' andshow c’ should show the appropriate parts of the General Public License. Of course, your program’s commands might be different; for a GUI interface, you would use an “about box”.

+

You should also get your employer (if you work as a programmer) or school, if any, to sign a “copyright disclaimer” for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see https://www.gnu.org/licenses/.

+

The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read https://www.gnu.org/licenses/why-not-lgpl.html.

+ + +
+ + + +
+ + + +
+ + +
+

Site built with pkgdown 1.6.1.

+
+ +
+
+ + + + + + + + diff --git a/docs/articles/bigfoot.html b/docs/articles/bigfoot.html new file mode 100644 index 0000000..d888383 --- /dev/null +++ b/docs/articles/bigfoot.html @@ -0,0 +1,509 @@ + + + + + + + +Gridded footprint calculation layers • foot + + + + + + + + + + +
+
+ + + + +
+
+ + + + +

The foot package was developed by WorldPop at the University of Southampton (www.worldpop.org) to support geometric calculations and zonal summaries of measures from building footprint polygons. The vignette("footsteps", package="foot") provides an introduction to package and the functionality of calculating and summarising morphology measures. This vignette builds on those methods and demonstrates a more advanced workflow to produce gridded summaries of buildings measures. In particular, the focus is on using foot::calculate_bigfoot() in order how to handle very large (i.e.  national-scale) datasets of building footprints.

+ +
+

+Calculations with foot +

+

The central function for producing gridded layers is calculate_bigfoot. It is designed to support processing large sets of building polygons into gridded (GeoTiff) summary layers. The procedure is intended to be more memory-efficient by splitting the processing area into “tiles” which can then be handled individually or in parallel. This function works as a wrapper to calculate_footstats as well as several helper functions within the foot package for managing the input/output and creating and indexing spatial zones. Because it extends calculate_footstats, this function takes the same what= and how= arguments to define the summary operations. Likewise the available metrics are available from list_fs().

+

By default the function performs calculations in parallel (which can be changed with the argument parallel=FALSE). Users can adjust the number of computing cores used with the argument nCores=) To monitor the supplied values and processing steps, set verbose=TRUE.

+
+

+Main inputs

+

Users need to supply calculate_bigfoot() with:

+
    +
  • A path to a file of building footprints in a spatial vector data format (e.g. .gpkg, .shp)
  • +
  • A filepath to a template gridded dataset specifying the extent and resolution for the outputs (e.g. .tif)
  • +
+

For example:

+

While R objects can be supplied for the function parameters (as will be shown in this example), it is recommended to supply character strings of paths to the files. calculate_bigfoot will only read the subset of data needed for a particular processing step. This is intended to allow users to process much larger datasets than can be held in memory.

+
+
+

+Basic calculations

+

With the key inputs for the file paths set, the processing can be executed with a single function.

+
+# basic function call with default parameters
+calculate_bigfoot(X=buildings,
+                  template=mgrid,
+                  what="settled", how="binary",
+                  verbose=TRUE)
+#> trying to read file: C:\Users\Admin\Documents\GitHub\foot\wd\in\kampala_grid.tif
+#> Selecting metrics 
+#> Setting control values. 
+#> Creating template output grids 
+#> Creating list of processing tiles 
+#> Setting up cluster...
+#> Begin parallel tile processing: 2020-10-23 12:14:30
+#> 
+#> Finished processing all tiles: 2020-10-23 12:14:32
+

The documentation and default parameters are available from ?foot::calculate_bigfoot.

+

The arguments what and how are the same as for calculate_footstats. They provide a character or list list of character names or characteristics (i.e. columns within the building footprint attributes) and summary functions, respectively. The available metrics can be retrieved with list_fs().

+
+
+

+Specifying outputs

+

By default the outputs of bigfoot are saved as GeoTiffs in the user’s working directory (getwd()). Each grid is named by the combination of characteristics and summary function. The example below shows how to retrieve an output of calculate_bigfoot.

+
+# retrieve the gridded outputs
+outGrid <- raster::raster(file.path(getwd(), "settled_binary.tif"))
+
+raster::plot(outGrid)
+
+Binary settled raster at 100m resolution

+Binary settled raster at 100m resolution +

+
+

Users can specify an output path to another folder location (outputPath=). Additionally a “tag” can be specified as a parameter to the function (outputTag=). The tag is appended to the beginning of each file name with an underscore. This can be useful for identifying different outputs. These options are demonstrated in the code block below.

+
+# basic function call specifying output parameters
+calculate_bigfoot(X=buildings,
+                  template=mgrid,
+                  what="settled", how="binary",
+                  outputPath=tempdir(),
+                  outputTag="TAG",
+                  verbose=TRUE)
+#> trying to read file: C:\Users\Admin\Documents\GitHub\foot\wd\in\kampala_grid.tif
+#> Selecting metrics 
+#> Setting control values. 
+#> Creating template output grids 
+#> Creating list of processing tiles 
+#> Setting up cluster...
+#> Begin parallel tile processing: 2020-10-23 12:14:33
+#> 
+#> Finished processing all tiles: 2020-10-23 12:14:35
+
+
+

+Multiple metrics

+

As with calculate_footstats multiple metrics and summary statistics can be supplied to bigfoot as a list or nested lists grouping characteristics and functions.

+
+# re-running the basic call with multiple metrics
+calculate_bigfoot(X=buildings,
+                  template=mgrid, 
+                  what=list(list("settled"), list("area")),
+                  how=list(list("binary"), list("cv")),
+                  outputPath=tempdir(),  
+                  outputTag="TAG")  
+#> trying to read file: C:\Users\Admin\Documents\GitHub\foot\wd\in\kampala_grid.tif
+#> Selecting metrics 
+#> Setting control values. 
+#> Creating template output grids 
+#> Creating list of processing tiles 
+#> Setting up cluster...
+#> Begin parallel tile processing: 2020-10-23 12:14:35
+#> 
+#> Finished processing all tiles: 2020-10-23 12:14:38
+
+# retrieve the gridded outputs Note: must add the 'tag' to the filename
+outGrid <- raster::raster(file.path(tempdir(), "TAG_area_cv.tif"))
+
+raster::plot(outGrid)
+plot(sf::st_geometry(buildings), fill = NA, add = T)
+
+Coeff. of variation in building area at 100m resolution

+Coeff. of variation in building area at 100m resolution +

+
+
+
+

+Focal window statistics

+

In the examples above, the footprint statistics are summarised for buildings whose centroid intersect the pixels of the template grid. Internally this is handled by zonalIndex(). The method for assigning footprints to spatial zones can be adjusted using controlZone options.

+

It is also possible to extend those zones beyond the individual pixels and to calculate and summarise building footprints from within a local, circular window centred on each pixel. The output is still associated with each template grid cell. This process is similar to a moving window or filter analysis. The focal radius distance is specified in meters.

+A hypothetical example of using a 300 m focal radius around a pixel centroid to construct a local zone and using building centroids is shown below. +
+Example of 300 m focal radius. Selected footprints are highlighted in red

+Example of 300 m focal radius. Selected footprints are highlighted in red +

+
+

Below is an example of applying a 300 m focal radius in action for a gridded output layer.

+
+# moving focal window calculations
+calculate_bigfoot(X=buildings,
+                  template=mgrid,
+                  what=list(list("area"), list("perimeter")),
+                  how=list(list("mean"), list("sum")),
+                  focalRadius=300,  # buffer radius in meters
+                  outputPath=tempdir(), 
+                  outputTag="TAG")
+#> trying to read file: C:\Users\Admin\Documents\GitHub\foot\wd\in\kampala_grid.tif
+#> Selecting metrics 
+#> Setting control values. 
+#> Creating template output grids 
+#> Creating list of processing tiles 
+#> Setting up cluster...
+#> Begin parallel tile processing: 2020-10-23 12:14:41
+#> 
+#> Finished processing all tiles: 2020-10-23 12:14:51
+

Creating the focal window is handled by calculate_bigfoot. The focal window is also taken into consideration by the function when creating processing tiles - the footprints are extract from outside the tile, in neighbouring areas, to prevent edge effects

+

Note that when a focal radius is specified, this value is always appended to the end of the file names so that the outputs can be identified.

+
+# Note that the filename includes both a tag and a focal radius value
+outGrid <- raster::raster(file.path(tempdir(), "TAG_perimeter_sum_300.tif"))
+
+raster::plot(outGrid)
+
+Total building perimeter in a 300m radius window. Output at a 100m resolution

+Total building perimeter in a 300m radius window. Output at a 100m resolution +

+
+
+
+
+

+Options and finer control

+

The calculate_bigfoot function is set up with default values that should work under most conditions; however, there is additional flexibility for users to specify alternative parameters.

+
+

+Specifying geometry units

+

To override the default units used in the geometry calculations, a named list of unit strings can be supplied to the controlUnits argument. This list can contain named items for areaUnit, perimUnit, and distUnit. The default values are meters (“m”) and square meters (“m^2”) The value of each item should be coercible with units::as_units.

+
+# change the default units used to calculate area and distance
+calculate_bigfoot(X=buildings,
+                  template=mgrid, 
+                  what=list(list("area"), list("perimeter")),
+                  how=list(list("mean"), list("sum")),
+                  controlZone=list("method"="intersect"), # join by intersection
+                  controlUnits=list(areaUnit="m^2",  # change default units
+                                    perimUnit="km"),
+                  outputPath=tempdir(),  
+                  outputTag="TAG",
+                  parallel=FALSE,
+                  verbose=TRUE)  
+#> trying to read file: C:\Users\Admin\Documents\GitHub\foot\wd\in\kampala_grid.tif
+#> Selecting metrics 
+#> Setting control values. 
+#> Creating template output grids 
+#> Creating list of processing tiles 
+#> 
+#> Tile: 1 of 1
+#> Reading footprints 
+#> Reading template grid 
+#> Selecting metrics 
+#> Setting control values. 
+#> Creating zonal index 
+#> Pre-calculating areas 
+#> Pre-calculating perimeters 
+#> 
+#> Calculating  2  metrics ... 
+#>    area mean  
+#>    perimeter sum  
+#> Finished calculating metrics. 
+#> Writing output tiles 
+#> Finished writing grids
+#> 
+#> Finished processing all tiles: 2020-10-23 12:14:54
+
+# plot the total perimeter, measured in kilometres
+outGrid <- raster::raster(file.path(tempdir(), "TAG_perimeter_sum.tif"))
+raster::plot(outGrid)
+
+Total building perimeter in KM

+Total building perimeter in KM +

+
+
+
+

+Filtering buildings

+

In some settings it may be preferable to exclude very small and/or very large building footprint polygons. The lower and upper bounds for filtering can be specified with minArea and maxArea in the filter argument. The values for these filters are in the same units specified by controlUnits or the default value for area calculations. Note that an “area” footprint statistic does not need to be requested as this characteristic is automatically calculated to enable filtering.

+
+# Filtering: # footprints must be larger than 50 m^2 and smaller than 1000 m^2
+calculate_bigfoot(X=buildings,
+                  template=mgrid, 
+                  what=list(list("shape"), list("settled"), list("perimeter")),
+                  how=list(list("mean"), list("count"), list("sum")),
+                  controlUnits=list(areaUnit="m^2"),
+                  filter=list(minArea=50, maxArea=1000),
+                  outputPath=tempdir(),  
+                  parallel=FALSE,
+                  verbose=TRUE)  
+#> trying to read file: C:\Users\Admin\Documents\GitHub\foot\wd\in\kampala_grid.tif
+#> Selecting metrics 
+#> Setting control values. 
+#> Creating template output grids 
+#> Creating list of processing tiles 
+#> 
+#> Tile: 1 of 1
+#> Reading footprints 
+#> Reading template grid 
+#> Selecting metrics 
+#> Setting control values. 
+#> Creating zonal index 
+#> Pre-calculating areas 
+#>  Filtering features larger than 50 
+#>  Filtering features smaller than 1000 
+#> Pre-calculating perimeters 
+#> Pre-calculating shape 
+#> 
+#> Calculating  3  metrics ... 
+#>    shape mean  
+#>    settled count  
+#>    perimeter sum  
+#> Finished calculating metrics. 
+#> Writing output tiles 
+#> Finished writing grids
+#> 
+#> Finished processing all tiles: 2020-10-23 12:15:01
+

In the map of the results below, each pixel is the count of footprints present. Note the smaller number of structures and sparseness of structures in pixels around the centre portions of the image. This corresponds with the a business district and industrial areas with fewer, but larger structures.

+
+outGrid <- raster::raster(file.path(tempdir(), "settled_count.tif"))
+
+raster::plot(outGrid)
+
+Count of buildings with area >50 m^2 and <1000 m^2

+Count of buildings with area >50 m^2 and <1000 m^2 +

+
+
+
+

+Tile size

+

The size of the processing tiles, specified in pixel dimensions (rows, columns) can be an important factor in the efficiency of the calculations. Smaller tile regions result in fewer building footprints being read/processed at one time, but there is an overhead computational cost of reading/writing files. The default value is 500 pixels. For the small demonstration shown here that results in one tile for the whole region. To show multiple tile processing, a small size is supplied and the processing is done in parallel with verbose output.

+
+calculate_bigfoot(X=buildings,
+                  template=mgrid,
+                  what="compact",
+                  how="mean",
+                  tileSize=c(20, 20),  # rows x columns in pixels
+                  parallel=FALSE,
+                  verbose=TRUE)
+#> trying to read file: C:\Users\Admin\Documents\GitHub\foot\wd\in\kampala_grid.tif
+#> Selecting metrics 
+#> Setting control values. 
+#> Creating template output grids 
+#> Creating list of processing tiles 
+#> 
+#> Tile: 1 of 4
+#> Reading footprints 
+#> Reading template grid 
+#> Selecting metrics 
+#> Setting control values. 
+#> Creating zonal index 
+#> Pre-calculating compactness 
+#> 
+#> Calculating  1  metrics ... 
+#>    compact mean  
+#> Finished calculating metrics. 
+#> Writing output tiles 
+#> Finished writing grids
+#> 
+#> Tile: 2 of 4
+#> Reading footprints 
+#> Reading template grid 
+#> Selecting metrics 
+#> Setting control values. 
+#> Creating zonal index 
+#> Pre-calculating compactness 
+#> 
+#> Calculating  1  metrics ... 
+#>    compact mean  
+#> Finished calculating metrics. 
+#> Writing output tiles 
+#> Finished writing grids
+#> 
+#> Tile: 3 of 4
+#> Reading footprints 
+#> Reading template grid 
+#> Selecting metrics 
+#> Setting control values. 
+#> Creating zonal index 
+#> Pre-calculating compactness 
+#> 
+#> Calculating  1  metrics ... 
+#>    compact mean  
+#> Finished calculating metrics. 
+#> Writing output tiles 
+#> Finished writing grids
+#> 
+#> Tile: 4 of 4
+#> Reading footprints 
+#> Reading template grid 
+#> Selecting metrics 
+#> Setting control values. 
+#> Creating zonal index 
+#> Pre-calculating compactness 
+#> 
+#> Calculating  1  metrics ... 
+#>    compact mean  
+#> Finished calculating metrics. 
+#> Writing output tiles 
+#> Finished writing grids
+#> 
+#> Finished processing all tiles: 2020-10-23 12:15:07
+
+
+
+

+Next steps

+

This vignette has provided an overview of how to create gridded outputs layers summarising building footprint morphology measures. This workflow uses calculate_bigfoot and is designed to work for large sets of data through tiled read/writing and processing these tiles in parallel. The bigfoot functionality extends and makes use of footstats. Both of these functions can take use-supplied and custom summary functions. This advanced usage is demonstrated in vignette("cobbler", package="foot").

+
+
+sessionInfo()
+#> R version 3.6.3 (2020-02-29)
+#> Platform: x86_64-pc-linux-gnu (64-bit)
+#> Running under: Ubuntu 20.04.1 LTS
+#> 
+#> Matrix products: default
+#> BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.9.0
+#> LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.9.0
+#> 
+#> locale:
+#>  [1] LC_CTYPE=en_GB.UTF-8       LC_NUMERIC=C              
+#>  [3] LC_TIME=en_GB.UTF-8        LC_COLLATE=en_GB.UTF-8    
+#>  [5] LC_MONETARY=en_GB.UTF-8    LC_MESSAGES=en_GB.UTF-8   
+#>  [7] LC_PAPER=en_GB.UTF-8       LC_NAME=C                 
+#>  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
+#> [11] LC_MEASUREMENT=en_GB.UTF-8 LC_IDENTIFICATION=C       
+#> 
+#> attached base packages:
+#> [1] stats     graphics  grDevices utils     datasets  methods   base     
+#> 
+#> other attached packages:
+#> [1] sf_0.9-6     raster_3.1-5 sp_1.4-1     foot_0.5    
+#> 
+#> loaded via a namespace (and not attached):
+#>  [1] tidyselect_1.0.0   xfun_0.18          purrr_0.3.4        lattice_0.20-40   
+#>  [5] vctrs_0.2.4        htmltools_0.4.0    stars_0.4-3        yaml_2.2.1        
+#>  [9] rlang_0.4.8        e1071_1.7-4        pkgdown_1.6.1      pillar_1.4.3      
+#> [13] glue_1.4.0         DBI_1.1.0          foreach_1.5.1      lifecycle_0.2.0   
+#> [17] stringr_1.4.0      ragg_0.3.1         codetools_0.2-16   memoise_1.1.0     
+#> [21] evaluate_0.14      knitr_1.30         doParallel_1.0.16  parallel_3.6.3    
+#> [25] class_7.3-16       highr_0.8          Rcpp_1.0.5         KernSmooth_2.23-16
+#> [29] backports_1.1.5    classInt_0.4-3     filelock_1.0.2     formatR_1.7       
+#> [33] lwgeom_0.2-5       desc_1.2.0         abind_1.4-5        systemfonts_0.3.2 
+#> [37] fs_1.3.1           digest_0.6.25      stringi_1.4.6      dplyr_0.8.5       
+#> [41] grid_3.6.3         rprojroot_1.3-2    rgdal_1.4-8        tools_3.6.3       
+#> [45] magrittr_1.5       tibble_3.0.1       crayon_1.3.4       pkgconfig_2.0.3   
+#> [49] ellipsis_0.3.0     data.table_1.13.2  assertthat_0.2.1   rmarkdown_2.1     
+#> [53] rstudioapi_0.11    iterators_1.0.13   R6_2.4.1           units_0.6-7       
+#> [57] compiler_3.6.3
+
+
+ + + +
+ + + +
+ +
+

Site built with pkgdown 1.6.1.

+
+ +
+
+ + + + + + diff --git a/docs/articles/bigfoot_files/figure-html/Fig1-1.png b/docs/articles/bigfoot_files/figure-html/Fig1-1.png new file mode 100644 index 0000000..d8dcaa5 Binary files /dev/null and b/docs/articles/bigfoot_files/figure-html/Fig1-1.png differ diff --git a/docs/articles/bigfoot_files/figure-html/Fig2-1.png b/docs/articles/bigfoot_files/figure-html/Fig2-1.png new file mode 100644 index 0000000..257c6c1 Binary files /dev/null and b/docs/articles/bigfoot_files/figure-html/Fig2-1.png differ diff --git a/docs/articles/bigfoot_files/figure-html/Fig3-1.png b/docs/articles/bigfoot_files/figure-html/Fig3-1.png new file mode 100644 index 0000000..1ce540c Binary files /dev/null and b/docs/articles/bigfoot_files/figure-html/Fig3-1.png differ diff --git a/docs/articles/bigfoot_files/figure-html/Fig4-1.png b/docs/articles/bigfoot_files/figure-html/Fig4-1.png new file mode 100644 index 0000000..54dd690 Binary files /dev/null and b/docs/articles/bigfoot_files/figure-html/Fig4-1.png differ diff --git a/docs/articles/bigfoot_files/figure-html/Fig5-1.png b/docs/articles/bigfoot_files/figure-html/Fig5-1.png new file mode 100644 index 0000000..d07b9e4 Binary files /dev/null and b/docs/articles/bigfoot_files/figure-html/Fig5-1.png differ diff --git a/docs/articles/bigfoot_files/figure-html/Fig6-1.png b/docs/articles/bigfoot_files/figure-html/Fig6-1.png new file mode 100644 index 0000000..f8fda0e Binary files /dev/null and b/docs/articles/bigfoot_files/figure-html/Fig6-1.png differ diff --git a/docs/articles/cobbler.html b/docs/articles/cobbler.html new file mode 100644 index 0000000..8431dc1 --- /dev/null +++ b/docs/articles/cobbler.html @@ -0,0 +1,525 @@ + + + + + + + +Using custom summary functions in `foot` • foot + + + + + + + + + + +
+
+ + + + +
+
+ + + + +

The foot package was developed by WorldPop at the University of Southampton (www.worldpop.org) to support geometric calculations and summaries of measures from building footprint polygons. This vignette demonstrates how users can extend the basic functionality of calculate_footstats and calculate_bigfoot by making and supplying their own functions to summarise footprint characteristics. For an introduction to the package, see vignette("footsteps").

+ +
+

+Calculations with foot +

+

As noted in the introductory vignettes, foot primarily uses calculate_footstats to calculate and summarise metrics. Internally this function uses data.table in order to handle large sets of building footprints and efficiently summarise them. The attributes to be summarised (what) are supplied to function names (how). These internal structures also allow for user-defined functions to be specified.

+
+

+Data preparation

+

To demonstrate using custom functions, we will first add some additional attribute “data” to the footprints which we will use.

+
+data("kampala", package = "foot")
+
+buildings <- kampala$buildings
+adminzones <- kampala$adminZones
+
+# Adding random data categorical variable
+buildings$category <- sample(LETTERS[1:5], size = nrow(buildings), replace = T)
+# continuous variable
+buildings$mult <- sample(rnorm(nrow(buildings), mean = 10, sd = 2))
+

We can use any attributes of the footprints within calculate_footstats and calculate_bigfoot, not only the built-in morphology measures listed by list_fs(what='all').

+
+# summarising a new data value
+calculate_footstats(buildings,
+                    adminzones,
+                    what="mult", # new attribute to summarise
+                    how="mean",
+                    verbose=F)
+#>     zoneID mult_mean
+#>  1:      1  9.961545
+#>  2:      2  9.913183
+#>  3:      3  9.670020
+#>  4:      4 10.335166
+#>  5:      5 10.316197
+#>  6:      6 10.046222
+#>  7:      7  9.832401
+#>  8:      8 10.152749
+#>  9:      9 10.040800
+#> 10:     10  9.757328
+#> 11:     11  9.841775
+#> 12:     12  9.471944
+#> 13:     13 10.450637
+#> 14:     14  9.903275
+#> 15:     15 10.130726
+#> 16:     16 10.294182
+#> 17:     17 10.181753
+#> 18:     18 10.071704
+#> 19:     19  9.916351
+#> 20:     20  9.944380
+#> 21:     21  9.887309
+#> 22:     22  9.757067
+#> 23:     23  9.933821
+#> 24:     24 10.062952
+#> 25:     25 11.009569
+#> 26:     26  9.811995
+#> 27:     27  9.820535
+#> 28:     29  9.852871
+#> 29:     30 10.087399
+#> 30:     31  9.364069
+#> 31:     32  9.900104
+#> 32:     33 10.323564
+#> 33:     34 10.027981
+#>     zoneID mult_mean
+
+
+

+Additional built-in functions

+

The internal foot functions are documented in ?fs_functions; however, these functions are intended to be used within the wrapper functions of foot and are rarely intended to be used as standalone functions. One built-in summary function, not applied by default, is majority. It is designed to summarise categorical data. This function is available for users in the same manner of specifying the how argument.

+
+# get the majority category in each zone
+calculate_footstats(buildings, 
+                    adminzones, 
+                    what="category", 
+                    how="majority", 
+                    verbose=F)
+#>     zoneID category_majority
+#>  1:      1                 A
+#>  2:      2                 A
+#>  3:      3                 E
+#>  4:      4                 E
+#>  5:      5                 E
+#>  6:      6                 A
+#>  7:      7                 C
+#>  8:      8                 E
+#>  9:      9                 C
+#> 10:     10                 A
+#> 11:     11                 A
+#> 12:     12                 C
+#> 13:     13                 C
+#> 14:     14                 E
+#> 15:     15                 C
+#> 16:     16                 E
+#> 17:     17                 A
+#> 18:     18                 C
+#> 19:     19                 A
+#> 20:     20                 B
+#> 21:     21                 C
+#> 22:     22                 C
+#> 23:     23                 B
+#> 24:     24                 B
+#> 25:     25                 D
+#> 26:     26                 C
+#> 27:     27                 E
+#> 28:     29                 A
+#> 29:     30                 E
+#> 30:     31                 A
+#> 31:     32                 A
+#> 32:     33                 A
+#> 33:     34                 A
+#>     zoneID category_majority
+

The majority function is similar to the idea of supplying a user-defined function which is demonstrated in the next section.

+
+
+
+

+User-defined summary functions

+

Creating functions for use with foot follows the same procedures and syntax for functions in R in general. They must be declared with <- function() and they must be available within the environment where foot functions are being used. When the functions are used internally by calculate_footstats, they are applied to footprints by zone index. Therefore they should return a single, summary value since the function for that group of footprints in the zone.

+

The name of the function is what is passed to foot as an argument to how. The argument(s) to the custom function can be named anything, but they will typically be values present within the footprint attributes to be summarised.

+

The example below shows a simple function that calculates the sum of the square root of the values. We will apply it to ‘area’, and foot will automatically pre-calculate this characteristic since it is not present in the column names of the footprints.

+
+# simple function example 1
+f1 <- function(v){
+  units(v) <- NULL # ignore units in our function
+  return(sum(sqrt(v)))
+}
+
+# applying custom summary function to area
+calculate_footstats(buildings,
+                    adminzones,
+                    what="area", how="f1",
+                    verbose=F)
+#> Linking to GEOS 3.8.0, GDAL 3.0.4, PROJ 6.3.1
+#> WARNING: different compile-time and runtime versions for GEOS found:
+#> Linked against: 3.8.0-CAPI-1.13.1  compiled against: 3.8.1-CAPI-1.13.3
+#> It is probably a good idea to reinstall sf, and maybe rgeos and rgdal too
+#>     zoneID    area_f1
+#>  1:      1  364.18909
+#>  2:      2 1823.23957
+#>  3:      3  871.63637
+#>  4:      4 1966.54994
+#>  5:      5  867.92154
+#>  6:      6  822.51869
+#>  7:      7  485.66087
+#>  8:      8 1194.46899
+#>  9:      9 3048.19689
+#> 10:     10  385.24272
+#> 11:     11 1404.13211
+#> 12:     12  294.77870
+#> 13:     13   73.79837
+#> 14:     14  730.25610
+#> 15:     15 2014.66636
+#> 16:     16 1154.35602
+#> 17:     17 1133.49662
+#> 18:     18 3472.56857
+#> 19:     19 5905.48635
+#> 20:     20 5262.66216
+#> 21:     21  998.62900
+#> 22:     22  722.37587
+#> 23:     23 3441.00032
+#> 24:     24 3882.17755
+#> 25:     25  435.66830
+#> 26:     26 3426.43477
+#> 27:     27 2283.48433
+#> 28:     29 1910.57791
+#> 29:     30  989.77450
+#> 30:     31   11.92210
+#> 31:     32 1913.14204
+#> 32:     33  309.58519
+#> 33:     34 9839.29584
+#>     zoneID    area_f1
+

Although this function was just used to process area, the function can be used for any continuous value. It can also be used on multiple characteristics or combined with other lists of functions, just like any other built-in function in foot.

+
+calculate_footstats(buildings,
+                    adminzones,
+                    what=list("area","perimeter"), how="f1",
+                    verbose=F)
+#>     zoneID    area_f1 perimeter_f1
+#>  1:      1  364.18909   183.488651
+#>  2:      2 1823.23957  1048.879091
+#>  3:      3  871.63637   426.068457
+#>  4:      4 1966.54994   968.615609
+#>  5:      5  867.92154   412.069737
+#>  6:      6  822.51869   337.518569
+#>  7:      7  485.66087   171.066602
+#>  8:      8 1194.46899   515.464525
+#>  9:      9 3048.19689  1911.775834
+#> 10:     10  385.24272   226.579855
+#> 11:     11 1404.13211   857.273283
+#> 12:     12  294.77870   214.216882
+#> 13:     13   73.79837    35.239420
+#> 14:     14  730.25610   500.492421
+#> 15:     15 2014.66636  1407.986177
+#> 16:     16 1154.35602   835.883724
+#> 17:     17 1133.49662   716.240827
+#> 18:     18 3472.56857  2477.699320
+#> 19:     19 5905.48635  3199.202606
+#> 20:     20 5262.66216  2994.324208
+#> 21:     21  998.62900   552.056838
+#> 22:     22  722.37587   400.124081
+#> 23:     23 3441.00032  1548.754569
+#> 24:     24 3882.17755  2167.419552
+#> 25:     25  435.66830   207.129346
+#> 26:     26 3426.43477  1360.146927
+#> 27:     27 2283.48433  1300.818322
+#> 28:     29 1910.57791  1117.788513
+#> 29:     30  989.77450   569.928890
+#> 30:     31   11.92210     6.938055
+#> 31:     32 1913.14204  1042.133260
+#> 32:     33  309.58519   155.175160
+#> 33:     34 9839.29584  6005.424628
+#>     zoneID    area_f1 perimeter_f1
+
+
+

+Functions with multiple arguments

+

In some instances, a custom function may need to make use of two or more characteristics from within the building footprint datasets. The built-in functions in foot are primarily designed to work with a single value (e.g. area or perimeter).

+

While it may sometimes be quicker to pre-calculate the combination, it could be advantageous to use a function, particularly in calculate_bigfoot where smaller subsets of a large building footprint dataset are processed. To make sure multiple attributes are supplied to the summary function, the arguments in what should be specified using a special type (fs_varlist). The fs_varlist creates a nested list within the internal processing to keep the argument together. Keep in mind that the arguments are passed to the summary function by position, not be name, so the order within fs_varlist must match the order of parameters that the function is expecting.

+
+

+Creating a Perimeter/Area ratio

+

An example of a custom function using two characteristics is the average perimeter-area ratio. We can compare this to the built-in function which uses a Polsby-Popper metric (fs_compact).

+
+# average perimeter-area ratio
+pa <- function(p, a){
+  return(mean(p / a))
+}
+
+# used to summarise within zones
+# note that fs_varlist is still within a list
+calculate_footstats(buildings,
+                    adminzones,
+                    what=list(list("compact"), fs_varlist("area","perimeter")),
+                    how=list(list("mean"), list("pa")),
+                    verbose=T
+                   )
+#> Selecting metrics 
+#> Setting control values. 
+#> Creating zonal index 
+#> Pre-calculating areas 
+#> Pre-calculating perimeters 
+#> Pre-calculating compactness 
+#> 
+#> Calculating  2  metrics ... 
+#>    compact mean  
+#>    area perimeter pa  
+#> Finished calculating metrics.
+#>     zoneID compact_mean area_perimeter_pa
+#>  1:      1    0.5991569      3.610827 [m]
+#>  2:      2    0.6575220      2.821102 [m]
+#>  3:      3    0.6126862      3.640346 [m]
+#>  4:      4    0.6272569      3.572513 [m]
+#>  5:      5    0.6053632      3.772161 [m]
+#>  6:      6    0.6255586      5.090459 [m]
+#>  7:      7    0.5447738      6.750857 [m]
+#>  8:      8    0.5516914      4.786082 [m]
+#>  9:      9    0.6113972      2.338425 [m]
+#> 10:     10    0.6219757      2.535005 [m]
+#> 11:     11    0.5777758      2.484360 [m]
+#> 12:     12    0.6591031      1.771451 [m]
+#> 13:     13    0.5629929      3.321843 [m]
+#> 14:     14    0.7122782      2.109547 [m]
+#> 15:     15    0.7247100      1.988679 [m]
+#> 16:     16    0.6982459      1.869702 [m]
+#> 17:     17    0.6518730      2.353175 [m]
+#> 18:     18    0.6869924      1.842260 [m]
+#> 19:     19    0.6686708      3.282259 [m]
+#> 20:     20    0.6810544      2.956515 [m]
+#> 21:     21    0.6926037      3.095947 [m]
+#> 22:     22    0.7113827      3.226212 [m]
+#> 23:     23    0.6151105      4.069236 [m]
+#> 24:     24    0.6682211      3.074468 [m]
+#> 25:     25    0.6304162      4.012065 [m]
+#> 26:     26    0.6118970      5.404871 [m]
+#> 27:     27    0.6831782      2.964538 [m]
+#> 28:     29    0.6616867      2.660880 [m]
+#> 29:     30    0.6448110      2.882803 [m]
+#> 30:     31    0.7708405      2.952773 [m]
+#> 31:     32    0.6358119      3.116430 [m]
+#> 32:     33    0.5847305      3.728961 [m]
+#> 33:     34    0.6413189      2.529615 [m]
+#>     zoneID compact_mean area_perimeter_pa
+
+
+
+

+Accessing R objects other than the footprints

+

A more complicated scenario exists when a user-defined function needs to access data which is not an attribute of the footprints dataset. In order to access the non-footprint data, a partial function must be created first and then supplied to calculation function.

+

In the example below, a simple constant value is supplied to a summary function; however, the idea extends to any object in the R environment. This process is how the nearest neighbour index is calculated in foot by drawing on the spatial zones object as well as the footprints.

+
+# external "data"
+d1 <- 0.001
+
+# This will NOT work because argument 'd' is not found
+# f2 <- function(x, d){
+#   return(sum(d * x))
+# }
+# 
+# calculate_footstats(buildings, adminzones, what="area", how="f2", verbose=T)
+
+# Instead...
+# example of creating a partial function
+gen_f3 <- function(d){
+  force(d) # must include
+  function(x){
+    return(sum(d * x))
+  }
+}
+
+# generate the function and initialise it with `d1` from above.
+f3 <- gen_f3(d1)
+
+# this now uses the generated function, and `d` is found
+calculate_footstats(buildings, 
+                    adminzones, 
+                    what="area", 
+                    how="f3", 
+                    verbose=F
+                   )
+#>     zoneID           area_f3
+#>  1:      1   8.4545661 [m^2]
+#>  2:      2  29.7585314 [m^2]
+#>  3:      3  25.7286600 [m^2]
+#>  4:      4  63.8357113 [m^2]
+#>  5:      5  26.7296259 [m^2]
+#>  6:      6  35.7683531 [m^2]
+#>  7:      7  28.3878563 [m^2]
+#>  8:      8  44.5041537 [m^2]
+#>  9:      9  48.1457825 [m^2]
+#> 10:     10   7.5599164 [m^2]
+#> 11:     11  24.2123780 [m^2]
+#> 12:     12   3.1098042 [m^2]
+#> 13:     13   2.6464756 [m^2]
+#> 14:     14   6.7120641 [m^2]
+#> 15:     15  19.2269062 [m^2]
+#> 16:     16   9.9002318 [m^2]
+#> 17:     17  14.7525754 [m^2]
+#> 18:     18  36.8954355 [m^2]
+#> 19:     19  96.4900399 [m^2]
+#> 20:     20  80.3556287 [m^2]
+#> 21:     21  18.0633304 [m^2]
+#> 22:     22  10.1969657 [m^2]
+#> 23:     23 132.6730618 [m^2]
+#> 24:     24  61.8347854 [m^2]
+#> 25:     25  11.6430956 [m^2]
+#> 26:     26 150.2087190 [m^2]
+#> 27:     27  33.8168355 [m^2]
+#> 28:     29  37.0078380 [m^2]
+#> 29:     30  15.2761021 [m^2]
+#> 30:     31   0.1421365 [m^2]
+#> 31:     32  35.7328487 [m^2]
+#> 32:     33   7.1172864 [m^2]
+#> 33:     34 146.9351905 [m^2]
+#>     zoneID           area_f3
+

In this vignette, the foot package has been extended to incorporate user-defined functions. These functions can use one or more values from within the footprints, or even access other objects in the environment. While the examples used calculate_footstats, the same approaches can be used to create new gridded summary metrics with calculate_bigfoot.

+
+
+sessionInfo()
+#> R version 3.6.3 (2020-02-29)
+#> Platform: x86_64-pc-linux-gnu (64-bit)
+#> Running under: Ubuntu 20.04.1 LTS
+#> 
+#> Matrix products: default
+#> BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.9.0
+#> LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.9.0
+#> 
+#> locale:
+#>  [1] LC_CTYPE=en_GB.UTF-8       LC_NUMERIC=C              
+#>  [3] LC_TIME=en_GB.UTF-8        LC_COLLATE=en_GB.UTF-8    
+#>  [5] LC_MONETARY=en_GB.UTF-8    LC_MESSAGES=en_GB.UTF-8   
+#>  [7] LC_PAPER=en_GB.UTF-8       LC_NAME=C                 
+#>  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
+#> [11] LC_MEASUREMENT=en_GB.UTF-8 LC_IDENTIFICATION=C       
+#> 
+#> attached base packages:
+#> [1] stats     graphics  grDevices utils     datasets  methods   base     
+#> 
+#> other attached packages:
+#> [1] sf_0.9-6 foot_0.5
+#> 
+#> loaded via a namespace (and not attached):
+#>  [1] Rcpp_1.0.5         pillar_1.4.3       compiler_3.6.3     formatR_1.7       
+#>  [5] iterators_1.0.13   class_7.3-16       tools_3.6.3        digest_0.6.25     
+#>  [9] lifecycle_0.2.0    tibble_3.0.1       evaluate_0.14      memoise_1.1.0     
+#> [13] pkgconfig_2.0.3    rlang_0.4.8        foreach_1.5.1      DBI_1.1.0         
+#> [17] rstudioapi_0.11    filelock_1.0.2     yaml_2.2.1         parallel_3.6.3    
+#> [21] pkgdown_1.6.1      xfun_0.18          e1071_1.7-4        dplyr_0.8.5       
+#> [25] stringr_1.4.0      knitr_1.30         vctrs_0.2.4        desc_1.2.0        
+#> [29] fs_1.3.1           systemfonts_0.3.2  tidyselect_1.0.0   classInt_0.4-3    
+#> [33] rprojroot_1.3-2    grid_3.6.3         glue_1.4.0         data.table_1.13.2 
+#> [37] R6_2.4.1           rmarkdown_2.1      purrr_0.3.4        magrittr_1.5      
+#> [41] stars_0.4-3        ellipsis_0.3.0     units_0.6-7        backports_1.1.5   
+#> [45] codetools_0.2-16   htmltools_0.4.0    abind_1.4-5        assertthat_0.2.1  
+#> [49] ragg_0.3.1         KernSmooth_2.23-16 stringi_1.4.6      doParallel_1.0.16 
+#> [53] lwgeom_0.2-5       crayon_1.3.4
+
+
+ + + +
+ + + +
+ +
+

Site built with pkgdown 1.6.1.

+
+ +
+
+ + + + + + diff --git a/docs/articles/footsteps.html b/docs/articles/footsteps.html new file mode 100644 index 0000000..8455b13 --- /dev/null +++ b/docs/articles/footsteps.html @@ -0,0 +1,688 @@ + + + + + + + +Basic building footprint calculations • foot + + + + + + + + + + +
+
+ + + + +
+
+ + + + +

The foot package was developed by WorldPop at the University of Southampton (www.worldpop.org) to provide a set of consistent and flexible tools for processing 2D vector representations of buildings (e.g. “footprints”) and calculating urban morphology measurements. The functionality includes basic geometry and morphology characteristics, distance and clustering metrics. These calculations are supported with helper functions for spatial intersections and tiled reading/writing of data.

+ +

This vignette will demonstrate some of the core functionality in the package, including:

+
    +
  • The available measurements and summary statistics
  • +
  • How to define different types of spatial zones for area-level summaries
  • +
  • Calculating multiple summary metrics for a set of spatial areas
  • +
  • Example workflows to produce outputs using foot::calculate_footstats().
  • +
+

To demonstrate the package, this vignette will use a supplied sample of building footprint polygons produced by Microsoft Bing Maps (Source) from an area in Kampala, Uganda. These footprints have been reprocessed into a spatial data format which can be read with sf.

+
+

+Load the sample dataset

+
+data("kampala", package = "foot")
+
+buildings <- kampala$buildings
+adminzones <- kampala$adminZones
+clusters <- kampala$clusters
+

The sample dataset is a list of four named objects:

+
    +
  • “buildings” - polygons of building footprints in sf format. Contains 8480 records.
  • +
  • “mastergrid” - geoTiff RasterLayer aligned to WorldPop datalayers. This will be used as a template for producing other gridded outputs
  • +
  • “adminZones” - 34 polygons in sf format for zonal statistics
  • +
  • “clusters” - 10 small polygons in sf format for sample sites
  • +
+

Note that the adminZones and cluster boundaries are purely artificial and were created for demonstration purposes only.

+

For more information, see ?kampala.

+
+Sample buildings and zones in Kampala

+Sample buildings and zones in Kampala +

+
+
+
+

+Calculations with foot +

+

The core functions of foot are provided by calculate_footstats(). The operations include: 1) calculating geometry measures for each footprint, 2) summarising one or more geometry measures of footprints within zones. The simplest usage involves supplying a set of building footprint polygons and the desired characteristics to calculate. All operations return the same format - a data.table, optionally with a column for the index and the named column for the summarised footprint metric(s).

+
+# the area and perimeter of each building footprint
+built_area <- calculate_footstats(buildings, what = list("area", "perimeter"))
+#> Selecting metrics 
+#> Setting control values. 
+#> Pre-calculating areas 
+#> Pre-calculating perimeters 
+#> No summary functions found, returning metrics.
+
+head(built_area)
+#>               area    perimeter
+#> 1:  22.00824 [m^2] 19.26045 [m]
+#> 2: 220.39011 [m^2] 67.95023 [m]
+#> 3:  38.95750 [m^2] 25.37269 [m]
+#> 4: 386.74429 [m^2] 98.55654 [m]
+#> 5: 349.57765 [m^2] 82.22555 [m]
+#> 6: 164.00931 [m^2] 55.87172 [m]
+

To summarise the footprint characteristics over all building polygons, the name of a summary function, or a list of multiple functions can be supplied.

+
+# the average and standard deviation of all building areas
+calculate_footstats(buildings, what = "area", how = list("mean", "sd"))
+#> Selecting metrics 
+#> Setting control values. 
+#> Pre-calculating areas 
+#> 
+#> Calculating  2  metrics ... 
+#>    area mean  
+#>    area sd  
+#> Finished calculating metrics.
+#>    zoneID      area_mean area_sd
+#> 1:      1 263.1865 [m^2]  715.28
+
+

+Available metrics and summary measures

+

Currently implemented are measures for:

+
    +
  • building presence
  • +
  • area
  • +
  • perimeter
  • +
  • nearest neighbour distance
  • +
  • angle of rotation
  • +
  • compactness and shape
  • +
+

A table of metrics and other packaged summary function names available for each is available using list_fs(). The results of this function provide “cols” and “funs” which can be passed as what and how arguments, respectively, to calculate_footstats.

+
+# get all available built-in functions for perimeter calculations
+argsList <- list_fs(what = "perimeter")
+
+calculate_footstats(buildings, what = argsList$cols, how = argsList$funs)
+#> Selecting metrics 
+#> Setting control values. 
+#> Pre-calculating perimeters 
+#> 
+#> Calculating  7  metrics ... 
+#>    perimeter cv  
+#>    perimeter max  
+#>    perimeter mean  
+#>    perimeter median  
+#>    perimeter min  
+#>    perimeter sd  
+#>    perimeter sum  
+#> Finished calculating metrics.
+#>    zoneID perimeter_cv perimeter_max perimeter_mean perimeter_median
+#> 1:      1    0.9396027  1089.097 [m]   62.22027 [m]     46.68314 [m]
+#>    perimeter_min perimeter_sd perimeter_sum
+#> 1:  14.88332 [m]     58.46233  301146.1 [m]
+

With no other argument supplied, all the footprints are treated as coming from the same spatial zone for any summary function. A later section describes the process for identifying zones.

+
+
+
+

+Additional characteristics and geometry measures

+

Whenever possible, foot makes use of generic functions within R. Most low-level geometry calculations rely on sf and lwgeom and users need to have recent versions of these packages installed. There are other stand-alone functions within foot to support more complex or less-common measurements.

+
+

+Nearest neighbour distances

+

Distances can be calculated between footprints, or between footprints and other spatial objects. The distances can be measured edge-to-edge (method="poly") or the centroids of the building footprints can be used (method="centroid").

+
+# nearest distance for the first 10 buildings to any other building measured
+# between polygon edges
+fs_nndist(buildings[1:10, ], buildings, maxSearch = 200, unit = "m")
+#> Units: [m]
+#>  [1]  3.961359  7.894813  1.019971 22.306277  2.484471 16.257603  0.951225
+#>  [8]  1.864305  2.607113  2.291474
+
+# omitting argument 'Y' measures distance among the footprints supplied setting
+# maxSearch=NULL removes the search restriction
+fs_nndist(buildings[1:10, ], method = "centroid", maxSearch = NULL)
+#> Units: [m]
+#>  [1]  99.95859  99.95859  90.11091  90.11091 116.64498 116.64498 217.86037
+#>  [8] 228.63068 217.86037 438.91352
+

Note that distance calculations are slower for polygons and for unprojected coordinate systems. The centroid-based calculations are fast. It is recommended that a maximum search radius is always used. Internally the calculations are done with a data.table to benefit from multi-threading capabilities.

+
+
+

+Rotation angles

+

A less conventional geometric measure is derived from the rotated bounding rectangle. This is the rectangle enclosing a footprint polygon which has been rotated to minimise the area. In contrast, a “bounding box” for a polygon is always oriented along the x and y axes.

+
+# To obtain the rotated rectangle as a spatial object
+mbr <- fs_mbr(buildings[4502, ], returnShape = T)
+
+plot(sf::st_geometry(buildings[4502, ]), col = "grey")
+plot(mbr, border = "red", lwd = 2, add = T)
+

+
+
+# Or obtain just the angle measure
+fs_mbr(buildings[4502, ], returnShape = F)
+#>        X 
+#> 63.25363
+

The angles can be summarised as an entropy measure and normalised to describe how much the angles of a set of structures depart from a regular grid pattern (available in calculate_footstats where how="entropy").

+
+
+
+

+Creating and supplying zone indices

+

Rather than treating all footprints features as belonging to a single summary zone, it’s more common to want to summarise metrics within smaller areas. There are several ways to supply information on the zones.

+
+

+Index by vector

+

A vector of indices for the zones can be supplied to foot functions as a 1) column name within the footprints, 2) a standalone numeric vector of indices, or 3) a spatial polygons object to join to the building footprints. The length of a vector of indices must match the number of building polygons.

+
+# create a vector of ten random zones
+idx <- sample(1:10, nrow(buildings), replace = T)
+buildings$id <- idx  # add it to the buildings object
+table(buildings$id)  # splitting observations into 10 groups
+#> 
+#>   1   2   3   4   5   6   7   8   9  10 
+#> 506 469 463 487 463 497 511 490 500 454
+
+# 1. pass the index by name
+colnames(buildings)
+#> [1] "FID_1"    "geometry" "id"
+calculate_footstats(buildings, "id", what = "area", how = "mean", verbose = FALSE)
+#>     id      area_mean
+#>  1:  1 227.5561 [m^2]
+#>  2:  2 287.8912 [m^2]
+#>  3:  3 243.0087 [m^2]
+#>  4:  4 252.2376 [m^2]
+#>  5:  5 247.8959 [m^2]
+#>  6:  6 286.5005 [m^2]
+#>  7:  7 264.4540 [m^2]
+#>  8:  8 293.5032 [m^2]
+#>  9:  9 251.6161 [m^2]
+#> 10: 10 278.3670 [m^2]
+
+# 2. pass the index as a standalone vector
+calculate_footstats(buildings, idx, what = "settled", how = "count", verbose = FALSE)
+#>     zoneID settled_count
+#>  1:      1           506
+#>  2:      2           469
+#>  3:      3           463
+#>  4:      4           487
+#>  5:      5           463
+#>  6:      6           497
+#>  7:      7           511
+#>  8:      8           490
+#>  9:      9           500
+#> 10:     10           454
+
+# 3. pass a separate spatial object of zones
+calculate_footstats(buildings, zone = adminzones, what = "angle", how = "entropy", 
+    verbose = FALSE)
+#>     zoneID angle_entropy
+#>  1:      1     0.9351095
+#>  2:      2     0.3108829
+#>  3:      3     0.5849276
+#>  4:      4     0.2003194
+#>  5:      5     0.6678707
+#>  6:      6     0.7139565
+#>  7:      7     0.9691374
+#>  8:      8     0.6102641
+#>  9:      9     0.5110688
+#> 10:     10     0.7309444
+#> 11:     11     0.3933987
+#> 12:     12     0.4602628
+#> 13:     13     1.0000000
+#> 14:     14     0.4656334
+#> 15:     15     0.3129308
+#> 16:     16     0.4551733
+#> 17:     17     0.3864071
+#> 18:     18     0.6949125
+#> 19:     19     0.2928137
+#> 20:     20     0.4598566
+#> 21:     21     0.6047045
+#> 22:     22     0.6520860
+#> 23:     23     0.2425743
+#> 24:     24     0.4518750
+#> 25:     25     0.8426361
+#> 26:     26     0.7302834
+#> 27:     27     0.4325040
+#> 28:     29     0.6144511
+#> 29:     30     0.4754793
+#> 30:     31     0.6023774
+#> 31:     32     0.4508777
+#> 32:     33     0.8639418
+#> 33:     34     0.1613791
+#>     zoneID angle_entropy
+
+
+

+Index by zone shapes

+

Rather than supplying a pre-calculated column or vector of zonal indices, buildings can be assigned a zone based on a spatial join. When the index is created in the building footprints, it will be named “zoneID” or a user-specified name.

+
+# examine the other objects loaded from supplied data
+head(adminzones)
+#> Simple feature collection with 6 features and 1 field
+#> geometry type:  POLYGON
+#> dimension:      XY
+#> bbox:           xmin: 32.60589 ymin: 0.3277355 xmax: 32.62531 ymax: 0.3388665
+#> geographic CRS: WGS 84
+#>   Id                       geometry
+#> 1  1 POLYGON ((32.61231 0.327771...
+#> 2  2 POLYGON ((32.61034 0.335317...
+#> 3  3 POLYGON ((32.61592 0.327757...
+#> 4  4 POLYGON ((32.6214 0.3277355...
+#> 5  5 POLYGON ((32.62342 0.333760...
+#> 6  6 POLYGON ((32.62498 0.335199...
+head(clusters)
+#> Simple feature collection with 6 features and 1 field
+#> geometry type:  POLYGON
+#> dimension:      XY
+#> bbox:           xmin: 32.60624 ymin: 0.3303863 xmax: 32.63375 ymax: 0.3472696
+#> geographic CRS: WGS 84
+#>   Id                       geometry
+#> 1  1 POLYGON ((32.6066 0.3398617...
+#> 2  2 POLYGON ((32.61151 0.337602...
+#> 3  3 POLYGON ((32.61028 0.331954...
+#> 4  4 POLYGON ((32.61649 0.344649...
+#> 5  5 POLYGON ((32.62552 0.345516...
+#> 6  6 POLYGON ((32.63225 0.340927...
+
+# Return a table of index values based on administrative zone polygons using the
+# standalone function within `foot`
+zID <- zonalIndex(buildings, adminzones, returnObject = F)
+head(zID, 10)  # the xID column are row numbers to object X
+#>      xID zoneID
+#>  1:  145      1
+#>  2:  338      1
+#>  3:  655      1
+#>  4:  995      1
+#>  5: 2869      1
+#>  6: 3164      1
+#>  7: 3263      1
+#>  8: 3314      1
+#>  9: 3407      1
+#> 10: 3454      1
+
+# Alternatively (and preferably), join zones to create a new footprint object A
+# custom zone name can be used but must be specificed to the summary functions
+zObj <- zonalIndex(buildings, clusters, zoneField = "Id", returnObject = T)
+zObj
+#> Simple feature collection with 348 features and 3 fields
+#> geometry type:  POLYGON
+#> dimension:      XY
+#> bbox:           xmin: 32.60631 ymin: 0.328119 xmax: 32.63543 ymax: 0.349785
+#> geographic CRS: WGS 84
+#> First 10 features:
+#>    FID_1 id Id                       geometry
+#> 1   6293  4  1 POLYGON ((32.60744 0.338575...
+#> 2  17706  2  1 POLYGON ((32.60664 0.339347...
+#> 3  29106  8  1 POLYGON ((32.60719 0.339276...
+#> 4  53393  1  1 POLYGON ((32.60779 0.339691...
+#> 5  57187  5  1 POLYGON ((32.60826 0.338663...
+#> 6  59496  1  1 POLYGON ((32.60692 0.339387...
+#> 7  68588  6  1 POLYGON ((32.60791 0.338896...
+#> 8  68590  5  1 POLYGON ((32.60768 0.339633...
+#> 9  72387  4  1 POLYGON ((32.60777 0.338962...
+#> 10 78504  1  1 POLYGON ((32.60702 0.338636...
+

When using a new spatial object which has been joined to its zones, remember to supply the name of the zone field to calculate_foostats.

+
+# use the new object and zone field 'Id' in a summary calculation
+colnames(zObj)
+#> [1] "FID_1"    "id"       "Id"       "geometry"
+
+zarea <- calculate_footstats(zObj, zone = "Id", what = "area", how = "mean", verbose = F)
+clusters <- merge(clusters, zarea, by = "Id")
+plot(sf::st_geometry(adminzones))
+plot(clusters["area_mean"], add = T)
+

+

The zonalIndex function works by spatial intersection. This produces some (potentially useful) side effects that users need to be aware of. Specifically, note that if a building footprint intersects more than 1 zone it will be duplicated and associated to all intersecting zones.

+

The default behaviour (see method) is to assign a building to a zone based on its centroid.

+
+# Note the selected structures extend beyond the cluster boundary
+plot(sf::st_geometry(clusters)[[6]])
+plot(sf::st_geometry(buildings), add = T)
+plot(sf::st_geometry(zObj[zObj$Id == 6, ]), col = "red", add = T)
+plot(sf::st_geometry(sf::st_centroid(zObj[zObj$Id == 6, ])), pch = 16, add = T)
+

+

Alternatively, an intersection can be used to assign footprints to any zones which are intersected. The whole footprint is associated, even if the shape is not “contained” by the zone.

+
+# Note the selected structures extend beyond the cluster boundary
+zInt <- zonalIndex(buildings, clusters, zoneField = "Id", method = "intersect")
+
+plot(sf::st_geometry(clusters)[[6]])
+plot(sf::st_geometry(buildings), add = T)
+plot(sf::st_geometry(zInt[zInt$Id == 6, ]), col = "red", add = T)
+

+

Finally, the intersection can return a clipped set of buildings.

+
+zClip <- zonalIndex(buildings, clusters, zoneField = "Id", method = "clip")
+
+plot(sf::st_geometry(clusters)[[6]])
+plot(sf::st_geometry(buildings), add = T)
+plot(sf::st_geometry(zClip[zClip$Id == 6, ]), col = "red", add = T)
+

+

This third option clips the footprints via intersection, potentially leaving small slivers of structures in the zone which will affect the feature statistics.

+

An additional side effect of the intersection operation is that overlapping zones are allowed, and this can duplicate and associate footprints into both (overlapping) zones.

+
+# create a temporary shape by shifting one cluster
+newClusters <- st_sfc(sf::st_geometry(clusters)[[1]], sf::st_cast(sf::st_geometry(clusters)[[1]] + 
+    c(0.001, 1e-04), "POLYGON"), crs = sf::st_crs(clusters))
+
+newClusters <- st_sf(geometry = newClusters, crs = sf::st_crs(clusters))
+
+newObj <- zonalIndex(buildings, newClusters, method = "clip")
+
+# areas of overlap are in a purple hue
+plot(sf::st_geometry(newClusters))
+plot(sf::st_geometry(newObj[newObj$zoneID == 1, ]), col = "red", add = T)
+plot(sf::st_geometry(newObj[newObj$zoneID == 2, ]), col = sf::sf.colors(n = 1, alpha = 0.5), 
+    add = T)
+plot(sf::st_geometry(buildings), add = T)
+

+

These side effects are allowed because they allow for flexibility to support types of “focal” summaries of statistics and to produce a true gridded measure of footprint metrics.

+
+
+
+

+Calculating multiple metrics

+

The calculate_footstats() function provides a convenient wrapper to the individual footprint statistics and as well to zonalIndex. The function accepts a variety of input formats (see ?calculate_footstats). Multiple metrics can be calculated for the same sets of buildings and zones.

+
+# Creates a zonal index and calculates multiple metrics
+# Use the intersection method define zones
+results <- calculate_footstats(buildings, 
+                               zone=adminzones, 
+                               what="area",
+                               how=list("mean","cv"),
+                               controlZone=list(method="intersect"),
+                               verbose=F
+                              )
+  results
+#>     zoneID        area_mean   area_cv
+#>  1:      1  402.59838 [m^2] 1.2479809
+#>  2:      2  211.05341 [m^2] 1.1069491
+#>  3:      3  525.07469 [m^2] 1.9194754
+#>  4:      4  555.09314 [m^2] 3.8206699
+#>  5:      5  568.71544 [m^2] 1.7000587
+#>  6:      6 1021.95294 [m^2] 2.0652377
+#>  7:      7 2027.70402 [m^2] 1.4077033
+#>  8:      8  908.24803 [m^2] 1.4225521
+#>  9:      9  181.68220 [m^2] 1.6104922
+#> 10:     10  251.99721 [m^2] 1.5272718
+#> 11:     11  220.11253 [m^2] 1.4300230
+#> 12:     12   87.83690 [m^2] 1.2659928
+#> 13:     13  661.61890 [m^2] 1.7191007
+#> 14:     14   81.85444 [m^2] 0.3447115
+#> 15:     15   80.78532 [m^2] 1.0115881
+#> 16:     16   68.75161 [m^2] 0.5102143
+#> 17:     17  139.17524 [m^2] 0.8678702
+#> 18:     18   87.23691 [m^2] 1.5248900
+#> 19:     19  237.07627 [m^2] 0.6189696
+#> 20:     20  199.39362 [m^2] 0.9103598
+#> 21:     21  244.09906 [m^2] 2.4147823
+#> 22:     22  192.39558 [m^2] 0.3464272
+#> 23:     23  776.00000 [m^2] 2.1447695
+#> 24:     24  219.27229 [m^2] 0.7972001
+#> 25:     25  506.22155 [m^2] 1.3228666
+#> 26:     26 1129.64270 [m^2] 1.7076343
+#> 27:     27  228.00865 [m^2] 2.2623265
+#> 28:     29  238.76025 [m^2] 2.7887959
+#> 29:     30  203.68136 [m^2] 0.8303153
+#> 30:     31  142.13649 [m^2]        NA
+#> 31:     32  272.76984 [m^2] 1.0894764
+#> 32:     33  418.66391 [m^2] 0.9787568
+#> 33:     34  176.81732 [m^2] 1.3707628
+#>     zoneID        area_mean   area_cv
+

Multiple metrics can be applied to specific groups of characteristics by providing nested lists of metrics and summary functions. The argument what will accept a string or a list of strings for specific metric names. Users may also supply "all" or "nodist" to calculate all available metrics or all bar the nearest neighbour distance-related ones, respectively. Excluding the distance-related metrics can speed up the calculations due to the long-running. Other performance improvements can be to set controlDistance=list("method"="centroid"), which uses centroid-to-centroid nearest neighbour distances rather than polygon edge-to-edge. See also, ?fs_nndist.

+
+# Use nested lists to group characteristics and summary functions
+results <- calculate_footstats(buildings, 
+                               zone=adminzones, 
+                               what=list(list("area","perimeter"), 
+                                         list("settled")),
+                               how=list(list("sum","cv"), 
+                                        list("count")),
+                               controlZone=list(method="centroid"),
+                               verbose=F
+                              )
+  results
+#>     zoneID          area_sum   perimeter_sum   area_cv perimeter_cv
+#>  1:      1   8454.5661 [m^2]  1781.62245 [m] 1.2479809    0.6561304
+#>  2:      2  29758.5314 [m^2]  8596.30974 [m] 1.1069491    0.7313348
+#>  3:      3  25728.6600 [m^2]  4414.86934 [m] 1.9194754    0.9811076
+#>  4:      4  63835.7113 [m^2]  9640.94681 [m] 3.8206699    1.0600954
+#>  5:      5  26729.6259 [m^2]  4329.10334 [m] 1.7000587    0.9581201
+#>  6:      6  35768.3531 [m^2]  3835.74843 [m] 2.0652377    0.9616081
+#>  7:      7  28387.8563 [m^2]  2565.09927 [m] 1.4077033    0.9286094
+#>  8:      8  44504.1537 [m^2]  6348.37632 [m] 1.4225521    0.8489816
+#>  9:      9  48145.7825 [m^2] 15739.78807 [m] 1.6104922    0.9246860
+#> 10:     10   7559.9164 [m^2]  2087.88260 [m] 1.5272718    1.0900293
+#> 11:     11  24212.3780 [m^2]  7672.66021 [m] 1.4300230    0.8815988
+#> 12:     12   3109.8042 [m^2]  1403.25960 [m] 1.3014215    0.7257624
+#> 13:     13   2646.4756 [m^2]   423.87801 [m] 1.7191007    1.3165558
+#> 14:     14   6712.0641 [m^2]  3088.40482 [m] 0.3447115    0.2059276
+#> 15:     15  19226.9062 [m^2]  8676.96497 [m] 1.0115881    0.5073846
+#> 16:     16   9900.2318 [m^2]  4968.39946 [m] 0.5102143    0.3095971
+#> 17:     17  14752.5754 [m^2]  5213.18182 [m] 0.8678702    0.5541695
+#> 18:     18  36895.4355 [m^2] 15742.13708 [m] 1.5308486    0.7093738
+#> 19:     19  96490.0399 [m^2] 26354.10548 [m] 0.6189696    0.4151437
+#> 20:     20  80355.6287 [m^2] 23566.14546 [m] 0.9103598    0.5257108
+#> 21:     21  18063.3304 [m^2]  4403.44976 [m] 2.4147823    0.7810948
+#> 22:     22  10196.9657 [m^2]  3061.47987 [m] 0.3464272    0.2238327
+#> 23:     23 132673.0618 [m^2] 17464.78357 [m] 2.1625263    1.1205392
+#> 24:     24  61834.7854 [m^2] 17707.47705 [m] 0.7972001    0.5317242
+#> 25:     25  11643.0956 [m^2]  2163.70761 [m] 1.3228666    0.8940386
+#> 26:     26 150208.7190 [m^2] 17604.40321 [m] 1.6814458    1.0767096
+#> 27:     27  33816.8355 [m^2] 10017.95165 [m] 0.7963207    0.4462858
+#> 28:     29  37007.8380 [m^2]  9185.83050 [m] 2.7887959    1.0713172
+#> 29:     30  15276.1021 [m^2]  4621.61764 [m] 0.8303153    0.5426390
+#> 30:     31    142.1365 [m^2]    48.13661 [m]        NA           NA
+#> 31:     32  35732.8487 [m^2]  9127.30909 [m] 1.0894764    0.6469339
+#> 32:     33   7117.2864 [m^2]  1557.24261 [m] 0.9787568    0.6104610
+#> 33:     34 146935.1905 [m^2] 47733.83601 [m] 1.3707628    0.7474269
+#>     zoneID          area_sum   perimeter_sum   area_cv perimeter_cv
+#>     settled_count
+#>  1:            21
+#>  2:           141
+#>  3:            49
+#>  4:           115
+#>  5:            47
+#>  6:            35
+#>  7:            14
+#>  8:            49
+#>  9:           265
+#> 10:            30
+#> 11:           110
+#> 12:            36
+#> 13:             4
+#> 14:            82
+#> 15:           238
+#> 16:           144
+#> 17:           106
+#> 18:           424
+#> 19:           407
+#> 20:           403
+#> 21:            74
+#> 22:            53
+#> 23:           172
+#> 24:           282
+#> 25:            23
+#> 26:           129
+#> 27:           177
+#> 28:           155
+#> 29:            75
+#> 30:             1
+#> 31:           131
+#> 32:            17
+#> 33:           831
+#>     settled_count
+
+

+Filtering buildings

+

In some settings it may be preferable to exclude very small and/or very large building footprint polygons. The lower and upper bounds for filtering can be specified with minArea and maxArea in the filter argument. The values for these filters are in the same units specified by controlUnits or the default value for area calculations. Note that an “area” footprint statistic does not need to be requested as this characteristic is automatically calculated to enable filtering.

+
+# Filtering: # footprints must be larger than 50 m^2 and smaller than 1000 m^2
+calculate_footstats(buildings,
+                    what="perimeter",
+                    how=list("mean", "sum"),
+                    controlUnits=list(areaUnit="m^2"),
+                    filter=list(minArea=50, maxArea=1000),
+                    verbose=FALSE)  
+#>    zoneID perimeter_mean perimeter_sum
+#> 1:      1   63.16899 [m]  228482.2 [m]
+
+
+
+

+Next steps

+

The calculate_footstats function provides the core functionality for calculating and summarising the characteristics of building footprints. It also wraps the functionality of assigning footprints to zones based on different spatial joining techniques. To go further with foot the concept of footprint morphology calculations can be extended to created gridded data. See vignette("bigfoot", package="foot"). Additionally, users can specify their own custom summary functions to be used with calculate_footstats. This and other advanced options are covered in vignette("cobbler", package="foot").

+
+
+sessionInfo()
+#> R version 3.6.3 (2020-02-29)
+#> Platform: x86_64-pc-linux-gnu (64-bit)
+#> Running under: Ubuntu 20.04.1 LTS
+#> 
+#> Matrix products: default
+#> BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.9.0
+#> LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.9.0
+#> 
+#> locale:
+#>  [1] LC_CTYPE=en_GB.UTF-8       LC_NUMERIC=C              
+#>  [3] LC_TIME=en_GB.UTF-8        LC_COLLATE=en_GB.UTF-8    
+#>  [5] LC_MONETARY=en_GB.UTF-8    LC_MESSAGES=en_GB.UTF-8   
+#>  [7] LC_PAPER=en_GB.UTF-8       LC_NAME=C                 
+#>  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
+#> [11] LC_MEASUREMENT=en_GB.UTF-8 LC_IDENTIFICATION=C       
+#> 
+#> attached base packages:
+#> [1] stats     graphics  grDevices utils     datasets  methods   base     
+#> 
+#> other attached packages:
+#> [1] sf_0.9-6 foot_0.5
+#> 
+#> loaded via a namespace (and not attached):
+#>  [1] tidyselect_1.0.0   xfun_0.18          purrr_0.3.4        vctrs_0.2.4       
+#>  [5] htmltools_0.4.0    stars_0.4-3        yaml_2.2.1         rlang_0.4.8       
+#>  [9] e1071_1.7-4        pkgdown_1.6.1      pillar_1.4.3       glue_1.4.0        
+#> [13] DBI_1.1.0          foreach_1.5.1      lifecycle_0.2.0    stringr_1.4.0     
+#> [17] ragg_0.3.1         codetools_0.2-16   memoise_1.1.0      evaluate_0.14     
+#> [21] knitr_1.30         doParallel_1.0.16  parallel_3.6.3     class_7.3-16      
+#> [25] highr_0.8          Rcpp_1.0.5         KernSmooth_2.23-16 backports_1.1.5   
+#> [29] classInt_0.4-3     filelock_1.0.2     formatR_1.7        lwgeom_0.2-5      
+#> [33] desc_1.2.0         abind_1.4-5        systemfonts_0.3.2  fs_1.3.1          
+#> [37] digest_0.6.25      stringi_1.4.6      dplyr_0.8.5        grid_3.6.3        
+#> [41] rprojroot_1.3-2    tools_3.6.3        magrittr_1.5       tibble_3.0.1      
+#> [45] crayon_1.3.4       pkgconfig_2.0.3    ellipsis_0.3.0     data.table_1.13.2 
+#> [49] assertthat_0.2.1   rmarkdown_2.1      rstudioapi_0.11    iterators_1.0.13  
+#> [53] R6_2.4.1           units_0.6-7        compiler_3.6.3
+
+
+ + + +
+ + + +
+ +
+

Site built with pkgdown 1.6.1.

+
+ +
+
+ + + + + + diff --git a/docs/articles/footsteps_files/figure-html/fsFig1-1.png b/docs/articles/footsteps_files/figure-html/fsFig1-1.png new file mode 100644 index 0000000..83070fd Binary files /dev/null and b/docs/articles/footsteps_files/figure-html/fsFig1-1.png differ diff --git a/docs/articles/footsteps_files/figure-html/fsFig2-1.png b/docs/articles/footsteps_files/figure-html/fsFig2-1.png new file mode 100644 index 0000000..bab70e7 Binary files /dev/null and b/docs/articles/footsteps_files/figure-html/fsFig2-1.png differ diff --git a/docs/articles/footsteps_files/figure-html/fsFig3-1.png b/docs/articles/footsteps_files/figure-html/fsFig3-1.png new file mode 100644 index 0000000..0fb1c21 Binary files /dev/null and b/docs/articles/footsteps_files/figure-html/fsFig3-1.png differ diff --git a/docs/articles/footsteps_files/figure-html/fsFig4-1.png b/docs/articles/footsteps_files/figure-html/fsFig4-1.png new file mode 100644 index 0000000..251403c Binary files /dev/null and b/docs/articles/footsteps_files/figure-html/fsFig4-1.png differ diff --git a/docs/articles/footsteps_files/figure-html/fsFig5-1.png b/docs/articles/footsteps_files/figure-html/fsFig5-1.png new file mode 100644 index 0000000..d63ad87 Binary files /dev/null and b/docs/articles/footsteps_files/figure-html/fsFig5-1.png differ diff --git a/docs/articles/footsteps_files/figure-html/fsFig6-1.png b/docs/articles/footsteps_files/figure-html/fsFig6-1.png new file mode 100644 index 0000000..1d32684 Binary files /dev/null and b/docs/articles/footsteps_files/figure-html/fsFig6-1.png differ diff --git a/docs/articles/footsteps_files/figure-html/fsFig7-1.png b/docs/articles/footsteps_files/figure-html/fsFig7-1.png new file mode 100644 index 0000000..fd68811 Binary files /dev/null and b/docs/articles/footsteps_files/figure-html/fsFig7-1.png differ diff --git a/docs/articles/index.html b/docs/articles/index.html new file mode 100644 index 0000000..edf39e2 --- /dev/null +++ b/docs/articles/index.html @@ -0,0 +1,166 @@ + + + + + + + + +Articles • foot + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + +
+ +
+
+ + +
+

All vignettes

+

+ +
+
Gridded footprint calculation layers
+

Building footprints or polygons representing the outline shape of structures can provide a unique data source for studying urban areas at high spatial resolutions. This vignette will demonstrate how to use the foot package to produce high-spatial resolution gridded data layers of geometric and morphological measures, potentially for large regions.

+
Using custom summary functions in `foot`
+

Building footprints or polygons representing the outline shape of structures can provide a unique data source for studying urban areas at high spatial resolutions. This vignette will demonstrate how to supply custom functions to the foot package to extend the range of potential summary metrics.

+
Basic building footprint calculations
+

Building footprints or polygons representing the outline shape of structures can provide a unique data source for studying urban areas at high spatial resolutions. This vignette will introduce the foot package and demonstrate some of the tools provided to calculate summary measures of geometric properties and to produce tabular, zonal, and gridded outputs.

+
+
+
+
+ + +
+ + +
+

Site built with pkgdown 1.6.1.

+
+ +
+
+ + + + + + + + diff --git a/docs/authors.html b/docs/authors.html new file mode 100644 index 0000000..96d9cf2 --- /dev/null +++ b/docs/authors.html @@ -0,0 +1,178 @@ + + + + + + + + +Authors • foot + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + +
+ +
+
+ + +
    +
  • +

    WorldPop Research Group, University of Southampton. Author. +

    +
  • +
  • +

    Chris Jochem. Contributor, maintainer. +

    +
  • +
  • +

    Claire Dooley. Contributor. +

    +
  • +
  • +

    Doug Leasure. Contributor. +

    +
  • +
  • +

    Edith Darin. Contributor. +

    +
  • +
+ +
+ +
+ + + +
+ + +
+

Site built with pkgdown 1.6.1.

+
+ +
+
+ + + + + + + + diff --git a/docs/bootstrap-toc.css b/docs/bootstrap-toc.css new file mode 100644 index 0000000..5a85941 --- /dev/null +++ b/docs/bootstrap-toc.css @@ -0,0 +1,60 @@ +/*! + * Bootstrap Table of Contents v0.4.1 (http://afeld.github.io/bootstrap-toc/) + * Copyright 2015 Aidan Feldman + * Licensed under MIT (https://github.com/afeld/bootstrap-toc/blob/gh-pages/LICENSE.md) */ + +/* modified from https://github.com/twbs/bootstrap/blob/94b4076dd2efba9af71f0b18d4ee4b163aa9e0dd/docs/assets/css/src/docs.css#L548-L601 */ + +/* All levels of nav */ +nav[data-toggle='toc'] .nav > li > a { + display: block; + padding: 4px 20px; + font-size: 13px; + font-weight: 500; + color: #767676; +} +nav[data-toggle='toc'] .nav > li > a:hover, +nav[data-toggle='toc'] .nav > li > a:focus { + padding-left: 19px; + color: #563d7c; + text-decoration: none; + background-color: transparent; + border-left: 1px solid #563d7c; +} +nav[data-toggle='toc'] .nav > .active > a, +nav[data-toggle='toc'] .nav > .active:hover > a, +nav[data-toggle='toc'] .nav > .active:focus > a { + padding-left: 18px; + font-weight: bold; + color: #563d7c; + background-color: transparent; + border-left: 2px solid #563d7c; +} + +/* Nav: second level (shown on .active) */ +nav[data-toggle='toc'] .nav .nav { + display: none; /* Hide by default, but at >768px, show it */ + padding-bottom: 10px; +} +nav[data-toggle='toc'] .nav .nav > li > a { + padding-top: 1px; + padding-bottom: 1px; + padding-left: 30px; + font-size: 12px; + font-weight: normal; +} +nav[data-toggle='toc'] .nav .nav > li > a:hover, +nav[data-toggle='toc'] .nav .nav > li > a:focus { + padding-left: 29px; +} +nav[data-toggle='toc'] .nav .nav > .active > a, +nav[data-toggle='toc'] .nav .nav > .active:hover > a, +nav[data-toggle='toc'] .nav .nav > .active:focus > a { + padding-left: 28px; + font-weight: 500; +} + +/* from https://github.com/twbs/bootstrap/blob/e38f066d8c203c3e032da0ff23cd2d6098ee2dd6/docs/assets/css/src/docs.css#L631-L634 */ +nav[data-toggle='toc'] .nav > .active > ul { + display: block; +} diff --git a/docs/bootstrap-toc.js b/docs/bootstrap-toc.js new file mode 100644 index 0000000..1cdd573 --- /dev/null +++ b/docs/bootstrap-toc.js @@ -0,0 +1,159 @@ +/*! + * Bootstrap Table of Contents v0.4.1 (http://afeld.github.io/bootstrap-toc/) + * Copyright 2015 Aidan Feldman + * Licensed under MIT (https://github.com/afeld/bootstrap-toc/blob/gh-pages/LICENSE.md) */ +(function() { + 'use strict'; + + window.Toc = { + helpers: { + // return all matching elements in the set, or their descendants + findOrFilter: function($el, selector) { + // http://danielnouri.org/notes/2011/03/14/a-jquery-find-that-also-finds-the-root-element/ + // http://stackoverflow.com/a/12731439/358804 + var $descendants = $el.find(selector); + return $el.filter(selector).add($descendants).filter(':not([data-toc-skip])'); + }, + + generateUniqueIdBase: function(el) { + var text = $(el).text(); + var anchor = text.trim().toLowerCase().replace(/[^A-Za-z0-9]+/g, '-'); + return anchor || el.tagName.toLowerCase(); + }, + + generateUniqueId: function(el) { + var anchorBase = this.generateUniqueIdBase(el); + for (var i = 0; ; i++) { + var anchor = anchorBase; + if (i > 0) { + // add suffix + anchor += '-' + i; + } + // check if ID already exists + if (!document.getElementById(anchor)) { + return anchor; + } + } + }, + + generateAnchor: function(el) { + if (el.id) { + return el.id; + } else { + var anchor = this.generateUniqueId(el); + el.id = anchor; + return anchor; + } + }, + + createNavList: function() { + return $(''); + }, + + createChildNavList: function($parent) { + var $childList = this.createNavList(); + $parent.append($childList); + return $childList; + }, + + generateNavEl: function(anchor, text) { + var $a = $(''); + $a.attr('href', '#' + anchor); + $a.text(text); + var $li = $('
  • '); + $li.append($a); + return $li; + }, + + generateNavItem: function(headingEl) { + var anchor = this.generateAnchor(headingEl); + var $heading = $(headingEl); + var text = $heading.data('toc-text') || $heading.text(); + return this.generateNavEl(anchor, text); + }, + + // Find the first heading level (`

    `, then `

    `, etc.) that has more than one element. Defaults to 1 (for `

    `). + getTopLevel: function($scope) { + for (var i = 1; i <= 6; i++) { + var $headings = this.findOrFilter($scope, 'h' + i); + if ($headings.length > 1) { + return i; + } + } + + return 1; + }, + + // returns the elements for the top level, and the next below it + getHeadings: function($scope, topLevel) { + var topSelector = 'h' + topLevel; + + var secondaryLevel = topLevel + 1; + var secondarySelector = 'h' + secondaryLevel; + + return this.findOrFilter($scope, topSelector + ',' + secondarySelector); + }, + + getNavLevel: function(el) { + return parseInt(el.tagName.charAt(1), 10); + }, + + populateNav: function($topContext, topLevel, $headings) { + var $context = $topContext; + var $prevNav; + + var helpers = this; + $headings.each(function(i, el) { + var $newNav = helpers.generateNavItem(el); + var navLevel = helpers.getNavLevel(el); + + // determine the proper $context + if (navLevel === topLevel) { + // use top level + $context = $topContext; + } else if ($prevNav && $context === $topContext) { + // create a new level of the tree and switch to it + $context = helpers.createChildNavList($prevNav); + } // else use the current $context + + $context.append($newNav); + + $prevNav = $newNav; + }); + }, + + parseOps: function(arg) { + var opts; + if (arg.jquery) { + opts = { + $nav: arg + }; + } else { + opts = arg; + } + opts.$scope = opts.$scope || $(document.body); + return opts; + } + }, + + // accepts a jQuery object, or an options object + init: function(opts) { + opts = this.helpers.parseOps(opts); + + // ensure that the data attribute is in place for styling + opts.$nav.attr('data-toggle', 'toc'); + + var $topContext = this.helpers.createChildNavList(opts.$nav); + var topLevel = this.helpers.getTopLevel(opts.$scope); + var $headings = this.helpers.getHeadings(opts.$scope, topLevel); + this.helpers.populateNav($topContext, topLevel, $headings); + } + }; + + $(function() { + $('nav[data-toggle="toc"]').each(function(i, el) { + var $nav = $(el); + Toc.init($nav); + }); + }); +})(); diff --git a/docs/docsearch.css b/docs/docsearch.css new file mode 100644 index 0000000..e5f1fe1 --- /dev/null +++ b/docs/docsearch.css @@ -0,0 +1,148 @@ +/* Docsearch -------------------------------------------------------------- */ +/* + Source: https://github.com/algolia/docsearch/ + License: MIT +*/ + +.algolia-autocomplete { + display: block; + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1 +} + +.algolia-autocomplete .ds-dropdown-menu { + width: 100%; + min-width: none; + max-width: none; + padding: .75rem 0; + background-color: #fff; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, .1); + box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .175); +} + +@media (min-width:768px) { + .algolia-autocomplete .ds-dropdown-menu { + width: 175% + } +} + +.algolia-autocomplete .ds-dropdown-menu::before { + display: none +} + +.algolia-autocomplete .ds-dropdown-menu [class^=ds-dataset-] { + padding: 0; + background-color: rgb(255,255,255); + border: 0; + max-height: 80vh; +} + +.algolia-autocomplete .ds-dropdown-menu .ds-suggestions { + margin-top: 0 +} + +.algolia-autocomplete .algolia-docsearch-suggestion { + padding: 0; + overflow: visible +} + +.algolia-autocomplete .algolia-docsearch-suggestion--category-header { + padding: .125rem 1rem; + margin-top: 0; + font-size: 1.3em; + font-weight: 500; + color: #00008B; + border-bottom: 0 +} + +.algolia-autocomplete .algolia-docsearch-suggestion--wrapper { + float: none; + padding-top: 0 +} + +.algolia-autocomplete .algolia-docsearch-suggestion--subcategory-column { + float: none; + width: auto; + padding: 0; + text-align: left +} + +.algolia-autocomplete .algolia-docsearch-suggestion--content { + float: none; + width: auto; + padding: 0 +} + +.algolia-autocomplete .algolia-docsearch-suggestion--content::before { + display: none +} + +.algolia-autocomplete .ds-suggestion:not(:first-child) .algolia-docsearch-suggestion--category-header { + padding-top: .75rem; + margin-top: .75rem; + border-top: 1px solid rgba(0, 0, 0, .1) +} + +.algolia-autocomplete .ds-suggestion .algolia-docsearch-suggestion--subcategory-column { + display: block; + padding: .1rem 1rem; + margin-bottom: 0.1; + font-size: 1.0em; + font-weight: 400 + /* display: none */ +} + +.algolia-autocomplete .algolia-docsearch-suggestion--title { + display: block; + padding: .25rem 1rem; + margin-bottom: 0; + font-size: 0.9em; + font-weight: 400 +} + +.algolia-autocomplete .algolia-docsearch-suggestion--text { + padding: 0 1rem .5rem; + margin-top: -.25rem; + font-size: 0.8em; + font-weight: 400; + line-height: 1.25 +} + +.algolia-autocomplete .algolia-docsearch-footer { + width: 110px; + height: 20px; + z-index: 3; + margin-top: 10.66667px; + float: right; + font-size: 0; + line-height: 0; +} + +.algolia-autocomplete .algolia-docsearch-footer--logo { + background-image: url("data:image/svg+xml;utf8,"); + background-repeat: no-repeat; + background-position: 50%; + background-size: 100%; + overflow: hidden; + text-indent: -9000px; + width: 100%; + height: 100%; + display: block; + transform: translate(-8px); +} + +.algolia-autocomplete .algolia-docsearch-suggestion--highlight { + color: #FF8C00; + background: rgba(232, 189, 54, 0.1) +} + + +.algolia-autocomplete .algolia-docsearch-suggestion--text .algolia-docsearch-suggestion--highlight { + box-shadow: inset 0 -2px 0 0 rgba(105, 105, 105, .5) +} + +.algolia-autocomplete .ds-suggestion.ds-cursor .algolia-docsearch-suggestion--content { + background-color: rgba(192, 192, 192, .15) +} diff --git a/docs/docsearch.js b/docs/docsearch.js new file mode 100644 index 0000000..b35504c --- /dev/null +++ b/docs/docsearch.js @@ -0,0 +1,85 @@ +$(function() { + + // register a handler to move the focus to the search bar + // upon pressing shift + "/" (i.e. "?") + $(document).on('keydown', function(e) { + if (e.shiftKey && e.keyCode == 191) { + e.preventDefault(); + $("#search-input").focus(); + } + }); + + $(document).ready(function() { + // do keyword highlighting + /* modified from https://jsfiddle.net/julmot/bL6bb5oo/ */ + var mark = function() { + + var referrer = document.URL ; + var paramKey = "q" ; + + if (referrer.indexOf("?") !== -1) { + var qs = referrer.substr(referrer.indexOf('?') + 1); + var qs_noanchor = qs.split('#')[0]; + var qsa = qs_noanchor.split('&'); + var keyword = ""; + + for (var i = 0; i < qsa.length; i++) { + var currentParam = qsa[i].split('='); + + if (currentParam.length !== 2) { + continue; + } + + if (currentParam[0] == paramKey) { + keyword = decodeURIComponent(currentParam[1].replace(/\+/g, "%20")); + } + } + + if (keyword !== "") { + $(".contents").unmark({ + done: function() { + $(".contents").mark(keyword); + } + }); + } + } + }; + + mark(); + }); +}); + +/* Search term highlighting ------------------------------*/ + +function matchedWords(hit) { + var words = []; + + var hierarchy = hit._highlightResult.hierarchy; + // loop to fetch from lvl0, lvl1, etc. + for (var idx in hierarchy) { + words = words.concat(hierarchy[idx].matchedWords); + } + + var content = hit._highlightResult.content; + if (content) { + words = words.concat(content.matchedWords); + } + + // return unique words + var words_uniq = [...new Set(words)]; + return words_uniq; +} + +function updateHitURL(hit) { + + var words = matchedWords(hit); + var url = ""; + + if (hit.anchor) { + url = hit.url_without_anchor + '?q=' + escape(words.join(" ")) + '#' + hit.anchor; + } else { + url = hit.url + '?q=' + escape(words.join(" ")); + } + + return url; +} diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..679a5ea --- /dev/null +++ b/docs/index.html @@ -0,0 +1,291 @@ + + + + + + + +An R package for processing building footprints morphometrics • foot + + + + + + + + + + +
    +
    + + + + +
    +
    +
    + +

    WorldPop Research Group, University of Southampton

    +

    While remote sensing has long been used to monitor urbanisation patterns, in recent years there has been an increasing availability in finer resolution satellite data covering large areas of the globe. This very high resolution imagery (often <1 m spatial resolution), combined with increased computing power is producing new datasets on urban areas. In particular, machine learning algorithms are being applied to detect, automatically extract, and map full sets of building features in a scene. These automated methods add to the manually digitised information such as from OpenStreetMap and the property datasets available from some city governments.

    +

    Such building footprint datasets provide a new level of detail on urban areas, particularly in places which might otherwise lack detailed maps on cities and rapidly growing areas. Despite their spatial detail, building footprints typically lack any other attribute information to help differentiate land uses or other neighbourhood characteristics. However, the size, shape, orientation, and clustering of structures produces a spatial pattern that can suggest areas of different land use or activities.

    +

    The foot package is designed to provide a set of consistent and flexible tools for processing 2D vector representations of buildings and calculating urban morphology measurements. The functionality includes basic geometry and morphology measures, distance and clustering metrics. These calculations are supported with helper functions for spatial intersections and tiled reading/writing of data.

    +
    +

    +Installation

    +

    The foot package can be installed directly from Github.

    +
    +devtools::install_github("wgpg/foot", build_vignettes=TRUE)
    +

    Note that building and running the code may require additional packages: stars, raster, sf, data.table, lwgeom, mmap. et al.

    +
    +
    +

    +Quick Start

    +

    A sample dataset of building footprints is provided:

    +
    # load the sample
    +data("kampala", package="foot")
    +
    +# 2D vector building polygons
    +kampala$buildings
    +
    +

    +Vignettes

    +

    Vignettes are provided as an introduction to foot. The vignette on basic usage is available from vignette("footsteps", package="foot"). The supplied datasets can be used to replicate this vignette. For a discussion and example of creating gridded data layers, see vignette("bigfoot", package="foot"). Finally, techniques for using custom morphology metric functions with foot is demonstrated in vignette("cobbler", package="foot"). These vignettes are also available from this package website.

    +
    +
    +

    +Basic Usage

    +
    +library(foot)
    +
    +# load sample data
    +data("kampala", package="foot")
    +buildings <- kampala$buildings
    +zones <- kampala$adminZones
    +grid <- kampala$mastergrid
    +

    The foot package provides tools to calculate and summarise building morphology measures at multiple scales. These include building-level geometry measures.

    +
    +# building-level metrics
    +buildings$built_area <- calculate_footstats(buildings, what="area")
    +#> Selecting metrics 
    +#> Setting control values. 
    +#> Pre-calculating areas 
    +#> No summary functions found, returning metrics.
    +  head(buildings)
    +#> Simple feature collection with 6 features and 2 fields
    +#> geometry type:  POLYGON
    +#> dimension:      XY
    +#> bbox:           xmin: 32.60765 ymin: 0.341117 xmax: 32.61288 ymax: 0.345773
    +#> geographic CRS: WGS 84
    +#>   FID_1                       geometry            area
    +#> 1   130 POLYGON ((32.61282 0.341132...  22.00824 [m^2]
    +#> 2   132 POLYGON ((32.61229 0.341693... 220.39011 [m^2]
    +#> 3   133 POLYGON ((32.60817 0.342753...  38.95750 [m^2]
    +#> 4   135 POLYGON ((32.60808 0.343578... 386.74429 [m^2]
    +#> 5   137 POLYGON ((32.60786 0.344552... 349.57765 [m^2]
    +#> 6   138 POLYGON ((32.60765 0.345604... 164.00931 [m^2]
    +

    As well as area-level summaries within spatial zones.

    +
    +# Area-level summary metrics
    +# Optionally, create an index for the buildings to zones
    +building_zone <- zonalIndex(buildings, 
    +                            zones, 
    +                            zoneField = "Id", 
    +                            returnObject = TRUE)
    +
    +# summarise metrics within small areal units
    +admin_area <- calculate_footstats(building_zone, 
    +                                  zone="Id", 
    +                                  what="area", how="mean")
    +#> Selecting metrics 
    +#> Setting control values. 
    +#> Creating zonal index 
    +#> Pre-calculating areas 
    +#> 
    +#> Calculating  1  metrics ... 
    +#>    area mean  
    +#> Finished calculating metrics.
    +  head(admin_area)
    +#>    Id       area_mean
    +#> 1:  1  402.5984 [m^2]
    +#> 2:  2  211.0534 [m^2]
    +#> 3:  3  525.0747 [m^2]
    +#> 4:  4  555.0931 [m^2]
    +#> 5:  5  568.7154 [m^2]
    +#> 6:  6 1021.9529 [m^2]
    +

    Or gridded summary outputs, with the options to include a circular focal window.

    +
    +# calculated along a raster within a circular focal window
    +gridded <- calculate_bigfoot(buildings, 
    +                             what="area", how="mean",
    +                             focalRadius=200,
    +                             template=grid,
    +                             outputPath=tempdir())
    +#> Selecting metrics 
    +#> Setting control values. 
    +#> Creating template output grids 
    +#> Creating list of processing tiles 
    +#> Setting up cluster...
    +#> Begin parallel tile processing: 2020-10-21 16:14:52
    +#> 
    +#> Finished processing all tiles: 2020-10-21 16:14:56
    +
    +  raster::plot(raster::raster(gridded))
    +  plot(sf::st_geometry(buildings), add=TRUE)
    +

    +
    +
    +

    +Outputs

    +

    Rasters in GeoTiff format or data tables:

    +
      +
    1. Binary settlement indicators
    2. +
    3. Counts of structures
    4. +
    5. Building area
    6. +
    7. Building perimeter
    8. +
    9. Nearest neighbour distance
    10. +
    11. Nearest neighbour index
    12. +
    13. Structure orientation angle (normalised entropy)
    14. +
    15. Compactness (Polsby-Popper)
    16. +
    17. Roundness
    18. +
    +

    A full list of characteristics and summary function names can be retrieved with foot::list_fs().

    +
    +
    +
    +

    +Contributions

    +

    Contributions are welcome. Raise or respond to an issue, or create a new branch to develop a feature/modification and submit a pull request.

    +
    +
    +

    +Acknowledgements

    +
    +citation("foot")
    +#> 
    +#> To cite package 'foot' in publications use:
    +#> 
    +#>   WorldPop Research Group, University of Southampton (2020). foot: An R package for processing building footprints morphometrics. R package version 0.5.
    +#>   https://github.com/wpgp/foot
    +#> 
    +#> A BibTeX entry for LaTeX users is
    +#> 
    +#>   @Manual{,
    +#>     title = {foot: An R package for processing building footprints morphometrics},
    +#>     author = {{WorldPop Research Group, University of Southampton}},
    +#>     year = {2020},
    +#>     note = {R package version 0.5},
    +#>     url = {https://github.com/wpgp/foot},
    +#>   }
    +

    This work was undertaken by members of the WorldPop Research Group at the University of Southampton (Chris Jochem, Edith Darín, Claire Dooley, Doug Leasure) with support from Andy Tatem and Attila Lazar. Funding support comes from the Bill and Melinda Gates Foundation and the United Kingdom Foreign, Commonwealth & Development Office as part of the Geo-Referenced Infrastructure and Demographic Data for Development project (GRID3) (OPP1182408). Project partners in GRID3 include the WorldPop Research Group, the United Nations Population Fund, the Flowminder Foundation, and the Center for International Earth Science Information Network within the Earth Institute at Columbia University.

    +
    +
    +
    + + +
    + + +
    + +
    +

    Site built with pkgdown 1.6.1.

    +
    + +
    +
    + + + + + + diff --git a/docs/link.svg b/docs/link.svg new file mode 100644 index 0000000..88ad827 --- /dev/null +++ b/docs/link.svg @@ -0,0 +1,12 @@ + + + + + + diff --git a/docs/pkgdown.css b/docs/pkgdown.css new file mode 100644 index 0000000..1273238 --- /dev/null +++ b/docs/pkgdown.css @@ -0,0 +1,367 @@ +/* Sticky footer */ + +/** + * Basic idea: https://philipwalton.github.io/solved-by-flexbox/demos/sticky-footer/ + * Details: https://github.com/philipwalton/solved-by-flexbox/blob/master/assets/css/components/site.css + * + * .Site -> body > .container + * .Site-content -> body > .container .row + * .footer -> footer + * + * Key idea seems to be to ensure that .container and __all its parents__ + * have height set to 100% + * + */ + +html, body { + height: 100%; +} + +body { + position: relative; +} + +body > .container { + display: flex; + height: 100%; + flex-direction: column; +} + +body > .container .row { + flex: 1 0 auto; +} + +footer { + margin-top: 45px; + padding: 35px 0 36px; + border-top: 1px solid #e5e5e5; + color: #666; + display: flex; + flex-shrink: 0; +} +footer p { + margin-bottom: 0; +} +footer div { + flex: 1; +} +footer .pkgdown { + text-align: right; +} +footer p { + margin-bottom: 0; +} + +img.icon { + float: right; +} + +img { + max-width: 100%; +} + +/* Fix bug in bootstrap (only seen in firefox) */ +summary { + display: list-item; +} + +/* Typographic tweaking ---------------------------------*/ + +.contents .page-header { + margin-top: calc(-60px + 1em); +} + +dd { + margin-left: 3em; +} + +/* Section anchors ---------------------------------*/ + +a.anchor { + margin-left: -30px; + display:inline-block; + width: 30px; + height: 30px; + visibility: hidden; + + background-image: url(./link.svg); + background-repeat: no-repeat; + background-size: 20px 20px; + background-position: center center; +} + +.hasAnchor:hover a.anchor { + visibility: visible; +} + +@media (max-width: 767px) { + .hasAnchor:hover a.anchor { + visibility: hidden; + } +} + + +/* Fixes for fixed navbar --------------------------*/ + +.contents h1, .contents h2, .contents h3, .contents h4 { + padding-top: 60px; + margin-top: -40px; +} + +/* Navbar submenu --------------------------*/ + +.dropdown-submenu { + position: relative; +} + +.dropdown-submenu>.dropdown-menu { + top: 0; + left: 100%; + margin-top: -6px; + margin-left: -1px; + border-radius: 0 6px 6px 6px; +} + +.dropdown-submenu:hover>.dropdown-menu { + display: block; +} + +.dropdown-submenu>a:after { + display: block; + content: " "; + float: right; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; + border-width: 5px 0 5px 5px; + border-left-color: #cccccc; + margin-top: 5px; + margin-right: -10px; +} + +.dropdown-submenu:hover>a:after { + border-left-color: #ffffff; +} + +.dropdown-submenu.pull-left { + float: none; +} + +.dropdown-submenu.pull-left>.dropdown-menu { + left: -100%; + margin-left: 10px; + border-radius: 6px 0 6px 6px; +} + +/* Sidebar --------------------------*/ + +#pkgdown-sidebar { + margin-top: 30px; + position: -webkit-sticky; + position: sticky; + top: 70px; +} + +#pkgdown-sidebar h2 { + font-size: 1.5em; + margin-top: 1em; +} + +#pkgdown-sidebar h2:first-child { + margin-top: 0; +} + +#pkgdown-sidebar .list-unstyled li { + margin-bottom: 0.5em; +} + +/* bootstrap-toc tweaks ------------------------------------------------------*/ + +/* All levels of nav */ + +nav[data-toggle='toc'] .nav > li > a { + padding: 4px 20px 4px 6px; + font-size: 1.5rem; + font-weight: 400; + color: inherit; +} + +nav[data-toggle='toc'] .nav > li > a:hover, +nav[data-toggle='toc'] .nav > li > a:focus { + padding-left: 5px; + color: inherit; + border-left: 1px solid #878787; +} + +nav[data-toggle='toc'] .nav > .active > a, +nav[data-toggle='toc'] .nav > .active:hover > a, +nav[data-toggle='toc'] .nav > .active:focus > a { + padding-left: 5px; + font-size: 1.5rem; + font-weight: 400; + color: inherit; + border-left: 2px solid #878787; +} + +/* Nav: second level (shown on .active) */ + +nav[data-toggle='toc'] .nav .nav { + display: none; /* Hide by default, but at >768px, show it */ + padding-bottom: 10px; +} + +nav[data-toggle='toc'] .nav .nav > li > a { + padding-left: 16px; + font-size: 1.35rem; +} + +nav[data-toggle='toc'] .nav .nav > li > a:hover, +nav[data-toggle='toc'] .nav .nav > li > a:focus { + padding-left: 15px; +} + +nav[data-toggle='toc'] .nav .nav > .active > a, +nav[data-toggle='toc'] .nav .nav > .active:hover > a, +nav[data-toggle='toc'] .nav .nav > .active:focus > a { + padding-left: 15px; + font-weight: 500; + font-size: 1.35rem; +} + +/* orcid ------------------------------------------------------------------- */ + +.orcid { + font-size: 16px; + color: #A6CE39; + /* margins are required by official ORCID trademark and display guidelines */ + margin-left:4px; + margin-right:4px; + vertical-align: middle; +} + +/* Reference index & topics ----------------------------------------------- */ + +.ref-index th {font-weight: normal;} + +.ref-index td {vertical-align: top; min-width: 100px} +.ref-index .icon {width: 40px;} +.ref-index .alias {width: 40%;} +.ref-index-icons .alias {width: calc(40% - 40px);} +.ref-index .title {width: 60%;} + +.ref-arguments th {text-align: right; padding-right: 10px;} +.ref-arguments th, .ref-arguments td {vertical-align: top; min-width: 100px} +.ref-arguments .name {width: 20%;} +.ref-arguments .desc {width: 80%;} + +/* Nice scrolling for wide elements --------------------------------------- */ + +table { + display: block; + overflow: auto; +} + +/* Syntax highlighting ---------------------------------------------------- */ + +pre { + word-wrap: normal; + word-break: normal; + border: 1px solid #eee; +} + +pre, code { + background-color: #f8f8f8; + color: #333; +} + +pre code { + overflow: auto; + word-wrap: normal; + white-space: pre; +} + +pre .img { + margin: 5px 0; +} + +pre .img img { + background-color: #fff; + display: block; + height: auto; +} + +code a, pre a { + color: #375f84; +} + +a.sourceLine:hover { + text-decoration: none; +} + +.fl {color: #1514b5;} +.fu {color: #000000;} /* function */ +.ch,.st {color: #036a07;} /* string */ +.kw {color: #264D66;} /* keyword */ +.co {color: #888888;} /* comment */ + +.message { color: black; font-weight: bolder;} +.error { color: orange; font-weight: bolder;} +.warning { color: #6A0366; font-weight: bolder;} + +/* Clipboard --------------------------*/ + +.hasCopyButton { + position: relative; +} + +.btn-copy-ex { + position: absolute; + right: 0; + top: 0; + visibility: hidden; +} + +.hasCopyButton:hover button.btn-copy-ex { + visibility: visible; +} + +/* headroom.js ------------------------ */ + +.headroom { + will-change: transform; + transition: transform 200ms linear; +} +.headroom--pinned { + transform: translateY(0%); +} +.headroom--unpinned { + transform: translateY(-100%); +} + +/* mark.js ----------------------------*/ + +mark { + background-color: rgba(255, 255, 51, 0.5); + border-bottom: 2px solid rgba(255, 153, 51, 0.3); + padding: 1px; +} + +/* vertical spacing after htmlwidgets */ +.html-widget { + margin-bottom: 10px; +} + +/* fontawesome ------------------------ */ + +.fab { + font-family: "Font Awesome 5 Brands" !important; +} + +/* don't display links in code chunks when printing */ +/* source: https://stackoverflow.com/a/10781533 */ +@media print { + code a:link:after, code a:visited:after { + content: ""; + } +} diff --git a/docs/pkgdown.js b/docs/pkgdown.js new file mode 100644 index 0000000..7e7048f --- /dev/null +++ b/docs/pkgdown.js @@ -0,0 +1,108 @@ +/* http://gregfranko.com/blog/jquery-best-practices/ */ +(function($) { + $(function() { + + $('.navbar-fixed-top').headroom(); + + $('body').css('padding-top', $('.navbar').height() + 10); + $(window).resize(function(){ + $('body').css('padding-top', $('.navbar').height() + 10); + }); + + $('[data-toggle="tooltip"]').tooltip(); + + var cur_path = paths(location.pathname); + var links = $("#navbar ul li a"); + var max_length = -1; + var pos = -1; + for (var i = 0; i < links.length; i++) { + if (links[i].getAttribute("href") === "#") + continue; + // Ignore external links + if (links[i].host !== location.host) + continue; + + var nav_path = paths(links[i].pathname); + + var length = prefix_length(nav_path, cur_path); + if (length > max_length) { + max_length = length; + pos = i; + } + } + + // Add class to parent
  • , and enclosing
  • if in dropdown + if (pos >= 0) { + var menu_anchor = $(links[pos]); + menu_anchor.parent().addClass("active"); + menu_anchor.closest("li.dropdown").addClass("active"); + } + }); + + function paths(pathname) { + var pieces = pathname.split("/"); + pieces.shift(); // always starts with / + + var end = pieces[pieces.length - 1]; + if (end === "index.html" || end === "") + pieces.pop(); + return(pieces); + } + + // Returns -1 if not found + function prefix_length(needle, haystack) { + if (needle.length > haystack.length) + return(-1); + + // Special case for length-0 haystack, since for loop won't run + if (haystack.length === 0) { + return(needle.length === 0 ? 0 : -1); + } + + for (var i = 0; i < haystack.length; i++) { + if (needle[i] != haystack[i]) + return(i); + } + + return(haystack.length); + } + + /* Clipboard --------------------------*/ + + function changeTooltipMessage(element, msg) { + var tooltipOriginalTitle=element.getAttribute('data-original-title'); + element.setAttribute('data-original-title', msg); + $(element).tooltip('show'); + element.setAttribute('data-original-title', tooltipOriginalTitle); + } + + if(ClipboardJS.isSupported()) { + $(document).ready(function() { + var copyButton = ""; + + $(".examples, div.sourceCode").addClass("hasCopyButton"); + + // Insert copy buttons: + $(copyButton).prependTo(".hasCopyButton"); + + // Initialize tooltips: + $('.btn-copy-ex').tooltip({container: 'body'}); + + // Initialize clipboard: + var clipboardBtnCopies = new ClipboardJS('[data-clipboard-copy]', { + text: function(trigger) { + return trigger.parentNode.textContent; + } + }); + + clipboardBtnCopies.on('success', function(e) { + changeTooltipMessage(e.trigger, 'Copied!'); + e.clearSelection(); + }); + + clipboardBtnCopies.on('error', function() { + changeTooltipMessage(e.trigger,'Press Ctrl+C or Command+C to copy'); + }); + }); + } +})(window.jQuery || window.$) diff --git a/docs/pkgdown.yml b/docs/pkgdown.yml new file mode 100644 index 0000000..65ea3af --- /dev/null +++ b/docs/pkgdown.yml @@ -0,0 +1,9 @@ +pandoc: 2.7.3 +pkgdown: 1.6.1 +pkgdown_sha: ~ +articles: + bigfoot: bigfoot.html + cobbler: cobbler.html + footsteps: footsteps.html +last_built: 2020-10-23T11:14Z + diff --git a/docs/reference/Rplot001.png b/docs/reference/Rplot001.png new file mode 100644 index 0000000..17a3580 Binary files /dev/null and b/docs/reference/Rplot001.png differ diff --git a/docs/reference/calculate_bigfoot.html b/docs/reference/calculate_bigfoot.html new file mode 100644 index 0000000..a3627dd --- /dev/null +++ b/docs/reference/calculate_bigfoot.html @@ -0,0 +1,392 @@ + + + + + + + + +calculate_bigfoot: Gridded feature statistics for large sets of + building footprint polygons — calculate_bigfoot • foot + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    +
    + + +
    +

    Calculate selected metrics of building footprints for + high-spatial resolution gridded outputs.

    +
    + +
    calculate_bigfoot(
    +  X,
    +  what = "all",
    +  how = "all",
    +  focalRadius = 0,
    +  controlZone = list(zoneName = "zoneID", method = "centroid"),
    +  controlUnits = list(areaUnit = "m^2", perimUnit = "m", distUnit = "m"),
    +  controlDist = list(maxSearch = 100, method = "centroid", unit =
    +    controlUnits$distUnit),
    +  filter = list(minArea = NULL, maxArea = NULL),
    +  template = NULL,
    +  tileSize = c(500, 500),
    +  parallel = TRUE,
    +  nCores = max(1, parallel::detectCores() - 1),
    +  outputPath = getwd(),
    +  outputTag = NULL,
    +  tries = 100,
    +  verbose = TRUE
    +)
    +
    +# S3 method for sf
    +calculate_bigfoot(
    +  X,
    +  what = "all",
    +  how = "all",
    +  focalRadius = 0,
    +  controlZone = list(zoneName = "zoneID", method = "centroid"),
    +  controlUnits = list(areaUnit = "m^2", perimUnit = "m", distUnit = "m"),
    +  controlDist = list(maxSearch = 100, method = "centroid", unit =
    +    controlUnits$distUnit),
    +  filter = list(minArea = NULL, maxArea = NULL),
    +  template = NULL,
    +  tileSize = c(500, 500),
    +  parallel = TRUE,
    +  nCores = max(1, parallel::detectCores() - 1),
    +  outputPath = getwd(),
    +  outputTag = NULL,
    +  tries = 100,
    +  verbose = TRUE
    +)
    +
    +# S3 method for sp
    +calculate_bigfoot(
    +  X,
    +  what = "all",
    +  how = "all",
    +  focalRadius = 0,
    +  controlZone = list(zoneName = "zoneID", method = "centroid"),
    +  controlUnits = list(areaUnit = "m^2", perimUnit = "m", distUnit = "m"),
    +  controlDist = list(maxSearch = 100, method = "centroid", unit =
    +    controlUnits$distUnit),
    +  filter = list(minArea = NULL, maxArea = NULL),
    +  template = NULL,
    +  tileSize = c(500, 500),
    +  parallel = TRUE,
    +  nCores = max(1, parallel::detectCores() - 1),
    +  outputPath = getwd(),
    +  outputTag = NULL,
    +  tries = 100,
    +  verbose = TRUE
    +)
    +
    +# S3 method for character
    +calculate_bigfoot(
    +  X,
    +  what = "all",
    +  how = "all",
    +  focalRadius = 0,
    +  controlZone = list(zoneName = "zoneID", method = "centroid"),
    +  controlUnits = list(areaUnit = "m^2", perimUnit = "m", distUnit = "m"),
    +  controlDist = list(maxSearch = 100, method = "centroid", unit =
    +    controlUnits$distUnit),
    +  filter = list(minArea = NULL, maxArea = NULL),
    +  template = NULL,
    +  tileSize = c(500, 500),
    +  parallel = TRUE,
    +  nCores = max(1, parallel::detectCores() - 1),
    +  outputPath = getwd(),
    +  outputTag = NULL,
    +  tries = 100,
    +  verbose = TRUE
    +)
    + +

    Arguments

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    X

    object with building footprint polygons. This argument can take +multiple spatial types, including sf and sp, or a filepath +string to a file, or a list where each member provides a spatial object or +a filepath string.

    what

    list of strings naming the columns or built-in geometry measures to +calculate for each footprint. Other options include 'all' or +'nodist' to calculate all available characteristics and all except +nearest-neighbour distance metrics.

    how

    list of strings naming functions to be used to calculate summary +statistics. The functions can be built-in functions (e.g. "mean","sd"), or +user-defined function names.

    focalRadius

    numeric. Distance in meters for a buffer around each +template pixel. Creates a focal processing window for metrics.

    controlZone

    (optional) named list. Setting controls passed on to +zonalIndex. Elements can include zoneName and +method.

    controlUnits

    (optional) named list. Elements can include +areaUnit, perimUnit, and distUnit. The values for +these items should be strings that can be coerced into a units +object.

    controlDist

    (optional) named list to override default +options for distance calculations. Elements can include maxSearch +and method. Ignored if metrics does not include a distance +calculation. See fs_nndist.

    filter

    (optional) named list with minArea and maxArea. +These are numeric values to filter footprints prior to processing. Default +values are NULL and do not filter any records.

    template

    (optional). When creating a gridded output, a supplied +stars or raster dataset to align the data.

    tileSize

    number of pixels per side of a tile. Can be a vector of +length 2 (rows, column pixels). Default is c(500, 500).

    parallel

    logical. Should a parallel backend be used to process the +tiles. Default is TRUE.

    nCores

    number of CPU cores to use if parallel is TRUE. +Default is 1 less than the available CPUs.

    outputPath

    (optional). When creating a gridded output, a path for the +location of the output. Default is the temp directory.

    outputTag

    (optional). A character string that will be added to the +beginning of the output name for the gridded files.

    tries

    (optional). The number of attempts to write a tile to the output +file. Default is 100.

    verbose

    logical. Should progress messages be printed. Default +TRUE.

    + +

    Value

    + +

    Invisible. Returns a vector of paths to the output files.

    +

    Details

    + +

    calculate_bigfoot

    +

    calculate_bigfoot provides a wrapper for a workflow to + process vector polygons of structures to create a gridded output summary of + morphology measures. The function wraps calculate_footstats along + with other geometry functions of foot and read/writing functions + from stars and sf.

    +

    The suggested way of using this function is to supply character strings for + X and template rather than objects. Using strings is more + memory-efficient. This function processes based on 'tiles' or sub-regions + of the template grid and will only read in the portion of the object needed + for the calculations.

    +

    See also

    + + + +

    Examples

    +
    data("kampala", package="foot") +buildings <- kampala$buildings +templateGrid <- kampala$mastergrid + +calculate_bigfoot(X=buildings, + what=list(list("shape"), list("perimeter")), + how=list(list("mean"), list("sum")), + controlUnits=list(areaUnit="m^2"), + minArea=50, # footprints must be larger than 50 m^2 + maxArea=1000, # footprints must be smaller than 1000 m^2 + template=templateGrid, + outputPath=tempdir(), + outputTag="kampala", + parallel=FALSE, + verbose=TRUE) +
    #> Error in calculate_bigfoot(X = buildings, what = list(list("shape"), list("perimeter")), how = list(list("mean"), list("sum")), controlUnits = list(areaUnit = "m^2"), minArea = 50, maxArea = 1000, template = templateGrid, outputPath = tempdir(), outputTag = "kampala", parallel = FALSE, verbose = TRUE): unused arguments (minArea = 50, maxArea = 1000)
    +# read one of the output files and plot as a raster layer +outGrid <- raster::raster(file.path(tempdir(), "kampala_count.tif")) +
    #> Error in .local(.Object, ...) : +#>
    #> Error in .rasterObjectFromFile(x, band = band, objecttype = "RasterLayer", ...): Cannot create a RasterLayer object from this file. (file does not exist)
    raster::plot(outGrid) +
    #> Error in raster::plot(outGrid): object 'outGrid' not found
    +
    +
    + +
    + + +
    + + +
    +

    Site built with pkgdown 1.6.1.

    +
    + +
    +
    + + + + + + + + diff --git a/docs/reference/calculate_footstats.html b/docs/reference/calculate_footstats.html new file mode 100644 index 0000000..c23b3ca --- /dev/null +++ b/docs/reference/calculate_footstats.html @@ -0,0 +1,443 @@ + + + + + + + + +calculate_footstats: Feature statistics of building footprints — calculate_footstats • foot + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    +
    + + +
    +

    Calculate groups of metrics for building footprint datasets

    +
    + +
    calculate_footstats(
    +  X,
    +  zone = NULL,
    +  what = "all",
    +  how = NULL,
    +  controlZone = list(zoneName = "zoneID", method = "centroid"),
    +  controlUnits = list(areaUnit = "m^2", perimUnit = "m", distUnit = "m"),
    +  controlDist = list(maxSearch = 100, method = "centroid", unit =
    +    controlUnits$distUnit),
    +  filter = list(minArea = NULL, maxArea = NULL),
    +  verbose = TRUE
    +)
    +
    +# S3 method for sf
    +calculate_footstats(
    +  X,
    +  zone = NULL,
    +  what = "all",
    +  how = NULL,
    +  controlZone = list(zoneName = "zoneID", method = "centroid"),
    +  controlUnits = list(areaUnit = "m^2", perimUnit = "m", distUnit = "m"),
    +  controlDist = list(maxSearch = 100, method = "centroid", unit =
    +    controlUnits$distUnit),
    +  filter = list(minArea = NULL, maxArea = NULL),
    +  verbose = TRUE
    +)
    +
    +# S3 method for sfc
    +calculate_footstats(
    +  X,
    +  zone = NULL,
    +  what = "all",
    +  how = NULL,
    +  controlZone = list(zoneName = "zoneID", method = "centroid"),
    +  controlUnits = list(areaUnit = "m^2", perimUnit = "m", distUnit = "m"),
    +  controlDist = list(maxSearch = 100, method = "centroid", unit =
    +    controlUnits$distUnit),
    +  filter = list(minArea = NULL, maxArea = NULL),
    +  verbose = TRUE
    +)
    +
    +# S3 method for sp
    +calculate_footstats(
    +  X,
    +  zone = NULL,
    +  what = "all",
    +  how = NULL,
    +  controlZone = list(zoneName = "zoneID", method = "centroid"),
    +  controlUnits = list(areaUnit = "m^2", perimUnit = "m", distUnit = "m"),
    +  controlDist = list(maxSearch = 100, method = "centroid", unit =
    +    controlUnits$distUnit),
    +  filter = list(minArea = NULL, maxArea = NULL),
    +  verbose = TRUE
    +)
    +
    +# S3 method for character
    +calculate_footstats(
    +  X,
    +  zone = NULL,
    +  what = "all",
    +  how = NULL,
    +  controlZone = list(zoneName = "zoneID", method = "centroid"),
    +  controlUnits = list(areaUnit = "m^2", perimUnit = "m", distUnit = "m"),
    +  controlDist = list(maxSearch = 100, method = "centroid", unit =
    +    controlUnits$distUnit),
    +  filter = list(minArea = NULL, maxArea = NULL),
    +  verbose = TRUE
    +)
    + +

    Arguments

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    X

    object with building footprint polygons. This argument can take +multiple spatial types, including sf and sp, or a filepath +string to a file.

    zone

    A spatial polygon file defining areas. Or a string identifying a +column within X which provides a zonal index for summarising values. +Alternatively a vector of indices (with length(index)==nrow(X)) can +be provided. If omitted, all observations in X are assumed to be +within one zone.

    what

    list of strings naming the columns or built-in geometry measures to +calculate for each footprint. Other options include 'all' or +'nodist' to calculate all available characteristics and all except +nearest-neighbour distance metrics.

    how

    list of strings naming functions to be used to calculate summary +statistics. The functions can be built-in functions (e.g. "mean","sd"), or +user-defined function names.

    controlZone

    (optional) named list. Setting controls passed on to +zonalIndex. Elements can include zoneName and +method.

    controlUnits

    (optional) named list. Elements can include +areaUnit, perimUnit, and distUnit. The values for +these items should be strings that can be coerced into a units +object.

    controlDist

    (optional) named list to override default +options for distance calculations. Elements can include maxSearch +and method. Ignored if metrics does not include a distance +calculation. See fs_nndist.

    filter

    (optional) named list with minArea and maxArea. +These are numeric values to filter footprints prior to processing. Default +values are NULL and do not filter any records.

    verbose

    logical. Should progress messages be printed. +Default TRUE.

    + +

    Value

    + +

    a data.table with an index column identifying the zones and + named columns for each footprint summary statistic.

    +

    Details

    + +

    calculate_footstats

    +

    calculate_footstats is a wrapper function combining several + internal functions from foot. It can calculate various geometric + measures for each footprint polygon, including area, perimeter, + compactness, shape, angle of rotation, and nearest neighbour distance. To + find the list of built-in characteristics and summary metrics, use + list_fs().

    +

    The what and how arguments are lists specifying the + characteristics and the summary metrics to calculated, respectively. Each + "how" function will be applied to each "what" characteristic. To apply a + metric to only a subset of characteristics, nested lists, with groups of + characteristics and functions can be supplied. See examples.

    +

    The control arguments are sets of lists with named arguments that + pass on these parameters to other foot functions. These are + optional.

    +

    See also

    + + + +

    Examples

    +
    data("kampala", package="foot") +buildings <- kampala$buildings +adminzones <- kampala$adminZones + +# no summary statistics, just geometry calculations +calculate_footstats(buildings, adminzones, what=list("area","perimeter")) +
    #> Selecting metrics +#> Setting control values. +#> Creating zonal index +#> Pre-calculating areas
    #> Linking to GEOS 3.8.0, GDAL 3.0.4, PROJ 6.3.1
    #> WARNING: different compile-time and runtime versions for GEOS found:
    #> Linked against: 3.8.0-CAPI-1.13.1 compiled against: 3.8.1-CAPI-1.13.3
    #> It is probably a good idea to reinstall sf, and maybe rgeos and rgdal too
    #> Pre-calculating perimeters +#> No summary functions found, returning metrics. +#>
    #> area perimeter +#> 1: 559.24140 [m^2] 121.55839 [m] +#> 2: 56.43618 [m^2] 31.20118 [m] +#> 3: 55.84536 [m^2] 34.11899 [m] +#> 4: 2293.58642 [m^2] 220.13423 [m] +#> 5: 229.26511 [m^2] 60.57280 [m] +#> --- +#> 4836: 75.50259 [m^2] 35.91359 [m] +#> 4837: 53.60515 [m^2] 31.22369 [m] +#> 4838: 69.56970 [m^2] 33.72564 [m] +#> 4839: 376.08518 [m^2] 99.95716 [m] +#> 4840: 164.93268 [m^2] 53.28785 [m]
    +# average building footprint area +calculate_footstats(buildings, + zone=adminzones, + what="area", how="mean") +
    #> Selecting metrics +#> Setting control values. +#> Creating zonal index +#> Pre-calculating areas +#> +#> Calculating 1 metrics ... +#> area mean +#> Finished calculating metrics.
    #> zoneID area_mean +#> 1: 1 402.59838 [m^2] +#> 2: 2 211.05341 [m^2] +#> 3: 3 525.07469 [m^2] +#> 4: 4 555.09314 [m^2] +#> 5: 5 568.71544 [m^2] +#> 6: 6 1021.95294 [m^2] +#> 7: 7 2027.70402 [m^2] +#> 8: 8 908.24803 [m^2] +#> 9: 9 181.68220 [m^2] +#> 10: 10 251.99721 [m^2] +#> 11: 11 220.11253 [m^2] +#> 12: 12 86.38345 [m^2] +#> 13: 13 661.61890 [m^2] +#> 14: 14 81.85444 [m^2] +#> 15: 15 80.78532 [m^2] +#> 16: 16 68.75161 [m^2] +#> 17: 17 139.17524 [m^2] +#> 18: 18 87.01754 [m^2] +#> 19: 19 237.07627 [m^2] +#> 20: 20 199.39362 [m^2] +#> 21: 21 244.09906 [m^2] +#> 22: 22 192.39558 [m^2] +#> 23: 23 771.35501 [m^2] +#> 24: 24 219.27229 [m^2] +#> 25: 25 506.22155 [m^2] +#> 26: 26 1164.40867 [m^2] +#> 27: 27 191.05557 [m^2] +#> 28: 29 238.76025 [m^2] +#> 29: 30 203.68136 [m^2] +#> 30: 31 142.13649 [m^2] +#> 31: 32 272.76984 [m^2] +#> 32: 33 418.66391 [m^2] +#> 33: 34 176.81732 [m^2] +#> zoneID area_mean
    +# calculate multiple metrics - nested lists to group arguments +calculate_footstats(buildings, adminzones, + what=list(list("area"), list("perimeter")), + how=list(list("mean","sum"), list("sd","cv"))) +
    #> Selecting metrics +#> Setting control values. +#> Creating zonal index +#> Pre-calculating areas +#> Pre-calculating perimeters +#> +#> Calculating 4 metrics ... +#> area mean +#> area sum +#> perimeter sd +#> perimeter cv +#> Finished calculating metrics.
    #> zoneID area_mean area_sum perimeter_sd perimeter_cv +#> 1: 1 402.59838 [m^2] 8454.5661 [m^2] 55.665551 0.6561304 +#> 2: 2 211.05341 [m^2] 29758.5314 [m^2] 44.587096 0.7313348 +#> 3: 3 525.07469 [m^2] 25728.6600 [m^2] 88.397185 0.9811076 +#> 4: 4 555.09314 [m^2] 63835.7113 [m^2] 88.872380 1.0600954 +#> 5: 5 568.71544 [m^2] 26729.6259 [m^2] 88.251084 0.9581201 +#> 6: 6 1021.95294 [m^2] 35768.3531 [m^2] 105.385333 0.9616081 +#> 7: 7 2027.70402 [m^2] 28387.8563 [m^2] 170.141093 0.9286094 +#> 8: 8 908.24803 [m^2] 44504.1537 [m^2] 109.992947 0.8489816 +#> 9: 9 181.68220 [m^2] 48145.7825 [m^2] 54.922117 0.9246860 +#> 10: 10 251.99721 [m^2] 7559.9164 [m^2] 75.861771 1.0900293 +#> 11: 11 220.11253 [m^2] 24212.3780 [m^2] 61.492800 0.8815988 +#> 12: 12 86.38345 [m^2] 3109.8042 [m^2] 28.289807 0.7257624 +#> 13: 13 661.61890 [m^2] 2646.4756 [m^2] 139.514757 1.3165558 +#> 14: 14 81.85444 [m^2] 6712.0641 [m^2] 7.755949 0.2059276 +#> 15: 15 80.78532 [m^2] 19226.9062 [m^2] 18.498145 0.5073846 +#> 16: 16 68.75161 [m^2] 9900.2318 [m^2] 10.681959 0.3095971 +#> 17: 17 139.17524 [m^2] 14752.5754 [m^2] 27.254588 0.5541695 +#> 18: 18 87.01754 [m^2] 36895.4355 [m^2] 26.337404 0.7093738 +#> 19: 19 237.07627 [m^2] 96490.0399 [m^2] 26.881426 0.4151437 +#> 20: 20 199.39362 [m^2] 80355.6287 [m^2] 30.741879 0.5257108 +#> 21: 21 244.09906 [m^2] 18063.3304 [m^2] 46.479887 0.7810948 +#> 22: 22 192.39558 [m^2] 10196.9657 [m^2] 12.929421 0.2238327 +#> 23: 23 771.35501 [m^2] 132673.0618 [m^2] 113.778927 1.1205392 +#> 24: 24 219.27229 [m^2] 61834.7854 [m^2] 33.388278 0.5317242 +#> 25: 25 506.22155 [m^2] 11643.0956 [m^2] 84.106005 0.8940386 +#> 26: 26 1164.40867 [m^2] 150208.7190 [m^2] 146.936669 1.0767096 +#> 27: 27 191.05557 [m^2] 33816.8355 [m^2] 25.259149 0.4462858 +#> 28: 29 238.76025 [m^2] 37007.8380 [m^2] 63.489927 1.0713172 +#> 29: 30 203.68136 [m^2] 15276.1021 [m^2] 33.438265 0.5426390 +#> 30: 31 142.13649 [m^2] 142.1365 [m^2] NA NA +#> 31: 32 272.76984 [m^2] 35732.8487 [m^2] 45.074544 0.6469339 +#> 32: 33 418.66391 [m^2] 7117.2864 [m^2] 55.919761 0.6104610 +#> 33: 34 176.81732 [m^2] 146935.1905 [m^2] 42.933274 0.7474269 +#> zoneID area_mean area_sum perimeter_sd perimeter_cv
    +
    +
    + +
    + + +
    + + +
    +

    Site built with pkgdown 1.6.1.

    +
    + +
    +
    + + + + + + + + diff --git a/docs/reference/figures/REAsDME-Fig1-1.png b/docs/reference/figures/REAsDME-Fig1-1.png new file mode 100644 index 0000000..a996a03 Binary files /dev/null and b/docs/reference/figures/REAsDME-Fig1-1.png differ diff --git a/docs/reference/figures/REAsDME-Fig2-1.png b/docs/reference/figures/REAsDME-Fig2-1.png new file mode 100644 index 0000000..85283a3 Binary files /dev/null and b/docs/reference/figures/REAsDME-Fig2-1.png differ diff --git a/docs/reference/figures/REAsDME-Fig3-1.png b/docs/reference/figures/REAsDME-Fig3-1.png new file mode 100644 index 0000000..61e8eac Binary files /dev/null and b/docs/reference/figures/REAsDME-Fig3-1.png differ diff --git a/docs/reference/figures/REAsDME-Fig4-1.png b/docs/reference/figures/REAsDME-Fig4-1.png new file mode 100644 index 0000000..c7a1c0e Binary files /dev/null and b/docs/reference/figures/REAsDME-Fig4-1.png differ diff --git a/vignettes/figure/Fig4-1.png b/docs/reference/figures/REAsDME-Fig5-1.png similarity index 100% rename from vignettes/figure/Fig4-1.png rename to docs/reference/figures/REAsDME-Fig5-1.png diff --git a/docs/reference/figures/REAsDME-Fig6-1.png b/docs/reference/figures/REAsDME-Fig6-1.png new file mode 100644 index 0000000..4cf9a55 Binary files /dev/null and b/docs/reference/figures/REAsDME-Fig6-1.png differ diff --git a/docs/reference/figures/REAsDME-unnamed-chunk-11-1.png b/docs/reference/figures/REAsDME-unnamed-chunk-11-1.png new file mode 100644 index 0000000..b72a6a7 Binary files /dev/null and b/docs/reference/figures/REAsDME-unnamed-chunk-11-1.png differ diff --git a/docs/reference/figures/REAsDME-unnamed-chunk-12-1.png b/docs/reference/figures/REAsDME-unnamed-chunk-12-1.png new file mode 100644 index 0000000..c7b9a5c Binary files /dev/null and b/docs/reference/figures/REAsDME-unnamed-chunk-12-1.png differ diff --git a/docs/reference/figures/REAsDME-unnamed-chunk-13-1.png b/docs/reference/figures/REAsDME-unnamed-chunk-13-1.png new file mode 100644 index 0000000..7e360c6 Binary files /dev/null and b/docs/reference/figures/REAsDME-unnamed-chunk-13-1.png differ diff --git a/docs/reference/figures/REAsDME-unnamed-chunk-14-1.png b/docs/reference/figures/REAsDME-unnamed-chunk-14-1.png new file mode 100644 index 0000000..f644b4c Binary files /dev/null and b/docs/reference/figures/REAsDME-unnamed-chunk-14-1.png differ diff --git a/docs/reference/figures/REAsDME-unnamed-chunk-15-1.png b/docs/reference/figures/REAsDME-unnamed-chunk-15-1.png new file mode 100644 index 0000000..cf0267c Binary files /dev/null and b/docs/reference/figures/REAsDME-unnamed-chunk-15-1.png differ diff --git a/docs/reference/figures/REAsDME-unnamed-chunk-3-1.png b/docs/reference/figures/REAsDME-unnamed-chunk-3-1.png new file mode 100644 index 0000000..9edc044 Binary files /dev/null and b/docs/reference/figures/REAsDME-unnamed-chunk-3-1.png differ diff --git a/docs/reference/figures/REAsDME-unnamed-chunk-4-1.png b/docs/reference/figures/REAsDME-unnamed-chunk-4-1.png new file mode 100644 index 0000000..f5b93d5 Binary files /dev/null and b/docs/reference/figures/REAsDME-unnamed-chunk-4-1.png differ diff --git a/docs/reference/figures/REAsDME-unnamed-chunk-8-1.png b/docs/reference/figures/REAsDME-unnamed-chunk-8-1.png new file mode 100644 index 0000000..d605c31 Binary files /dev/null and b/docs/reference/figures/REAsDME-unnamed-chunk-8-1.png differ diff --git a/docs/reference/foot.html b/docs/reference/foot.html new file mode 100644 index 0000000..b48160f --- /dev/null +++ b/docs/reference/foot.html @@ -0,0 +1,211 @@ + + + + + + + + +foot: An R package for calculating building footprint shape metrics — foot • foot + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    +
    + + +
    +

    The foot package provides functions to calculate summary statistics of +geometric measurements of building footprint polygons. Footprint shapes +representing buildings are becoming more widely available by being detected +and extracted from very high resolution satellite imagery. Such datasets are +spatially detailed but often are unlabelled. However, the size, shape, and +distribution of buildings can suggest possible land uses or differences in +structure use, socio-economic status, etc.

    +
    + + + +

    Details

    + +

    The foot package is designed to provide a set of consistent and +flexible tools for processing 2D vector representations of buildings. The +functionality includes basic geometry and morphology measures, distance and +clustering metrics. These calculations are supported with helper functions +for spatial intersections and tiled reading/writing of data.

    +

    The measurements in foot include: area, perimeter, nearest-neighbour +distance, angle of rotation for a bounding rectangle, as well as a binary +indicator of structure presence and a count of structures.

    +

    These measures can be summarised as a mean, standard deviation, coefficient +of variation, the nearest neighbour index of clustering, or a (normalised) +entropy measure for the angles. The output can be formatted as a data table +or as a gridded dataset.

    +

    Helper functions

    + +

    The foot package provides convenience + functions (calculate_footstats and + calculate_bigfoot) to wrap common analysis steps + together, taking a list of measurements and parameters and returning a + collected output.

    +

    In addition to bulk processing helper functions, there are additional +utility functions supplied with the package to support identifying +nearest neighbours, adjacent raster cells, creating zonal indices for +spatial data and providing efficient I/O and parallel processing.

    +

    Credits

    + + + +

    This work was undertaken by members of the WorldPop Research Group at the +University of Southampton(https://www.worldpop.org/).

    + +
    + +
    + + +
    + + +
    +

    Site built with pkgdown 1.6.1.

    +
    + +
    +
    + + + + + + + + diff --git a/docs/reference/fs_angle_entropy.html b/docs/reference/fs_angle_entropy.html new file mode 100644 index 0000000..85d44ee --- /dev/null +++ b/docs/reference/fs_angle_entropy.html @@ -0,0 +1,241 @@ + + + + + + + + +Building angle calculation — fs_angle_entropy • foot + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    +
    + + +
    +

    Calculate the entropy of rotation angles for building footprint +polygons within zones.

    +
    + +
    fs_angle_entropy(X, index = NULL, col = NULL, normalize = TRUE)
    +
    +# S3 method for sp
    +fs_angle_entropy(X, index = NULL, col = NULL, normalize = TRUE)
    +
    +# S3 method for sf
    +fs_angle_entropy(X, index = NULL, col = NULL, normalize = TRUE)
    +
    +# S3 method for sfc
    +fs_angle_entropy(X, index = NULL, col = NULL, normalize = TRUE)
    + +

    Arguments

    + + + + + + + + + + + + + + + + + + + + + + +
    X

    Spatial object with building footprint polygons

    index

    A string identifying a column within X which provides a +zonal index for summarising values. Alternatively a vector of indices can +be provided. If omitted, all observations in X are assumed to be +within one zone.

    col

    column name within X with pre-calculated area measures

    normalize

    A logical value indicating whether to normalize the entropy. +Default is TRUE.

    unit

    character or units object to define area. Default is +NULL which will use the units of the spatial reference system

    + +

    Value

    + +

    data.table of zonal indices and values.

    +

    Details

    + +

    This measure uses the angle of the minimum rotated rectangle + enclosing each footprint polygon. Entropy is an information criteria + measure. When summarising the angles of footprints, higher entropy values + may suggest less formally planned or zoned areas. The entropy calculation + uses the common Shannon's Entropy. The normalization step produces an + indicator for how much a zone departs from a grid. This metric is based on + work by Boeing (2019).

    +

    Note that this function is provided as a standalone calculation for + convenience. The same summary measure can be executed within + calculate_footstats by specifying what='angle' and + how='entropy'.

    +

    References

    + +

    Boeing, Geoff (2019). "Urban spatial order: Street network + orientation, configuration, and entropy." Applied Network Science, 4(67), + https://doi.org/10.1007/s41109-019-0189-1.

    + +

    Examples

    +
    data("kampala", package="foot") +b <- kampala$buildings + +# assign random groups +idx <- sample(1:10, nrow(b), replace=T) + +angles <- fs_angle_entropy(b, index=idx, normalize=FALSE) +angle_norm <- fs_angle_entropy(b, index=idx, normalize=TRUE) +
    #> Error in `[.data.table`(result, , `:=`(entropy, 1 - ((entropy - hg)/(hmax - hg))^2), by = index): The items in the 'by' or 'keyby' list are length(s) (4840). Each must be length 10; the same length as there are rows in x (after subsetting if i is provided).
    +
    +
    + +
    + + +
    + + +
    +

    Site built with pkgdown 1.6.1.

    +
    + +
    +
    + + + + + + + + diff --git a/docs/reference/fs_area.html b/docs/reference/fs_area.html new file mode 100644 index 0000000..b32e042 --- /dev/null +++ b/docs/reference/fs_area.html @@ -0,0 +1,187 @@ + + + + + + + + +Area — fs_area • foot + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    +
    + + +
    +

    Helper geometry function to measure the area of building + footprint polygons.

    +
    + +
    fs_area(X, unit = NULL)
    + +

    Arguments

    + + + + + + + + + + +
    X

    polygons of building footprints of type sf.

    unit

    string indicating unit of measure. Passed to +units::set_units.

    + +

    Value

    + +

    numeric vector of area measured for each item in X.

    + +
    + +
    + + +
    + + +
    +

    Site built with pkgdown 1.6.1.

    +
    + +
    +
    + + + + + + + + diff --git a/docs/reference/fs_count.html b/docs/reference/fs_count.html new file mode 100644 index 0000000..7c03be1 --- /dev/null +++ b/docs/reference/fs_count.html @@ -0,0 +1,210 @@ + + + + + + + + +Count of footprint locations per zone — fs_count • foot + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    +
    + + +
    +

    Calculate and summarise selected metrics of building +footprints within zones.

    +
    + +
    fs_count(X, index = NULL, col = NULL)
    +
    +# S3 method for sp
    +fs_count(X, index = NULL, col = NULL)
    +
    +# S3 method for sf
    +fs_count(X, index = NULL, col = NULL)
    + +

    Arguments

    + + + + + + + + + + + + + + + + + + +
    X

    Spatial object with building footprint polygons

    index

    A string identifying a column within X which provides a +zonal index for summarising values. Alternatively a vector of indices can +be provided. If omitted, all observations in X are assumed to be +within one zone.

    col

    column name within X with pre-calculated area measures

    unit

    character or units object to define area. Default is +NULL which will use the units of the spatial reference system

    + +

    Value

    + +

    data.table of zonal indices and values.

    +

    Details

    + +

    Note that this function is provided as a standalone calculation for + convenience. The same summary measure can be executed within + calculate_footstats by specifying what='settled' and + how='count'.

    + +
    + +
    + + +
    + + +
    +

    Site built with pkgdown 1.6.1.

    +
    + +
    +
    + + + + + + + + diff --git a/docs/reference/fs_functions.html b/docs/reference/fs_functions.html new file mode 100644 index 0000000..e10c005 --- /dev/null +++ b/docs/reference/fs_functions.html @@ -0,0 +1,199 @@ + + + + + + + + +Built-in 'foot' functions — fs_functions • foot + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    +
    + + +
    +

    In addition to generic R functions (e.g. 'mean', 'max', + etc.), these functions are designed to provide simple access to common + calculations.

    +
    + +
    binary(x)
    +
    +count(x)
    +
    +cv(x)
    +
    +entropy(x)
    +
    +majority(x)
    + +

    Arguments

    + + + + + + +
    x

    numeric vector of values to summarize.

    + +

    Value

    + +

    numeric value

    +

    Details

    + +

    These functions are designed to be used within + calculate_footstats which processes a data.table + by group ID. Therefore all functions take a vector of values and return a + single summary statistic. These functions are not likely to be used on + their own.

    + +
    + +
    + + +
    + + +
    +

    Site built with pkgdown 1.6.1.

    +
    + +
    +
    + + + + + + + + diff --git a/docs/reference/fs_mbc.html b/docs/reference/fs_mbc.html new file mode 100644 index 0000000..6be17d3 --- /dev/null +++ b/docs/reference/fs_mbc.html @@ -0,0 +1,187 @@ + + + + + + + + +Minimum bounding circle — fs_mbc • foot + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    +
    + + +
    +

    Helper function to calculate the minimum circle that bounds the + polygon features.

    +
    + +
    fs_mbc(X)
    + +

    Arguments

    + + + + + + +
    X

    Building footprint polygons in sf or sp or other valid +spatial types.

    + +

    Value

    + +

    Object of the same class as X

    +

    Details

    + +

    fs_mbc uses lwgeom::st_minimum_bounding_circle to + calculate the minimum bounding circle.

    + +
    + +
    + + +
    + + +
    +

    Site built with pkgdown 1.6.1.

    +
    + +
    +
    + + + + + + + + diff --git a/docs/reference/fs_mbr.html b/docs/reference/fs_mbr.html new file mode 100644 index 0000000..6770240 --- /dev/null +++ b/docs/reference/fs_mbr.html @@ -0,0 +1,197 @@ + + + + + + + + +Rotated minimum bounding rectangle — fs_mbr • foot + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    +
    + + +
    +

    Helper function to provide the minimum rotated bounding + rectangle of a polygon or set of points. Optionally, the function will + return the bearing angle in degrees from vertical in a clockwise direction.

    +
    + +
    fs_mbr(X, returnShape = FALSE)
    + +

    Arguments

    + + + + + + + + + + +
    X

    polygons of building footprints in type sf.

    returnShape

    logical. Should the function return the sf polygon +of the rotated bounding rectangle or should it return the angle (in degrees).

    + +

    Source

    + +

    https://gis.stackexchange.com/questions/22895/finding-minimum-area-rectangle-for-given-points

    +

    Value

    + +

    a numeric angle from 0 to 360 degrees or the rotated rectangle as a + polygon of type sf.

    +

    Details

    + +

    This function is currently not vectorized and processing is limited + to one shape.

    + +
    + +
    + + +
    + + +
    +

    Site built with pkgdown 1.6.1.

    +
    + +
    +
    + + + + + + + + diff --git a/docs/reference/fs_nndist.html b/docs/reference/fs_nndist.html new file mode 100644 index 0000000..3b45df2 --- /dev/null +++ b/docs/reference/fs_nndist.html @@ -0,0 +1,274 @@ + + + + + + + + +Nearest neighbour distance calculation — fs_nndist • foot + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    +
    + + +
    +

    Helper function to provide a distance calculation between + spatial objects. The distance to the first nearest neighbour found + (optionally within a maximum search radius) is returned.

    +
    + +
    fs_nndist(X, Y, maxSearch = 100, method = "poly", unit = "m")
    + +

    Arguments

    + + + + + + + + + + + + + + + + + + + + + + +
    X

    Spatial object of sf type, typically polygons or points.

    Y

    (Optional) Spatial object to measure distances to.

    maxSearch

    Maximum radius around X to search. Distance in +meters. Default is 100. To ignore the search limit, set to `NULL`.

    method

    Either 'poly' or 'centroid' to assign a geometry +method. See details. Default is 'poly'.

    unit

    Character abbreviation for the units to return from the distance +calculation.

    + +

    Details

    + +

    If Y is omitted the nearest neighbour distances are found + within X. Otherwise, the distance for each object in X to its + nearest neighbour in Y is returned.

    +

    Use method to adjust which geometry of X and Y is used + for distance calculations. When method='poly' distances are measured + between polygon edges. When method='centroid', the centroids of + building footprints are used instead. Centroid-based distance calculations + are faster.

    +

    Providing a maximum search radius is strongly advised to speed up the + calculation.

    + +

    Examples

    +
    data("kampala", package="foot") + +# get sample of buildings +buildings <- kampala$buildings +buildings <- buildings[sample(1:nrow(buildings), size=100, replace=F),] +clusters <- kampala$clusters + +# calculate distance between buildings in m +fs_nndist(buildings, unit="m") +
    #> Units: [m] +#> [1] 95.156073 65.601817 93.609789 NA 62.488049 NA NA +#> [8] 59.100124 46.189843 NA NA 61.047564 NA 99.874799 +#> [15] 40.838343 NA 49.031866 47.966077 81.864406 NA NA +#> [22] 72.769564 72.769564 NA 40.838343 47.966077 NA 84.452095 +#> [29] 95.156073 51.058873 81.407425 NA 26.408178 NA NA +#> [36] 26.408178 6.255871 28.996263 NA NA 74.070750 56.812414 +#> [43] NA 33.712512 NA NA 28.996263 NA NA +#> [50] 65.601817 NA 48.945229 51.058873 56.335511 49.031866 75.466904 +#> [57] NA 33.712512 81.407425 NA NA NA NA +#> [64] NA 32.857832 NA 92.682442 54.565714 59.100124 78.789825 +#> [71] NA NA 56.812414 NA NA NA NA +#> [78] NA NA 75.466904 61.047564 NA NA NA +#> [85] NA 68.010798 6.255871 32.857832 70.251191 NA 68.010798 +#> [92] 13.171564 84.452095 46.189843 13.171564 48.945229 81.864406 93.609789 +#> [99] NA NA
    +# calculate unrestricted distance +# between buildings and another set of points +fs_nndist(buildings, sf::st_centroid(clusters), maxSearch=NULL) +
    #> Warning: st_centroid assumes attributes are constant over geometries of x
    #> Warning: st_centroid does not give correct centroids for longitude/latitude data
    #> Units: [m] +#> [1] 565.3182 575.2489 504.6872 588.2953 347.4469 686.9973 913.9714 +#> [8] 648.2031 559.8968 625.5754 440.8780 715.5617 541.6824 737.0240 +#> [15] 493.5160 444.8671 496.0283 720.9820 983.2503 917.4102 732.0587 +#> [22] 580.3403 621.2782 740.2187 544.3737 756.0986 522.6995 490.5673 +#> [29] 702.6093 924.0940 647.9873 702.6034 629.1258 621.1267 599.2047 +#> [36] 671.9687 707.0049 629.2830 745.5526 679.7994 701.4090 327.6876 +#> [43] 813.2120 691.1722 667.1933 765.7186 660.6861 700.4227 636.3701 +#> [50] 607.0421 626.7668 342.6762 902.3689 913.9209 463.9412 584.4413 +#> [57] 708.2913 672.0993 748.4964 707.0380 340.0872 1075.9869 350.3166 +#> [64] 659.4719 454.7672 599.6658 406.7191 415.9892 651.2843 579.1138 +#> [71] 641.4879 659.4152 376.9941 483.4831 767.8711 642.5196 590.6956 +#> [78] 760.1477 582.7286 574.1047 751.5171 908.2384 791.5463 763.0176 +#> [85] 692.2449 598.3155 699.5359 405.8884 670.7742 530.1799 712.6713 +#> [92] 592.3538 526.0394 600.8611 610.7973 407.7618 1002.0050 558.2650 +#> [99] 501.0853 518.0380
    +# use footprint centroids +fs_nndist(buildings, method='centroid', unit='m') +
    #> Units: [m] +#> [1] NA 73.36415 NA NA 83.76082 NA NA 66.17950 +#> [9] 61.77284 NA NA 71.77209 NA NA 53.63681 NA +#> [17] 63.03600 68.78486 91.96353 NA NA 83.19872 83.19872 NA +#> [25] 53.63681 68.78486 NA 94.12350 NA 65.76860 NA NA +#> [33] 47.35636 NA NA 47.35636 37.43966 40.41447 NA NA +#> [41] 98.95258 65.42620 NA 52.52931 NA NA 40.41447 NA +#> [49] NA 73.36415 NA 72.91177 65.76860 74.43525 63.03600 91.90861 +#> [57] NA 52.52931 NA NA NA NA NA NA +#> [65] 45.11204 NA 99.85517 73.54571 66.17950 89.78993 NA NA +#> [73] 65.42620 NA NA NA NA NA NA 91.90861 +#> [81] 71.77209 NA NA NA NA NA 37.43966 45.11204 +#> [89] 90.28790 NA NA 27.67890 94.12350 61.77284 27.67890 72.91177 +#> [97] 91.96353 NA NA NA
    +
    +
    + +
    + + +
    + + +
    +

    Site built with pkgdown 1.6.1.

    +
    + +
    +
    + + + + + + + + diff --git a/docs/reference/fs_nnindex.html b/docs/reference/fs_nnindex.html new file mode 100644 index 0000000..9afc56d --- /dev/null +++ b/docs/reference/fs_nnindex.html @@ -0,0 +1,224 @@ + + + + + + + + +Building Nearest Neighbour Index (NNI) — fs_nnindex • foot + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    +
    + + +
    +

    Calculate and summarise selected metrics of building footprint + polygons within zones.

    +
    + +
    fs_nnindex(X, zone = NULL, zoneField = NULL, unit = NULL)
    +
    +# S3 method for sp
    +fs_nnindex(X, zone = NULL, zoneField = NULL, unit = NULL)
    +
    +# S3 method for sf
    +fs_nnindex(X, zone = NULL, zoneField = NULL, unit = NULL)
    +
    +# S3 method for sfc
    +fs_nnindex(X, zone = NULL, zoneField = NULL, unit = NULL)
    + +

    Arguments

    + + + + + + + + + + + + + + + + + + +
    X

    Spatial object with building footprints or their centroid locations.

    zone

    A spatial polygon object of sf or sp type. If +omitted all observations in X are assumed to be within one zone and +the area of the minimum bounding circle is used for the nearest neighbour +index.

    zoneField

    (Optional) Column name of unique identifiers in zone +to use. If omitted, the 'zoneID' will be numbered 1:nrow(zone).

    unit

    character or units object to define distance. Default +NULL will attempt to coerce units to meters.

    + +

    Value

    + +

    data.table of zonal indices and values

    +

    Details

    + +

    The nearest neighbour index (NNI) is a measure of spatial + clustering. It compares the observed mean neighbour distances with a + hypothetical maximum of dispersed observations given the area of the zone. + Note that NNI is sensitive to changes in the zone area.

    +

    $$ NNI_z = \frac{\bar{NND_z}}{(0.5 * \sqrt{\frac{A_z}{n_z}}})$$, where z + is the zone, A is the area, NND is the mean nearest neighbour distance, and + n is the count. The value of NNI can range from 0 (fully disperse) to 2.15 + (clustered), with values of 1 indicating spatial randomness.

    +

    The function uses fs_nndist to calculate the distance between + centroids of the building footprints within the same spatial zone indicated + by zone.

    +

    Note that this function is provided as a standalone calculation. The + summary measure can be executed from within calculate_footstats by + specifying what='nndist' and how='nnindex'.

    + +
    + +
    + + +
    + + +
    +

    Site built with pkgdown 1.6.1.

    +
    + +
    +
    + + + + + + + + diff --git a/docs/reference/fs_perimeter.html b/docs/reference/fs_perimeter.html new file mode 100644 index 0000000..1308d5d --- /dev/null +++ b/docs/reference/fs_perimeter.html @@ -0,0 +1,187 @@ + + + + + + + + +Perimeter — fs_perimeter • foot + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    +
    + + +
    +

    Helper geometry function to measure the perimeter of building + footprint polygons.

    +
    + +
    fs_perimeter(X, unit = NULL)
    + +

    Arguments

    + + + + + + + + + + +
    X

    polygons of building footprints of type sf.

    unit

    string indicating unit of measure. Passed to +units::set_units.

    + +

    Value

    + +

    numeric vector of perimeter measured for each item in X.

    + +
    + +
    + + +
    + + +
    +

    Site built with pkgdown 1.6.1.

    +
    + +
    +
    + + + + + + + + diff --git a/docs/reference/fs_shape.html b/docs/reference/fs_shape.html new file mode 100644 index 0000000..6e9de3a --- /dev/null +++ b/docs/reference/fs_shape.html @@ -0,0 +1,911 @@ + + + + + + + + +Compactness index — fs_shape • foot + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    +
    + + +
    +

    Calculates an approximate measure of shape "compactness".

    +

    Calculates a measure of shape or "roundness".

    +
    + +
    fs_compact(X)
    +
    +fs_shape(X, unit = NULL)
    + +

    Arguments

    + + + + + + + + + + +
    X

    polygons of building footprints of type sf.

    unit

    string indicating unit of measure. Passed to +units::set_units.

    + +

    Source

    + +

    Polsby, Daniel D., and Robert D. Popper. 1991. “The Third Criterion: + Compactness as a procedural safeguard against partisan gerrymandering.” + Yale Law & Policy Review 9 (2): 301–353.

    +

    Value

    + +

    numeric vector of compactness values ranging from 0 to 1 for each + item in X.

    +

    numeric vector with shape index values ranging from 0 to 1 or each + item in X.

    +

    Details

    + +

    The compactness measure is the Polsby-Popper test based on the + perimeter and area of each footprint.

    +

    $$ PP_i = \frac{4$\pi$ * A_z}{P_i^2} $$, + where P and A are the perimeter and area of footprint 'i', respectively. + Values closer to 1 indicate more compact shapes.

    +

    The shape index is calculated as the ratio of footprint polygon's + area to the area of minimum bounding circle. The mbc is calculated using + fs_mbc and lwgeom. Values closer 1 suggest more rounded + shapes.

    +

    The function first looks for pre-calculated values of area in a field called +fs_area. If not present, the areas are calculated in m^2.

    + +

    Examples

    +
    data("kampala", package="foot") +buildings = kampala$buildings + +fs_shape(buildings) +
    #> [1] 0.5732710 0.3936445 0.5940952 0.4757789 0.5279643 0.4594503 0.5520442 +#> [8] 0.6186284 0.6146991 0.3191903 0.5831908 0.3734735 0.6199261 0.6284222 +#> [15] 0.5296343 0.5650630 0.6277424 0.3951911 0.4613214 0.4603272 0.5462177 +#> [22] 0.5498980 0.3694690 0.6364900 0.6306242 0.6126680 0.5760368 0.4579807 +#> [29] 0.4540760 0.5232673 0.5624979 0.6091825 0.4467311 0.4729080 0.6155511 +#> [36] 0.5747637 0.4689331 0.2900026 0.5714818 0.6252113 0.2084648 0.5164600 +#> [43] 0.5895021 0.4781960 0.6219337 0.4698386 0.4870982 0.6131104 0.4987134 +#> [50] 0.5717630 0.6246021 0.6336822 0.3113282 0.6368507 0.5620981 0.5332064 +#> [57] 0.5861844 0.5753732 0.5172882 0.3914588 0.5730957 0.6212242 0.5352548 +#> [64] 0.6166963 0.5826926 0.6194321 0.6346215 0.6360542 0.6337827 0.3677917 +#> [71] 0.6074897 0.4088532 0.6211858 0.3858858 0.6314255 0.6312969 0.5383332 +#> [78] 0.5501453 0.5237341 0.5971225 0.6362928 0.4894178 0.5507644 0.5254038 +#> [85] 0.6256830 0.3799600 0.4169085 0.6101309 0.4104715 0.6279296 0.5463384 +#> [92] 0.5938882 0.6168179 0.5788152 0.6059261 0.6118937 0.5606799 0.3868816 +#> [99] 0.4737257 0.5770828 0.3220908 0.5360758 0.4319531 0.5599972 0.6290184 +#> [106] 0.5668813 0.6205130 0.6293328 0.6272623 0.6122693 0.5587328 0.5049882 +#> [113] 0.3051793 0.6009426 0.5759311 0.5329558 0.4953883 0.4195630 0.3488801 +#> [120] 0.3713825 0.6293937 0.4079712 0.6342412 0.6353557 0.5344720 0.6166060 +#> [127] 0.6145836 0.4787557 0.6159370 0.4435303 0.5680682 0.4134567 0.5604034 +#> [134] 0.6223524 0.6222142 0.4666822 0.4104656 0.6251515 0.5751696 0.3029486 +#> [141] 0.6270005 0.4170419 0.5508251 0.5442412 0.3209490 0.5772917 0.3654247 +#> [148] 0.4395288 0.5472499 0.6357586 0.5314024 0.6346224 0.6219423 0.5127182 +#> [155] 0.4165807 0.5605342 0.6110696 0.5840332 0.6135176 0.2914852 0.5835353 +#> [162] 0.4573072 0.4907642 0.6207798 0.4516017 0.6286756 0.4449222 0.6152135 +#> [169] 0.3954646 0.4889778 0.4554411 0.5491448 0.6173360 0.6249916 0.5291474 +#> [176] 0.4382508 0.5110412 0.5807750 0.3082652 0.5924805 0.6318618 0.5455830 +#> [183] 0.2144941 0.5903940 0.6158348 0.5992869 0.6310108 0.5682647 0.6028657 +#> [190] 0.5014111 0.3321919 0.3237416 0.5741066 0.3406314 0.4787624 0.5116576 +#> [197] 0.6266789 0.5881815 0.6302421 0.4981361 0.5232288 0.6051369 0.6286267 +#> [204] 0.5867073 0.6312247 0.2380819 0.5103474 0.3343464 0.4957874 0.6345263 +#> [211] 0.6073177 0.5310440 0.4946762 0.5352309 0.3151704 0.6353933 0.5233144 +#> [218] 0.6089199 0.6350131 0.4778253 0.3352737 0.3249772 0.5511612 0.2976067 +#> [225] 0.3716696 0.4085868 0.4476639 0.4888044 0.2567917 0.5781288 0.5863188 +#> [232] 0.6333638 0.6349958 0.4497029 0.4184147 0.6126092 0.3763937 0.3140237 +#> [239] 0.5297468 0.4694351 0.6081445 0.5412181 0.6154853 0.6196394 0.6266603 +#> [246] 0.6112887 0.4553350 0.5171945 0.5201911 0.5258431 0.4655982 0.2819886 +#> [253] 0.6282448 0.4215686 0.5548051 0.6110209 0.2804562 0.6155574 0.6321677 +#> [260] 0.5912452 0.6334682 0.5731057 0.4794716 0.5073525 0.3337234 0.4608597 +#> [267] 0.5734342 0.5899181 0.6132093 0.5943552 0.5992753 0.5602455 0.6331631 +#> [274] 0.4283728 0.3656979 0.4413582 0.4907641 0.3340617 0.6220349 0.3498176 +#> [281] 0.5828174 0.3540713 0.5617176 0.6171672 0.5951299 0.4344917 0.6234892 +#> [288] 0.2355929 0.2820776 0.4109798 0.5669006 0.4878998 0.5650190 0.3937041 +#> [295] 0.4946751 0.6162414 0.5884730 0.6117824 0.3905867 0.5665236 0.5751594 +#> [302] 0.5783626 0.5102864 0.5063288 0.6004536 0.4558807 0.5164326 0.5681200 +#> [309] 0.4237661 0.6190042 0.4553182 0.5952796 0.6265730 0.5135872 0.6154363 +#> [316] 0.4490011 0.5627850 0.4934252 0.6073085 0.5964845 0.6264703 0.4959896 +#> [323] 0.6108062 0.2445629 0.5032880 0.4988642 0.5054230 0.5236550 0.5567678 +#> [330] 0.4868169 0.6032521 0.5343491 0.4993182 0.6315328 0.4439322 0.2899944 +#> [337] 0.5459245 0.5503120 0.6328837 0.5254628 0.4907643 0.4871387 0.5274039 +#> [344] 0.5160851 0.4560086 0.5511349 0.6254122 0.4514210 0.4867920 0.5775545 +#> [351] 0.5380659 0.4420309 0.5426239 0.5590395 0.3600087 0.5661078 0.4993523 +#> [358] 0.6310833 0.6109735 0.5393428 0.6211642 0.5030190 0.5828338 0.4349430 +#> [365] 0.5177292 0.4375615 0.3364812 0.5769620 0.6242330 0.5030205 0.2939523 +#> [372] 0.2919352 0.5378008 0.3232712 0.6126469 0.6342668 0.4371585 0.4857195 +#> [379] 0.3250053 0.3841079 0.5657421 0.3640387 0.5130934 0.4707291 0.6341429 +#> [386] 0.5789662 0.6197214 0.6149280 0.4034005 0.6259740 0.6294173 0.4701926 +#> [393] 0.6332121 0.6280026 0.5443160 0.4723348 0.5167603 0.5643454 0.2218265 +#> [400] 0.5186333 0.3003906 0.3438613 0.3851202 0.4712817 0.4408043 0.5192706 +#> [407] 0.6129728 0.4977088 0.5078556 0.4621832 0.6264236 0.6218553 0.6364886 +#> [414] 0.5708970 0.4199439 0.5343389 0.5754435 0.6286242 0.5071244 0.2731176 +#> [421] 0.3588342 0.3524822 0.5265317 0.4690751 0.3075076 0.2981464 0.6040818 +#> [428] 0.5376357 0.6166605 0.6089670 0.3953498 0.4411253 0.3779294 0.2095702 +#> [435] 0.5950563 0.6325626 0.4739803 0.4341733 0.4031456 0.6271136 0.4640386 +#> [442] 0.4204684 0.5244978 0.5896838 0.6341158 0.5777366 0.5810100 0.6310730 +#> [449] 0.5443592 0.4898006 0.3487655 0.3660179 0.3422737 0.4355352 0.6234069 +#> [456] 0.5988559 0.6054003 0.6272093 0.6317477 0.4240549 0.4319856 0.5344767 +#> [463] 0.4847702 0.3086354 0.4863598 0.4843595 0.5501974 0.5829452 0.3565756 +#> [470] 0.6207804 0.4821480 0.5681096 0.6280397 0.2481636 0.4765679 0.6010248 +#> [477] 0.5234882 0.5851540 0.6194848 0.5694812 0.3602561 0.4437520 0.4325012 +#> [484] 0.3363195 0.4177447 0.6306657 0.5204181 0.6263997 0.3763353 0.6310642 +#> [491] 0.2116967 0.4607549 0.6030418 0.6093217 0.6217588 0.5073897 0.4223739 +#> [498] 0.6080279 0.5180106 0.4505512 0.6281868 0.4015603 0.3741296 0.5226196 +#> [505] 0.3727357 0.6316951 0.6341458 0.3506984 0.5189126 0.6182345 0.4860993 +#> [512] 0.5230491 0.5597473 0.3518304 0.5665207 0.6119533 0.6236514 0.3865508 +#> [519] 0.6312434 0.6001366 0.5095290 0.5662660 0.4521175 0.6009226 0.5902575 +#> [526] 0.6366135 0.5133396 0.6335521 0.4319895 0.5544088 0.5504812 0.2173761 +#> [533] 0.4464663 0.6174966 0.6184821 0.5832016 0.6044964 0.6021954 0.2504613 +#> [540] 0.5945694 0.4288782 0.3144497 0.5785801 0.5621867 0.6273348 0.6226980 +#> [547] 0.6332093 0.3712081 0.5006005 0.5906673 0.6278862 0.3681063 0.5976102 +#> [554] 0.5413126 0.3895227 0.5980455 0.6107422 0.6299805 0.4624703 0.5142207 +#> [561] 0.4598335 0.5736639 0.5492279 0.6167352 0.6161164 0.4927961 0.6282874 +#> [568] 0.5953835 0.5759180 0.2594582 0.2372441 0.2862208 0.6273307 0.3855589 +#> [575] 0.6058378 0.6031919 0.3428940 0.6252074 0.5812902 0.6342821 0.4705476 +#> [582] 0.3370678 0.4906443 0.5568022 0.6192486 0.5896636 0.5642246 0.5369756 +#> [589] 0.5756475 0.3033686 0.4692258 0.4796803 0.6198067 0.5964515 0.5795839 +#> [596] 0.6302058 0.6226900 0.5470343 0.4153690 0.5168632 0.4447551 0.4233263 +#> [603] 0.4452254 0.5692898 0.6335120 0.6301772 0.4647665 0.3793293 0.4061856 +#> [610] 0.5289068 0.6080370 0.6303457 0.3116544 0.5997258 0.6162540 0.6068475 +#> [617] 0.6364216 0.6196737 0.3534938 0.2156252 0.5039802 0.4563233 0.3045341 +#> [624] 0.2421332 0.5513591 0.4534947 0.4758278 0.4551949 0.4427932 0.5528196 +#> [631] 0.5917759 0.6271123 0.5893848 0.5003454 0.4305480 0.4985175 0.4656115 +#> [638] 0.6063196 0.3658696 0.4620063 0.5337524 0.4537626 0.6206484 0.4878103 +#> [645] 0.3942528 0.6226156 0.5676665 0.6344001 0.6191047 0.4423194 0.4037743 +#> [652] 0.5591198 0.5238699 0.3113517 0.3943048 0.6310876 0.1601515 0.4526350 +#> [659] 0.2352558 0.4025776 0.4744588 0.3974717 0.6288158 0.4759927 0.6311707 +#> [666] 0.5691453 0.6137133 0.4463501 0.4937083 0.4166530 0.6013743 0.4656406 +#> [673] 0.4196051 0.5041547 0.3977263 0.2927175 0.5139103 0.6336539 0.6339708 +#> [680] 0.3920872 0.6004322 0.5188119 0.6172445 0.6158114 0.4599246 0.5743461 +#> [687] 0.4396942 0.3240908 0.5201655 0.6255877 0.3728242 0.3581155 0.6348057 +#> [694] 0.5633942 0.4245638 0.5810356 0.5603739 0.6253223 0.6077592 0.5941165 +#> [701] 0.6191409 0.5396764 0.4739475 0.4038053 0.5906011 0.5502820 0.5704220 +#> [708] 0.2693134 0.4381253 0.6244780 0.5972424 0.6360789 0.6184275 0.6301819 +#> [715] 0.3368946 0.4461897 0.6021506 0.5420965 0.5782848 0.6212046 0.3650181 +#> [722] 0.6183571 0.3572183 0.5348795 0.4438407 0.3506321 0.4645435 0.6225970 +#> [729] 0.3159927 0.6141073 0.4974583 0.3637649 0.5259179 0.5316568 0.2017726 +#> [736] 0.6277897 0.5289219 0.4836637 0.5295846 0.6240678 0.4903333 0.5279136 +#> [743] 0.5171392 0.3514787 0.5469757 0.6289613 0.6300891 0.5478358 0.6365040 +#> [750] 0.4316076 0.4477987 0.3861417 0.6235274 0.5244979 0.3555305 0.5199921 +#> [757] 0.5687701 0.4349287 0.4375990 0.6320334 0.5795257 0.4391309 0.6207192 +#> [764] 0.5268319 0.5689055 0.3814241 0.5961305 0.5864450 0.5589907 0.3785462 +#> [771] 0.3316558 0.5324322 0.5791553 0.4950030 0.6310966 0.3960935 0.5487231 +#> [778] 0.6162800 0.5468457 0.6301377 0.5995084 0.6228970 0.5401552 0.5631466 +#> [785] 0.6127445 0.5816185 0.4899236 0.2738833 0.5013999 0.6284455 0.6337520 +#> [792] 0.6048169 0.5918516 0.6292261 0.4592504 0.5718666 0.5635348 0.5854236 +#> [799] 0.6221509 0.5449255 0.5285729 0.5627531 0.5806874 0.3841315 0.6361135 +#> [806] 0.4953878 0.2667858 0.3993132 0.2882159 0.4968648 0.6374088 0.5240189 +#> [813] 0.5362542 0.4707712 0.6339191 0.6041877 0.6267897 0.4756080 0.5976727 +#> [820] 0.6260212 0.4194103 0.6284446 0.6350910 0.6087920 0.6356300 0.5958984 +#> [827] 0.6310163 0.3259878 0.5644550 0.4025124 0.4647859 0.3350220 0.4787996 +#> [834] 0.5147372 0.4605675 0.4060274 0.3340625 0.6292617 0.6265776 0.3932857 +#> [841] 0.3936139 0.6181260 0.2751822 0.5248792 0.6232512 0.5162159 0.5780396 +#> [848] 0.4034054 0.5181452 0.4807423 0.6316876 0.6334858 0.6310610 0.4839599 +#> [855] 0.5750939 0.5370777 0.5093767 0.5120306 0.5233530 0.6354201 0.2962075 +#> [862] 0.5782383 0.5683217 0.4611922 0.5993442 0.6299565 0.3967887 0.5636015 +#> [869] 0.3589966 0.6286638 0.3697711 0.3839319 0.5386242 0.4775034 0.3151843 +#> [876] 0.3848272 0.5199583 0.3576236 0.3771404 0.5188192 0.6332005 0.4791474 +#> [883] 0.6147499 0.5578307 0.6313850 0.6328016 0.6224536 0.5193310 0.4795770 +#> [890] 0.6086293 0.5900342 0.4970858 0.4662966 0.3162165 0.6176077 0.5701661 +#> [897] 0.4242119 0.6240008 0.6042503 0.6096156 0.5053933 0.5363969 0.5835724 +#> [904] 0.3125335 0.5044611 0.5237147 0.4798265 0.5513145 0.6316295 0.3722557 +#> [911] 0.3716820 0.6111699 0.3147572 0.1665517 0.6008452 0.3884744 0.2472651 +#> [918] 0.6297467 0.5196551 0.6048465 0.5976123 0.4102202 0.5669134 0.3150132 +#> [925] 0.4263606 0.5836956 0.4205462 0.4141819 0.6175562 0.6280492 0.5094887 +#> [932] 0.3006627 0.2339602 0.2403166 0.4235083 0.5410465 0.3295207 0.6274547 +#> [939] 0.5640970 0.4055716 0.6021384 0.2571944 0.5019379 0.6317557 0.4388105 +#> [946] 0.6352481 0.6037116 0.4928955 0.4744770 0.4112464 0.6221252 0.5744561 +#> [953] 0.5196747 0.5177324 0.4725303 0.5587121 0.5585537 0.1482683 0.5471279 +#> [960] 0.5538261 0.6132567 0.5714377 0.4901121 0.4060362 0.5639523 0.6348961 +#> [967] 0.4908297 0.6177689 0.3039919 0.5438748 0.4519374 0.6351808 0.6227518 +#> [974] 0.5211898 0.3077041 0.5175888 0.5463321 0.3954046 0.3008286 0.6282459 +#> [981] 0.3172420 0.5813043 0.4692551 0.5616514 0.4113244 0.6319068 0.4315120 +#> [988] 0.6232941 0.3274399 0.4196015 0.5112502 0.5636144 0.5357319 0.5165005 +#> [995] 0.4347852 0.5814915 0.5295942 0.6302892 0.5095274 0.4470930 0.6220685 +#> [1002] 0.6031638 0.5949167 0.6332395 0.5989503 0.4453559 0.6328399 0.5651982 +#> [1009] 0.6081813 0.5669768 0.5472535 0.5261336 0.5440786 0.6110058 0.5082158 +#> [1016] 0.4718325 0.5522990 0.5560969 0.4491829 0.5057780 0.6105554 0.4780434 +#> [1023] 0.6116319 0.6038185 0.3424305 0.3603820 0.2130853 0.3988120 0.2610077 +#> [1030] 0.3814498 0.3731425 0.6180976 0.3364811 0.6106703 0.5128430 0.6151940 +#> [1037] 0.5981454 0.5689511 0.6093419 0.3599769 0.5463804 0.6335989 0.4059631 +#> [1044] 0.5331885 0.4484582 0.5590002 0.6327802 0.5436614 0.5495525 0.5807634 +#> [1051] 0.6128090 0.2821544 0.5573909 0.4761125 0.5243067 0.4863942 0.6291943 +#> [1058] 0.5738120 0.5922975 0.4232084 0.6236662 0.4973884 0.4038657 0.5869718 +#> [1065] 0.6191795 0.6176250 0.4489893 0.5195959 0.5209997 0.5380163 0.6338627 +#> [1072] 0.6290501 0.4816923 0.4699936 0.3456574 0.4313712 0.5583240 0.3774122 +#> [1079] 0.5187069 0.3385637 0.5766082 0.4384961 0.4563241 0.6331067 0.3736198 +#> [1086] 0.5947670 0.4947165 0.5546529 0.4455737 0.5281409 0.6302632 0.4240166 +#> [1093] 0.6301046 0.4300323 0.6331033 0.5411014 0.4835477 0.4245399 0.5643302 +#> [1100] 0.4911515 0.3679446 0.6028067 0.5493206 0.5818494 0.4996365 0.5383897 +#> [1107] 0.6296564 0.5610426 0.4290313 0.5697876 0.4876329 0.5334303 0.6214045 +#> [1114] 0.6242321 0.5279250 0.3855861 0.6086961 0.6281856 0.3786901 0.6059679 +#> [1121] 0.2535309 0.6340306 0.5594654 0.6125931 0.4160740 0.5635050 0.4950759 +#> [1128] 0.4331312 0.4884424 0.5715263 0.3458089 0.3655642 0.5036871 0.6232775 +#> [1135] 0.2499420 0.5653807 0.6155403 0.5865035 0.2647364 0.1980682 0.5651188 +#> [1142] 0.6263170 0.5027793 0.4218357 0.3580470 0.6171854 0.5077206 0.5458805 +#> [1149] 0.6259842 0.6297200 0.4418360 0.6227358 0.5706768 0.6196949 0.6354399 +#> [1156] 0.4550147 0.5848764 0.4938661 0.5647317 0.4791298 0.4863487 0.6065359 +#> [1163] 0.6339042 0.6291448 0.6331573 0.2358555 0.5680450 0.4994313 0.3529505 +#> [1170] 0.6146770 0.6317057 0.3084176 0.5801749 0.3492815 0.5661407 0.5810523 +#> [1177] 0.4668322 0.5739634 0.4232758 0.5200966 0.6164735 0.6312811 0.5540664 +#> [1184] 0.4969293 0.4102588 0.4164426 0.5657184 0.5887773 0.4635443 0.2040928 +#> [1191] 0.5754663 0.3152714 0.5703626 0.4391808 0.6312118 0.4716953 0.5158079 +#> [1198] 0.6142175 0.4995656 0.6353242 0.6167455 0.5114181 0.4269275 0.6241194 +#> [1205] 0.5978878 0.5409567 0.3522448 0.6350448 0.5440234 0.3962578 0.5740438 +#> [1212] 0.2733090 0.6221000 0.5441683 0.5600057 0.3422381 0.3836824 0.6247980 +#> [1219] 0.5252466 0.4477855 0.4956362 0.6065348 0.3377301 0.5913681 0.5682318 +#> [1226] 0.3786343 0.3460017 0.2519237 0.6042224 0.5471976 0.3315589 0.4970374 +#> [1233] 0.5816660 0.4974054 0.4294619 0.5675367 0.6202194 0.6343995 0.5662340 +#> [1240] 0.3066187 0.5884776 0.5597429 0.4816609 0.5456299 0.5817454 0.2912314 +#> [1247] 0.4911649 0.6104203 0.6147436 0.6368699 0.3366291 0.5475173 0.4425569 +#> [1254] 0.4999441 0.5678319 0.4872551 0.5613642 0.4713946 0.5598935 0.6357296 +#> [1261] 0.4043941 0.6261498 0.4924055 0.6160237 0.4680970 0.6367289 0.5177453 +#> [1268] 0.6283802 0.6282151 0.2980256 0.5299257 0.4584694 0.2777927 0.6146265 +#> [1275] 0.6178620 0.5350980 0.6334681 0.6077421 0.3503879 0.5926009 0.5645429 +#> [1282] 0.4508415 0.6240619 0.5530775 0.5230861 0.6184134 0.4504311 0.5232912 +#> [1289] 0.1651927 0.4719406 0.4932092 0.5911109 0.5244351 0.5634940 0.5780053 +#> [1296] 0.5169906 0.3957591 0.4517691 0.5402785 0.5437097 0.5212200 0.5487128 +#> [1303] 0.4910608 0.5330297 0.4387320 0.4304743 0.3845018 0.4730081 0.5163181 +#> [1310] 0.5078072 0.6315831 0.5080045 0.5173042 0.3910029 0.6239505 0.5397493 +#> [1317] 0.5832833 0.5870071 0.4363159 0.3533933 0.5290850 0.6120671 0.6217367 +#> [1324] 0.5957055 0.6175114 0.6268639 0.3341747 0.3015113 0.6184139 0.5833168 +#> [1331] 0.4767627 0.4209834 0.4351852 0.4955233 0.3594634 0.5898042 0.6045349 +#> [1338] 0.5635497 0.4628321 0.4604791 0.6024431 0.2893890 0.5749768 0.4549575 +#> [1345] 0.6313493 0.3366260 0.5007445 0.5854605 0.6124005 0.5670756 0.6338317 +#> [1352] 0.6101628 0.6047587 0.6041409 0.3889395 0.6102488 0.5077943 0.5087929 +#> [1359] 0.4704248 0.6181156 0.6110793 0.5851147 0.4507213 0.2697361 0.6137332 +#> [1366] 0.6202260 0.4233934 0.6143848 0.5960379 0.5857240 0.4136331 0.6291838 +#> [1373] 0.5467662 0.6348779 0.3898125 0.4807697 0.4792792 0.3733325 0.3052003 +#> [1380] 0.6277508 0.3236390 0.4026692 0.6101662 0.4783569 0.6293727 0.5298125 +#> [1387] 0.4163073 0.4357026 0.4492658 0.6083922 0.4626191 0.6062209 0.4116020 +#> [1394] 0.5794290 0.4331861 0.5376289 0.6303999 0.4395190 0.5786034 0.5793440 +#> [1401] 0.6304945 0.6315934 0.6288932 0.5577008 0.6259892 0.6159478 0.4838799 +#> [1408] 0.5311253 0.5547327 0.5473493 0.4343987 0.6369104 0.5792322 0.6081168 +#> [1415] 0.5559827 0.6127285 0.6329238 0.4449173 0.4246893 0.4837837 0.6306067 +#> [1422] 0.5853764 0.6231562 0.4503533 0.4414939 0.5782582 0.4457541 0.6002144 +#> [1429] 0.3549352 0.4954533 0.4846741 0.4300163 0.6302884 0.6065819 0.4723503 +#> [1436] 0.6302894 0.2460225 0.6239235 0.3783983 0.6127092 0.6034642 0.3289884 +#> [1443] 0.2807099 0.4588279 0.6363084 0.5725049 0.2593765 0.5299879 0.6221083 +#> [1450] 0.5913924 0.6274378 0.3755199 0.5202661 0.5445315 0.3752770 0.5868225 +#> [1457] 0.5711651 0.5172068 0.5071115 0.4207703 0.5634899 0.6130075 0.5765701 +#> [1464] 0.6151693 0.5095900 0.6139315 0.6308729 0.4339944 0.4129853 0.5888326 +#> [1471] 0.4726393 0.5556919 0.3708281 0.2586952 0.3328556 0.2889363 0.5516927 +#> [1478] 0.4918340 0.6324149 0.5737921 0.4425483 0.4149613 0.4739838 0.3870572 +#> [1485] 0.5987156 0.5463849 0.5333943 0.6172436 0.4433606 0.5063457 0.4399224 +#> [1492] 0.5404101 0.5915000 0.6156019 0.5626383 0.5053854 0.6209221 0.4434433 +#> [1499] 0.6100181 0.6086059 0.4983855 0.6231707 0.4794264 0.4589483 0.5932648 +#> [1506] 0.4110069 0.4176181 0.5350045 0.4465064 0.5387907 0.5327294 0.4038936 +#> [1513] 0.5854686 0.5314150 0.3039855 0.3259169 0.6062543 0.6329590 0.6022887 +#> [1520] 0.6314046 0.6075567 0.6164928 0.2751976 0.6300722 0.4074884 0.6244816 +#> [1527] 0.6278040 0.4949864 0.6320611 0.4979222 0.5801825 0.2875041 0.2758681 +#> [1534] 0.6357923 0.6334576 0.5479338 0.6366539 0.3911373 0.4749977 0.5985033 +#> [1541] 0.4022466 0.5428029 0.4865894 0.6332584 0.4959775 0.6076415 0.6067614 +#> [1548] 0.6024831 0.5056661 0.5700491 0.5236822 0.5953677 0.5667715 0.6158365 +#> [1555] 0.5728074 0.5469095 0.4852725 0.4802223 0.5956806 0.5391064 0.6218521 +#> [1562] 0.4098597 0.6034631 0.3450600 0.4125250 0.4606107 0.6264731 0.5123338 +#> [1569] 0.5218297 0.6136100 0.3912692 0.6279591 0.3259670 0.6117305 0.6012035 +#> [1576] 0.6215874 0.3402441 0.5547300 0.5453705 0.3925839 0.1948972 0.4552859 +#> [1583] 0.5687396 0.3960977 0.4028870 0.2387630 0.5719240 0.3310889 0.3454836 +#> [1590] 0.4356716 0.5909345 0.2985915 0.3560170 0.6339002 0.6282742 0.3376678 +#> [1597] 0.5295566 0.6365861 0.4750434 0.5289899 0.5775289 0.3784653 0.4701706 +#> [1604] 0.2963073 0.4474272 0.5341741 0.6046495 0.4778614 0.5353823 0.4741635 +#> [1611] 0.5044810 0.5436225 0.6362432 0.4612441 0.5707948 0.2933015 0.3550865 +#> [1618] 0.5506143 0.3620881 0.4737120 0.5117952 0.6088439 0.4941354 0.6274679 +#> [1625] 0.6364444 0.5827827 0.4556713 0.5745136 0.6227194 0.5102415 0.6150061 +#> [1632] 0.6054406 0.4983483 0.3636911 0.6101884 0.5860757 0.5232158 0.5146077 +#> [1639] 0.4046631 0.4465804 0.6106466 0.2373987 0.6231667 0.4778329 0.3437171 +#> [1646] 0.2980784 0.2858226 0.6329139 0.6258149 0.4482006 0.4881872 0.2375925 +#> [1653] 0.5862133 0.4355302 0.6359366 0.5663658 0.4073282 0.4629336 0.6251575 +#> [1660] 0.6113839 0.5097586 0.5829083 0.5986968 0.3414249 0.5994151 0.6054066 +#> [1667] 0.4485668 0.5769245 0.5496496 0.5396149 0.6369101 0.6091950 0.4772047 +#> [1674] 0.5656796 0.5475357 0.3847368 0.5097539 0.5805283 0.5000006 0.6151204 +#> [1681] 0.6051149 0.3627772 0.5923451 0.5458814 0.2755551 0.6338619 0.5769662 +#> [1688] 0.5650207 0.5732220 0.5559323 0.5904275 0.4505184 0.2769967 0.5877175 +#> [1695] 0.4490051 0.2869037 0.5444259 0.5830838 0.6336507 0.5229710 0.4628339 +#> [1702] 0.4621400 0.5179274 0.5328115 0.5736484 0.3765249 0.3592438 0.6097592 +#> [1709] 0.6124922 0.6368414 0.6343071 0.3991913 0.5717661 0.4978004 0.5201082 +#> [1716] 0.5749761 0.6348722 0.5738563 0.6106312 0.5943223 0.5071991 0.5888312 +#> [1723] 0.5725018 0.6116546 0.6114338 0.4143200 0.5095289 0.6337253 0.5617567 +#> [1730] 0.5486894 0.3703925 0.3851718 0.5442044 0.4475834 0.6246299 0.5791722 +#> [1737] 0.5500823 0.5284372 0.6260059 0.6004079 0.3268480 0.6314821 0.5902585 +#> [1744] 0.5461369 0.6324324 0.3431131 0.5046840 0.6271111 0.5429758 0.4375269 +#> [1751] 0.6287372 0.4521991 0.6227449 0.4833830 0.5576862 0.4618899 0.4730532 +#> [1758] 0.4448125 0.6328549 0.4489654 0.5182546 0.4367473 0.5303005 0.5706547 +#> [1765] 0.5913444 0.4733216 0.4300171 0.6115821 0.4850984 0.4484754 0.6166955 +#> [1772] 0.3346978 0.6178947 0.5304771 0.4277000 0.5507006 0.6120059 0.5093960 +#> [1779] 0.4163354 0.4488826 0.3450031 0.5383146 0.4497813 0.3932125 0.4223870 +#> [1786] 0.4894414 0.6098642 0.5662779 0.5758614 0.2386097 0.4074268 0.5893482 +#> [1793] 0.4135044 0.2729651 0.5104412 0.5082557 0.6311420 0.6366434 0.6273372 +#> [1800] 0.4180961 0.5421409 0.5041993 0.4513640 0.4824784 0.6299934 0.6244223 +#> [1807] 0.4434380 0.3202608 0.6209802 0.4923363 0.6298663 0.2946075 0.3467536 +#> [1814] 0.5363158 0.6230108 0.3341932 0.5193219 0.6306042 0.5677416 0.3684988 +#> [1821] 0.4261291 0.4772389 0.5520407 0.5648834 0.5018817 0.5434187 0.5048362 +#> [1828] 0.5912415 0.2467870 0.6202248 0.3788475 0.6122489 0.3876368 0.6247276 +#> [1835] 0.4213392 0.5593816 0.4254220 0.6330394 0.3918311 0.2481672 0.6256293 +#> [1842] 0.5159081 0.3867396 0.3804653 0.4711431 0.5737492 0.4909460 0.4964397 +#> [1849] 0.5671162 0.5293039 0.5494225 0.6362760 0.6281094 0.6360754 0.3914221 +#> [1856] 0.2947310 0.5439916 0.5208415 0.5548876 0.4545840 0.5742372 0.5721513 +#> [1863] 0.6263338 0.3505576 0.3236886 0.4613931 0.4331720 0.5715350 0.5788099 +#> [1870] 0.5361155 0.3250927 0.3946843 0.2321738 0.6192952 0.4727593 0.5136741 +#> [1877] 0.3684589 0.6020784 0.5662950 0.2313513 0.5735674 0.4815609 0.4555896 +#> [1884] 0.6316465 0.5962367 0.4251114 0.5779292 0.3568041 0.6308946 0.3727338 +#> [1891] 0.5881035 0.5079567 0.3856456 0.2486141 0.5007128 0.5957284 0.6145475 +#> [1898] 0.5634596 0.4325275 0.5210302 0.5228863 0.5949880 0.3689048 0.4091105 +#> [1905] 0.6204346 0.4848066 0.3127370 0.4355447 0.5146242 0.6084388 0.6049890 +#> [1912] 0.1723415 0.6273386 0.4811191 0.4521040 0.6051712 0.5387527 0.6233448 +#> [1919] 0.3821215 0.2916643 0.5595185 0.5467285 0.5658900 0.3263173 0.4134406 +#> [1926] 0.5523290 0.5619793 0.3995948 0.3634039 0.4799382 0.5501985 0.4901997 +#> [1933] 0.6319851 0.4878586 0.5291608 0.2302776 0.2082573 0.2585341 0.4439692 +#> [1940] 0.5880746 0.6227444 0.3952460 0.6288231 0.6305001 0.4640879 0.5451295 +#> [1947] 0.6354243 0.6339354 0.6341320 0.5361745 0.4993715 0.5440455 0.5289243 +#> [1954] 0.4013168 0.5634083 0.3410615 0.6362714 0.5831030 0.4705204 0.4324600 +#> [1961] 0.3646531 0.5511288 0.5281042 0.3706768 0.4644783 0.4738044 0.5187857 +#> [1968] 0.3116701 0.4117601 0.6251857 0.5095295 0.4580082 0.5848267 0.6061144 +#> [1975] 0.5911666 0.6369118 0.6319910 0.4011942 0.5563856 0.3983621 0.3940644 +#> [1982] 0.6369117 0.4995204 0.5696294 0.4741151 0.6339787 0.5302811 0.5375587 +#> [1989] 0.5505962 0.4429196 0.2524450 0.2321666 0.5868770 0.3314997 0.3145970 +#> [1996] 0.4007894 0.5652154 0.4837732 0.5859580 0.4361609 0.5064799 0.6347217 +#> [2003] 0.6227972 0.3012076 0.6036934 0.6117177 0.2851746 0.4228363 0.3755303 +#> [2010] 0.4732107 0.4589819 0.3569898 0.4433948 0.6322800 0.5692034 0.5738599 +#> [2017] 0.4987185 0.4023199 0.3304653 0.6288918 0.3310831 0.4746252 0.6118700 +#> [2024] 0.6144053 0.6287483 0.4237662 0.5543769 0.2806594 0.5038787 0.6195126 +#> [2031] 0.5377773 0.5928491 0.2932912 0.4103788 0.3109684 0.3133274 0.4967437 +#> [2038] 0.4487670 0.6289806 0.5902683 0.5427936 0.4742070 0.5678579 0.6206714 +#> [2045] 0.4539283 0.4681978 0.4961873 0.5775472 0.6360627 0.4065393 0.5519910 +#> [2052] 0.5570618 0.4942112 0.4860479 0.4822942 0.6126897 0.4541292 0.4225535 +#> [2059] 0.5250258 0.6292396 0.6228170 0.4852535 0.6345251 0.5404944 0.3822136 +#> [2066] 0.4410310 0.6170755 0.5832175 0.5191228 0.5314273 0.5868544 0.4887063 +#> [2073] 0.3515874 0.4828378 0.4778515 0.6319832 0.3463711 0.5272182 0.6314387 +#> [2080] 0.3012434 0.3618968 0.5942230 0.4874214 0.6063214 0.6337434 0.6269144 +#> [2087] 0.5840594 0.3784778 0.2469337 0.5194991 0.5351787 0.6287519 0.5037145 +#> [2094] 0.4016883 0.6189749 0.4021768 0.5624962 0.5776763 0.5191390 0.6130047 +#> [2101] 0.5020763 0.5209178 0.5879167 0.3925898 0.6299180 0.6214900 0.6317462 +#> [2108] 0.4311937 0.6097725 0.5217210 0.5768689 0.6247752 0.6049423 0.6369104 +#> [2115] 0.4807412 0.6301290 0.4628415 0.6308341 0.2622966 0.3483568 0.5818100 +#> [2122] 0.4559683 0.6327942 0.5879156 0.3396496 0.6283957 0.3182979 0.5453091 +#> [2129] 0.6300495 0.3594730 0.6100903 0.4159903 0.5385420 0.3924541 0.4183151 +#> [2136] 0.2893739 0.5954218 0.5115528 0.3707398 0.3571383 0.3661262 0.5935252 +#> [2143] 0.4341701 0.5529606 0.5959270 0.5472066 0.5970878 0.4557619 0.1923282 +#> [2150] 0.6007146 0.5809356 0.6090224 0.5198454 0.4780343 0.4391805 0.2254765 +#> [2157] 0.3841535 0.6233053 0.6339893 0.5696094 0.6010553 0.6282085 0.6079343 +#> [2164] 0.6297937 0.5976361 0.3830892 0.4269227 0.6333691 0.5846759 0.5848890 +#> [2171] 0.6166328 0.2837895 0.6334741 0.6360419 0.5108507 0.2738298 0.5216355 +#> [2178] 0.5288119 0.6083192 0.6213782 0.3753430 0.5932443 0.5392404 0.4066513 +#> [2185] 0.6191503 0.6083239 0.5243459 0.5468611 0.5401287 0.5696813 0.4931612 +#> [2192] 0.4775665 0.5727676 0.4673134 0.3555740 0.4436337 0.5139743 0.6352612 +#> [2199] 0.2805802 0.6049996 0.5547151 0.5263304 0.6065139 0.4961179 0.5934144 +#> [2206] 0.5546773 0.6279399 0.3231651 0.3890734 0.3387102 0.6252698 0.5242038 +#> [2213] 0.5672344 0.3458794 0.3828955 0.5929022 0.6326202 0.5605498 0.4327073 +#> [2220] 0.4457796 0.4326442 0.5262834 0.2624742 0.4461961 0.3638217 0.5951550 +#> [2227] 0.4970484 0.4807836 0.4638635 0.5996850 0.5210776 0.5681612 0.3698503 +#> [2234] 0.3184645 0.5282766 0.4324582 0.5292722 0.5409320 0.5344604 0.5532366 +#> [2241] 0.5310692 0.6239652 0.4450339 0.6177145 0.5333713 0.4597942 0.5821255 +#> [2248] 0.6337207 0.5909418 0.4663344 0.5934816 0.5907450 0.4595873 0.5436927 +#> [2255] 0.5590376 0.4160703 0.5414792 0.4165471 0.5746663 0.5360711 0.5391391 +#> [2262] 0.5743376 0.3439386 0.6123682 0.4694450 0.3076214 0.5676408 0.5869735 +#> [2269] 0.4927377 0.3013651 0.4034731 0.3359507 0.2380541 0.3329657 0.5333436 +#> [2276] 0.5803856 0.6328274 0.6106680 0.6275885 0.5789455 0.6103579 0.6073277 +#> [2283] 0.4507212 0.5681878 0.5194633 0.3372992 0.4741272 0.6156013 0.4906184 +#> [2290] 0.5996650 0.4674452 0.3412221 0.5326153 0.3994207 0.5959641 0.5958606 +#> [2297] 0.5750083 0.5756826 0.4507846 0.4528732 0.6184321 0.4549856 0.3732254 +#> [2304] 0.6189336 0.6142770 0.5932899 0.3837531 0.3790425 0.4530990 0.5761721 +#> [2311] 0.3818319 0.3978668 0.4276280 0.5538897 0.5691858 0.5106542 0.4167063 +#> [2318] 0.5960120 0.5983022 0.3263296 0.4477555 0.6298218 0.6360168 0.5070046 +#> [2325] 0.5634675 0.4686200 0.4893755 0.6312498 0.6197505 0.6116478 0.6030761 +#> [2332] 0.5739665 0.5168780 0.6003201 0.3844856 0.3014954 0.6079419 0.4479261 +#> [2339] 0.1752431 0.4173688 0.4793526 0.4975026 0.6294932 0.4725288 0.6334579 +#> [2346] 0.4411683 0.5636036 0.4505879 0.6369121 0.5455522 0.3703501 0.4668209 +#> [2353] 0.5235060 0.4246854 0.3670220 0.4271060 0.3247419 0.6260862 0.3135832 +#> [2360] 0.3697522 0.5842192 0.5733173 0.6052641 0.5034475 0.5704042 0.2531564 +#> [2367] 0.4801187 0.5992792 0.6280079 0.6300788 0.6178498 0.6004557 0.5301442 +#> [2374] 0.5800974 0.4047388 0.5636572 0.5764446 0.6335244 0.6175934 0.6129214 +#> [2381] 0.5346288 0.5329888 0.4111991 0.6079685 0.4900683 0.2430982 0.6151539 +#> [2388] 0.6267827 0.6329516 0.6345881 0.4023209 0.6326061 0.3661858 0.2973182 +#> [2395] 0.4091828 0.5485310 0.5714551 0.5112102 0.4918853 0.2469303 0.5451783 +#> [2402] 0.6097631 0.5773849 0.6262696 0.4940098 0.5959123 0.6369114 0.6303773 +#> [2409] 0.5357349 0.6146599 0.6283412 0.3402034 0.5277221 0.6186530 0.5520492 +#> [2416] 0.5083863 0.5410161 0.4800619 0.3862877 0.6057120 0.6072873 0.5282701 +#> [2423] 0.6345839 0.6049354 0.5851882 0.5620542 0.5306932 0.5536279 0.6286045 +#> [2430] 0.5013074 0.4954693 0.1296743 0.3568171 0.6274646 0.4435893 0.6186819 +#> [2437] 0.5970245 0.4405927 0.4326852 0.6367698 0.3906594 0.4666474 0.5930445 +#> [2444] 0.6127242 0.5058575 0.6108014 0.4421002 0.4050400 0.2318119 0.2529767 +#> [2451] 0.5041189 0.4249185 0.5908797 0.6234235 0.6205631 0.4525042 0.4885252 +#> [2458] 0.4397303 0.5609053 0.5694694 0.6200868 0.5440852 0.5573632 0.4971038 +#> [2465] 0.4242272 0.5339350 0.5060000 0.5207749 0.3597484 0.5040106 0.5195806 +#> [2472] 0.5211807 0.6230454 0.5631250 0.4887984 0.4557663 0.4425290 0.5136242 +#> [2479] 0.4209042 0.5351488 0.5756508 0.4096971 0.5455467 0.6295393 0.5512333 +#> [2486] 0.3386097 0.6302039 0.6298676 0.2325041 0.5842174 0.3681130 0.3910299 +#> [2493] 0.5885426 0.5748600 0.3956928 0.5371274 0.4206052 0.6290399 0.6287648 +#> [2500] 0.4090970 0.6264661 0.4158932 0.4981842 0.5267906 0.3991386 0.4725953 +#> [2507] 0.5985990 0.4068447 0.5753119 0.5356435 0.3930476 0.5387261 0.4377587 +#> [2514] 0.5488338 0.5280347 0.4194232 0.6311822 0.4517013 0.5640121 0.6307841 +#> [2521] 0.5324711 0.6306023 0.6367774 0.4568374 0.5290648 0.4773242 0.4636563 +#> [2528] 0.3657952 0.5639571 0.5068316 0.6146256 0.1183158 0.6338579 0.4793052 +#> [2535] 0.3070355 0.2972505 0.5502093 0.5312642 0.2292877 0.4320590 0.3185749 +#> [2542] 0.3330568 0.3971836 0.5234296 0.4840244 0.3790701 0.6026756 0.4905182 +#> [2549] 0.6369104 0.5817933 0.5532795 0.6187556 0.5570200 0.5747724 0.5783315 +#> [2556] 0.4106473 0.5848723 0.6254434 0.3648136 0.3237824 0.5828630 0.4025965 +#> [2563] 0.3382024 0.5606057 0.3377968 0.5373758 0.4126391 0.2630703 0.6294931 +#> [2570] 0.5789702 0.6185747 0.5421189 0.2769453 0.5320772 0.6283783 0.6106699 +#> [2577] 0.4269329 0.5047696 0.3053062 0.4446974 0.3992325 0.5508121 0.3360157 +#> [2584] 0.5293793 0.2987328 0.5407744 0.3580119 0.6291124 0.5807258 0.3976632 +#> [2591] 0.5988855 0.5674082 0.5850040 0.5811616 0.1986274 0.6302644 0.6046180 +#> [2598] 0.5525753 0.6292963 0.5854905 0.6129663 0.6322605 0.5878164 0.5438499 +#> [2605] 0.5871576 0.6311866 0.5891719 0.4890591 0.3994751 0.6352107 0.6195683 +#> [2612] 0.6292244 0.6180750 0.2172640 0.4014851 0.6332954 0.6282122 0.6162243 +#> [2619] 0.4412370 0.4544924 0.6211449 0.5650071 0.3293091 0.6272482 0.5144913 +#> [2626] 0.6063348 0.6177201 0.4958109 0.4334473 0.3053450 0.2104625 0.5760918 +#> [2633] 0.6192844 0.5476324 0.5597003 0.4361380 0.4739761 0.4572406 0.3928825 +#> [2640] 0.4897656 0.4025181 0.6078775 0.5260185 0.5705100 0.6256784 0.5993974 +#> [2647] 0.6109825 0.1902332 0.6327203 0.5421999 0.5398597 0.5953139 0.6305565 +#> [2654] 0.5404123 0.6350244 0.5187810 0.5180567 0.5486229 0.5282529 0.5231510 +#> [2661] 0.6213744 0.6296019 0.6255491 0.5824730 0.4207166 0.3023637 0.4744279 +#> [2668] 0.5668855 0.5003126 0.4479076 0.6337249 0.4727547 0.6347175 0.5705907 +#> [2675] 0.2652082 0.3968999 0.5466300 0.6308326 0.3987328 0.4836801 0.3874354 +#> [2682] 0.5694731 0.3154031 0.4461491 0.6213020 0.3144496 0.6064576 0.6107715 +#> [2689] 0.5625425 0.6141063 0.6262874 0.6324897 0.6236260 0.6286904 0.5993488 +#> [2696] 0.5578118 0.4176593 0.5044868 0.5861091 0.5987339 0.6093295 0.4844020 +#> [2703] 0.5835780 0.3852581 0.5930528 0.6342541 0.5132954 0.3569009 0.4635568 +#> [2710] 0.4512326 0.5630167 0.6233492 0.3736552 0.6020091 0.3702003 0.5001731 +#> [2717] 0.5905284 0.4954797 0.6136347 0.5565605 0.4523359 0.5847440 0.5774688 +#> [2724] 0.5659042 0.5879213 0.4153004 0.5798287 0.4868420 0.3739759 0.2596463 +#> [2731] 0.4224089 0.5218191 0.5995747 0.6144626 0.6349951 0.3038381 0.5186097 +#> [2738] 0.5947718 0.6030764 0.4949484 0.5769756 0.5508160 0.4489568 0.6078776 +#> [2745] 0.6002827 0.5006119 0.4936641 0.5586604 0.5901596 0.6304154 0.5298671 +#> [2752] 0.4146582 0.5993610 0.6057446 0.5479114 0.2142894 0.5873390 0.3537759 +#> [2759] 0.6287856 0.4442948 0.2947190 0.6296066 0.3539226 0.3481909 0.4009069 +#> [2766] 0.5813467 0.5504535 0.6133799 0.3725793 0.3056858 0.4681958 0.4556152 +#> [2773] 0.6366619 0.3709997 0.2074070 0.2181194 0.4918079 0.4319285 0.4538844 +#> [2780] 0.6037141 0.4146886 0.5743026 0.6295366 0.5377702 0.5368800 0.6208881 +#> [2787] 0.6179033 0.4486323 0.3559900 0.3242083 0.5824220 0.4633715 0.6044654 +#> [2794] 0.6288951 0.4612110 0.6369104 0.6127666 0.5748211 0.4525072 0.6084375 +#> [2801] 0.5090347 0.5356710 0.5143657 0.6038177 0.6003125 0.5193526 0.6225299 +#> [2808] 0.4741061 0.3163946 0.4017794 0.5699610 0.3027305 0.6232449 0.4736269 +#> [2815] 0.6234543 0.4203876 0.4407040 0.6354004 0.4625114 0.4163435 0.6138276 +#> [2822] 0.5654726 0.5627066 0.4218920 0.6219763 0.4306435 0.4711452 0.6243598 +#> [2829] 0.4397251 0.5071188 0.4242153 0.5399606 0.5569049 0.6369104 0.4907648 +#> [2836] 0.4521338 0.5681170 0.5589135 0.4416640 0.6314006 0.6027247 0.6256437 +#> [2843] 0.6065510 0.5951922 0.3970645 0.6144318 0.6323080 0.4597710 0.2996694 +#> [2850] 0.5923468 0.3823868 0.4746171 0.5587622 0.4883670 0.6239699 0.5055271 +#> [2857] 0.3233353 0.3289026 0.6348628 0.5175606 0.3097860 0.5682564 0.5171356 +#> [2864] 0.5263602 0.4961334 0.6355274 0.4899627 0.5587663 0.6364835 0.6356724 +#> [2871] 0.5896975 0.5628347 0.6115363 0.3166358 0.3925645 0.4944741 0.3410303 +#> [2878] 0.3986425 0.5596451 0.6210366 0.4963722 0.5400798 0.5032985 0.4194726 +#> [2885] 0.6317329 0.5158437 0.4630108 0.6327369 0.5136516 0.5448533 0.4352158 +#> [2892] 0.4496188 0.4631308 0.6045509 0.3904313 0.2384649 0.5567492 0.3982299 +#> [2899] 0.6115762 0.4132332 0.1923618 0.3716462 0.4327555 0.5582756 0.4044005 +#> [2906] 0.5286098 0.3085107 0.5557085 0.4987523 0.6229178 0.4430531 0.3789448 +#> [2913] 0.4079438 0.4071564 0.6342735 0.4998520 0.5685968 0.6255736 0.6202019 +#> [2920] 0.6123649 0.6343135 0.2888582 0.6051331 0.5502520 0.5386896 0.3877998 +#> [2927] 0.5855333 0.5042747 0.5173238 0.6208300 0.5148231 0.5538765 0.6354517 +#> [2934] 0.6342246 0.3621145 0.6302574 0.6147643 0.6269317 0.4850645 0.6165221 +#> [2941] 0.5225207 0.5655849 0.5068184 0.4243365 0.6308296 0.5344123 0.5213505 +#> [2948] 0.4093292 0.6311830 0.5404244 0.2934971 0.5384163 0.4321566 0.1719303 +#> [2955] 0.3438458 0.5928890 0.2673648 0.4275929 0.5135556 0.5962281 0.6096471 +#> [2962] 0.6088719 0.3137894 0.6248802 0.5748367 0.6264698 0.5111425 0.4717990 +#> [2969] 0.5564476 0.5070722 0.6103059 0.6241498 0.4048763 0.5560494 0.5395652 +#> [2976] 0.5608116 0.4005732 0.4039872 0.3985609 0.5897662 0.5236547 0.5294637 +#> [2983] 0.5555485 0.2686676 0.6129223 0.6303603 0.6229623 0.5079629 0.5588753 +#> [2990] 0.2465271 0.4056581 0.5990546 0.6055874 0.5484727 0.5684734 0.3977737 +#> [2997] 0.6052874 0.5870685 0.6369145 0.3714917 0.3617024 0.4506418 0.3886282 +#> [3004] 0.6345640 0.5099163 0.6035904 0.5571339 0.4133299 0.6290405 0.3908396 +#> [3011] 0.5199217 0.5669060 0.4821393 0.4557733 0.6298858 0.5607906 0.4383318 +#> [3018] 0.4594815 0.6002381 0.5865120 0.6124669 0.6284232 0.5671489 0.6284003 +#> [3025] 0.5420971 0.6312175 0.5443381 0.5475840 0.6096808 0.4621868 0.5127396 +#> [3032] 0.5023747 0.6240631 0.4405429 0.6358137 0.5611627 0.6112980 0.6223396 +#> [3039] 0.6348745 0.2195769 0.5095285 0.2744031 0.6279543 0.4791295 0.4960339 +#> [3046] 0.5726489 0.4870062 0.6252861 0.6217183 0.3224919 0.5416352 0.5986084 +#> [3053] 0.5081307 0.5394116 0.5940569 0.5224773 0.5966001 0.3017163 0.3742333 +#> [3060] 0.5910920 0.5926260 0.3108268 0.4880133 0.3651566 0.2838823 0.2789640 +#> [3067] 0.5634282 0.3472466 0.5993304 0.5556464 0.5686327 0.3035810 0.5957469 +#> [3074] 0.5594414 0.3396791 0.5740963 0.4157519 0.6025724 0.5079469 0.4356411 +#> [3081] 0.2594919 0.5167535 0.5378989 0.6290381 0.6168472 0.6193743 0.5276553 +#> [3088] 0.6338946 0.3479290 0.5301658 0.6360771 0.5862131 0.5814204 0.6334281 +#> [3095] 0.4357117 0.4601636 0.5095293 0.3715081 0.6037014 0.6328119 0.5964084 +#> [3102] 0.5123215 0.6331429 0.5971449 0.5681531 0.3454625 0.4296366 0.5661241 +#> [3109] 0.3335101 0.6303662 0.4151469 0.5677394 0.3544808 0.4501487 0.4639171 +#> [3116] 0.6034624 0.6338246 0.5328672 0.6278688 0.5794097 0.5072947 0.4090429 +#> [3123] 0.5719483 0.4080424 0.6179278 0.5691656 0.5099912 0.6072236 0.5008272 +#> [3130] 0.4621762 0.5526879 0.3515770 0.5803422 0.4991441 0.6203660 0.6177647 +#> [3137] 0.5983856 0.4976037 0.4783445 0.4416166 0.4589907 0.5377233 0.6225346 +#> [3144] 0.5976691 0.5910418 0.5601840 0.4608319 0.3674940 0.4488234 0.3917213 +#> [3151] 0.3104710 0.5340118 0.4837892 0.3847200 0.6233539 0.3821499 0.4922634 +#> [3158] 0.5625741 0.3053865 0.2059272 0.6138001 0.6296119 0.4190927 0.4377942 +#> [3165] 0.3663282 0.6323003 0.5808398 0.5505158 0.5230135 0.3766010 0.6251148 +#> [3172] 0.5659120 0.6064485 0.6155253 0.6291089 0.4067109 0.5380887 0.4609468 +#> [3179] 0.4597309 0.3747536 0.5837643 0.5841121 0.3302312 0.2725686 0.6314354 +#> [3186] 0.6278461 0.5580728 0.5006873 0.3118347 0.5884017 0.5890860 0.2866696 +#> [3193] 0.6034136 0.5482136 0.6100360 0.5190659 0.6165502 0.4509209 0.3848811 +#> [3200] 0.6152107 0.6307051 0.2237130 0.5985014 0.5513034 0.5334154 0.1803444 +#> [3207] 0.4588482 0.4145498 0.5435004 0.3790794 0.4278706 0.6363431 0.5348789 +#> [3214] 0.4457033 0.4776953 0.5582361 0.6280066 0.6267008 0.6297786 0.3352116 +#> [3221] 0.6368384 0.4434086 0.2929111 0.4487645 0.4572662 0.4861861 0.5308566 +#> [3228] 0.5742663 0.6273035 0.6125877 0.6345757 0.5228255 0.3958805 0.6302970 +#> [3235] 0.5024217 0.5680390 0.5133716 0.5730094 0.4488147 0.6264545 0.6304787 +#> [3242] 0.5562242 0.4158636 0.5986554 0.3875800 0.3328418 0.5796904 0.4597937 +#> [3249] 0.5539272 0.6257195 0.4707279 0.6495793 0.4429396 0.3819943 0.2692290 +#> [3256] 0.4328839 0.5053416 0.5177133 0.5302316 0.3344451 0.6363016 0.2036768 +#> [3263] 0.6334453 0.6324899 0.3900184 0.3815351 0.3945682 0.6171502 0.4596167 +#> [3270] 0.6118369 0.4756425 0.3579673 0.6301999 0.6302865 0.4723117 0.5450681 +#> [3277] 0.6357869 0.6333323 0.6022677 0.4981844 0.5934648 0.2035852 0.4804533 +#> [3284] 0.6267618 0.6307542 0.4338189 0.6369112 0.5105342 0.5846114 0.6304740 +#> [3291] 0.2557048 0.6146276 0.2950682 0.6338530 0.3790833 0.6118522 0.5215659 +#> [3298] 0.6013388 0.4584549 0.4614093 0.4965611 0.5895394 0.4908041 0.5638545 +#> [3305] 0.4570528 0.4718187 0.5656123 0.4917134 0.6016131 0.5255681 0.6090994 +#> [3312] 0.6276306 0.3197589 0.4975689 0.5196691 0.5945209 0.5995139 0.4732161 +#> [3319] 0.6347630 0.6154321 0.5883487 0.3593984 0.3603013 0.6108356 0.5565188 +#> [3326] 0.3504557 0.4637155 0.5644467 0.3916369 0.5309413 0.5541205 0.2761516 +#> [3333] 0.5050663 0.3887957 0.6103004 0.2496128 0.6210868 0.5507960 0.4603478 +#> [3340] 0.4644544 0.5505476 0.5351022 0.4396569 0.6119056 0.5840381 0.3821883 +#> [3347] 0.5532926 0.6324973 0.3412061 0.6174933 0.5581975 0.4561430 0.1900342 +#> [3354] 0.6109210 0.2664588 0.5785975 0.4593907 0.4720374 0.5789944 0.5740617 +#> [3361] 0.2501548 0.4661383 0.2340146 0.5129852 0.4430325 0.6102555 0.5338155 +#> [3368] 0.5626095 0.4302414 0.5053798 0.5969120 0.6265981 0.5409168 0.4743203 +#> [3375] 0.6274947 0.5572615 0.3997274 0.5376808 0.4708863 0.3462822 0.3702093 +#> [3382] 0.5243804 0.4324930 0.6068250 0.5142484 0.5146385 0.3595211 0.5439348 +#> [3389] 0.5547316 0.6242055 0.5527910 0.4887319 0.6238439 0.5088921 0.4925699 +#> [3396] 0.5258624 0.2644215 0.2959666 0.2390594 0.6214892 0.5131401 0.4367471 +#> [3403] 0.5594030 0.4392869 0.3289065 0.4991783 0.5900264 0.5582312 0.5958696 +#> [3410] 0.1783524 0.4638378 0.6085905 0.3491628 0.6369106 0.4482920 0.5533517 +#> [3417] 0.6270786 0.6183351 0.4603823 0.6144142 0.5482006 0.4882810 0.5286802 +#> [3424] 0.5688092 0.5257236 0.6312884 0.4028366 0.6307428 0.5058926 0.6369117 +#> [3431] 0.4867315 0.6280862 0.4638770 0.4692417 0.4680600 0.4067304 0.6217433 +#> [3438] 0.4947209 0.5732661 0.2212103 0.5292179 0.5768841 0.6142655 0.4843779 +#> [3445] 0.6014931 0.6282535 0.5870400 0.5176399 0.2780195 0.5906367 0.5911331 +#> [3452] 0.5119567 0.4438671 0.3201005 0.6360731 0.5878895 0.5072859 0.4329320 +#> [3459] 0.6320781 0.3777064 0.6232556 0.6113989 0.6357420 0.5396175 0.5044311 +#> [3466] 0.5035840 0.4840896 0.6353534 0.2919898 0.4033767 0.5203890 0.5718188 +#> [3473] 0.5580543 0.6255816 0.6298438 0.4672684 0.4173982 0.5744075 0.3554071 +#> [3480] 0.3801903 0.6010016 0.4203380 0.5689697 0.4333713 0.6134532 0.5824631 +#> [3487] 0.6101269 0.6186814 0.5184272 0.4890491 0.4986742 0.6306463 0.6258287 +#> [3494] 0.1934887 0.2711501 0.6087002 0.6143715 0.4514379 0.3784509 0.3706565 +#> [3501] 0.3196433 0.4195021 0.5921564 0.5845498 0.4913533 0.6142648 0.4766353 +#> [3508] 0.4109640 0.5154899 0.4209179 0.5454195 0.3837023 0.6210054 0.4722505 +#> [3515] 0.6259796 0.6149687 0.5235772 0.5963871 0.5787429 0.3295699 0.6029252 +#> [3522] 0.3187022 0.6157331 0.5978467 0.4609694 0.2972403 0.5722204 0.6081280 +#> [3529] 0.5779518 0.6329917 0.5633119 0.6232546 0.4458382 0.5635674 0.6115757 +#> [3536] 0.4872318 0.4030873 0.3909856 0.3636743 0.6360057 0.6187438 0.6059997 +#> [3543] 0.6293559 0.6075114 0.5300763 0.3177626 0.6083272 0.3415715 0.6258765 +#> [3550] 0.3821465 0.5211315 0.5429676 0.6245162 0.6100453 0.4443913 0.6001359 +#> [3557] 0.5687252 0.4239925 0.5306718 0.5289823 0.5131618 0.5347994 0.4716853 +#> [3564] 0.5273152 0.6190847 0.3692698 0.4158002 0.6147544 0.5709565 0.4924391 +#> [3571] 0.4738944 0.3931239 0.6262778 0.4071578 0.3523648 0.5916655 0.5600956 +#> [3578] 0.5039561 0.6102051 0.5691568 0.6267862 0.5346640 0.2540282 0.5807614 +#> [3585] 0.6263514 0.6249909 0.4986141 0.3438650 0.6186375 0.4323231 0.4551286 +#> [3592] 0.3688901 0.5478050 0.6247936 0.3984848 0.3794920 0.6281247 0.5527842 +#> [3599] 0.5204505 0.6369086 0.2888141 0.5180764 0.5982488 0.5068124 0.5512984 +#> [3606] 0.5676704 0.5173552 0.4727647 0.4305215 0.5690880 0.5739830 0.5130377 +#> [3613] 0.6093084 0.5640937 0.5193017 0.5142176 0.4586762 0.4122244 0.1951309 +#> [3620] 0.5673822 0.6024857 0.6076701 0.6297671 0.6247530 0.3913186 0.4630804 +#> [3627] 0.3647691 0.6274505 0.5203262 0.4477268 0.3184938 0.5659710 0.6316619 +#> [3634] 0.4527461 0.5012234 0.5209883 0.3635174 0.4124998 0.5881357 0.3273297 +#> [3641] 0.6145006 0.5025666 0.5238870 0.6008323 0.4254784 0.3009745 0.4344746 +#> [3648] 0.3203702 0.6105185 0.2781995 0.4907878 0.5371477 0.4828769 0.4141057 +#> [3655] 0.3591675 0.4600314 0.5740461 0.6317733 0.6264694 0.3522694 0.5435439 +#> [3662] 0.5189347 0.6349704 0.5190597 0.6269514 0.4769063 0.5008457 0.6024170 +#> [3669] 0.6292064 0.5598544 0.6025701 0.6185829 0.5750393 0.4402956 0.6183090 +#> [3676] 0.3596946 0.4837734 0.6278878 0.6338061 0.5923131 0.6268727 0.6263028 +#> [3683] 0.4567942 0.6359031 0.6150230 0.3624116 0.5677808 0.5826796 0.5461830 +#> [3690] 0.2898668 0.4027548 0.3015389 0.5658301 0.3826520 0.3229918 0.6184371 +#> [3697] 0.3390099 0.6179911 0.5653373 0.5120229 0.3063482 0.4881999 0.6114410 +#> [3704] 0.4998808 0.5148229 0.4683375 0.5273384 0.5112149 0.5181652 0.6350421 +#> [3711] 0.5710861 0.6308949 0.5900232 0.5454901 0.4417450 0.6369106 0.4792824 +#> [3718] 0.6361796 0.6182973 0.2573387 0.5486135 0.6298308 0.6305673 0.4891774 +#> [3725] 0.4490347 0.6334593 0.3764663 0.5461177 0.3771629 0.4458399 0.5103517 +#> [3732] 0.5418205 0.3940085 0.5972542 0.6135480 0.6110561 0.4672153 0.6369102 +#> [3739] 0.2304307 0.3838445 0.4786765 0.4425763 0.4701290 0.5338481 0.6160972 +#> [3746] 0.5732186 0.5323334 0.4362384 0.6291748 0.6339490 0.6257003 0.2807371 +#> [3753] 0.6212660 0.6359416 0.3601384 0.2391376 0.5986597 0.4068304 0.5065448 +#> [3760] 0.5262056 0.5464248 0.6348024 0.5064300 0.6104051 0.6093447 0.6136183 +#> [3767] 0.4458391 0.5467007 0.6332654 0.4860483 0.5001805 0.4086992 0.5221526 +#> [3774] 0.5055404 0.2713342 0.4620180 0.5919783 0.4765556 0.4461742 0.5328085 +#> [3781] 0.5046388 0.3940998 0.5412305 0.4774405 0.6027331 0.3607022 0.6151030 +#> [3788] 0.2887006 0.3381159 0.6151653 0.4549825 0.6363679 0.4510402 0.6257574 +#> [3795] 0.6227050 0.5219282 0.6360436 0.6246924 0.3346736 0.3931921 0.6121317 +#> [3802] 0.5879666 0.4815791 0.6269361 0.5744716 0.5015397 0.6219134 0.6074028 +#> [3809] 0.5458408 0.6042861 0.1949182 0.6344201 0.4285268 0.4021498 0.5459987 +#> [3816] 0.5440071 0.5167993 0.5725012 0.6346676 0.4528055 0.5354914 0.6313299 +#> [3823] 0.5533930 0.6320343 0.5346752 0.3778646 0.3983797 0.4053741 0.5454896 +#> [3830] 0.3564750 0.6317236 0.5235996 0.4143179 0.6146272 0.6330911 0.5218608 +#> [3837] 0.4810961 0.4951412 0.3472468 0.5714967 0.4426743 0.4485942 0.5669155 +#> [3844] 0.3379695 0.4388100 0.6265951 0.5072967 0.6341886 0.5310442 0.4546522 +#> [3851] 0.5670802 0.5880021 0.4453740 0.3691828 0.3198036 0.4542413 0.4507054 +#> [3858] 0.4120320 0.5230748 0.5833598 0.4710414 0.5990232 0.6182452 0.4936832 +#> [3865] 0.4132809 0.3188285 0.5606089 0.3754222 0.3316384 0.2239015 0.4997745 +#> [3872] 0.6039282 0.3664721 0.3172271 0.3389916 0.5709806 0.5308513 0.6143161 +#> [3879] 0.4797094 0.5480376 0.6173360 0.5807122 0.4503719 0.5936099 0.4976486 +#> [3886] 0.6036774 0.4701907 0.5367710 0.5135639 0.5423534 0.3282334 0.6177942 +#> [3893] 0.4372130 0.6291099 0.4599810 0.5885721 0.4640143 0.6074936 0.4386896 +#> [3900] 0.6097506 0.4728907 0.4274533 0.5730902 0.5804475 0.6082078 0.3463848 +#> [3907] 0.2280309 0.5159451 0.5207174 0.3562619 0.4189675 0.5623568 0.4752830 +#> [3914] 0.5372956 0.4442139 0.1096242 0.5952247 0.3999832 0.4708116 0.4906764 +#> [3921] 0.5241325 0.4235113 0.3216144 0.5868983 0.2373504 0.6036974 0.5214860 +#> [3928] 0.6338367 0.6295658 0.5951055 0.5980147 0.4561856 0.4785608 0.4656968 +#> [3935] 0.4286087 0.5705288 0.4513459 0.3284322 0.5732264 0.5934668 0.5393466 +#> [3942] 0.6333448 0.5270635 0.5811503 0.6368463 0.6121571 0.5926069 0.5306942 +#> [3949] 0.5022635 0.6348287 0.6255405 0.5106743 0.6355242 0.5975021 0.5681874 +#> [3956] 0.4883251 0.6029598 0.6337858 0.5271924 0.3306315 0.5387904 0.6368013 +#> [3963] 0.5721811 0.5902605 0.4411248 0.5619797 0.5831648 0.2448906 0.3805973 +#> [3970] 0.3331106 0.4781760 0.6091538 0.3075213 0.5210539 0.4882983 0.4362384 +#> [3977] 0.3286332 0.4117142 0.6365419 0.5811538 0.3618724 0.5165962 0.6155566 +#> [3984] 0.5615394 0.6096728 0.6213170 0.3911546 0.5708003 0.6216691 0.4205463 +#> [3991] 0.4111180 0.4160102 0.3819556 0.6206485 0.6264400 0.5383150 0.3897398 +#> [3998] 0.4991718 0.5197969 0.5634302 0.5210519 0.5040387 0.6293950 0.5012811 +#> [4005] 0.4871134 0.5995844 0.5744858 0.4879010 0.5109101 0.6214431 0.5885454 +#> [4012] 0.6364190 0.3569088 0.3303843 0.6298271 0.5987590 0.4595496 0.4577400 +#> [4019] 0.5375422 0.6022653 0.5471628 0.6157577 0.5631214 0.5489576 0.5854843 +#> [4026] 0.6146872 0.5993336 0.5354352 0.6313256 0.4850182 0.6092623 0.3824842 +#> [4033] 0.4153150 0.6301103 0.4463964 0.6368904 0.5619797 0.5930009 0.1891379 +#> [4040] 0.6159425 0.5882557 0.4971581 0.6272486 0.4797451 0.4364980 0.2852417 +#> [4047] 0.6112165 0.6097144 0.3426063 0.3940487 0.3671545 0.2281678 0.5540274 +#> [4054] 0.5569929 0.6210620 0.5320490 0.6186190 0.5088421 0.3541914 0.5356375 +#> [4061] 0.4103359 0.6194010 0.5990995 0.3088515 0.5364505 0.4909019 0.6007837 +#> [4068] 0.4947507 0.6000907 0.6016565 0.3720277 0.5319102 0.3095872 0.3828208 +#> [4075] 0.3868484 0.6253720 0.6213589 0.3877948 0.5769803 0.1563225 0.3823040 +#> [4082] 0.2574433 0.2584788 0.5559601 0.6310590 0.6117479 0.4054532 0.6281009 +#> [4089] 0.3217017 0.5700503 0.6095712 0.5057166 0.6362077 0.5821489 0.5351064 +#> [4096] 0.5265292 0.6242222 0.4471758 0.3239184 0.6083604 0.6331865 0.4133356 +#> [4103] 0.5790284 0.5542567 0.2706862 0.3517855 0.4602144 0.5858595 0.4963304 +#> [4110] 0.5350697 0.5185547 0.5362855 0.6105633 0.5506644 0.6070822 0.5905025 +#> [4117] 0.3400886 0.5127954 0.6184899 0.6352624 0.6214443 0.5350407 0.4190501 +#> [4124] 0.5100468 0.4192089 0.5210347 0.6254598 0.3626406 0.4579501 0.5548118 +#> [4131] 0.6245711 0.6369106 0.4491597 0.6152400 0.5827681 0.4154780 0.6313749 +#> [4138] 0.6033329 0.5402672 0.4852744 0.5821671 0.4409336 0.5330259 0.3593879 +#> [4145] 0.6325159 0.3559700 0.5669812 0.3657824 0.6315020 0.6033310 0.6289124 +#> [4152] 0.4489787 0.3434387 0.5297727 0.5211333 0.4586662 0.5610688 0.5386223 +#> [4159] 0.6146293 0.5708801 0.2067429 0.3633706 0.6358211 0.4669696 0.6149925 +#> [4166] 0.4484755 0.3863297 0.3619725 0.6369106 0.6221479 0.6020897 0.3358729 +#> [4173] 0.5948720 0.5798501 0.2406030 0.3974331 0.6079447 0.5226196 0.5688166 +#> [4180] 0.5905531 0.2499876 0.4897748 0.6314152 0.5682291 0.5005882 0.5898920 +#> [4187] 0.5852744 0.6142349 0.5592701 0.5126985 0.4888525 0.5631480 0.3253444 +#> [4194] 0.5005871 0.5238483 0.5886658 0.1757015 0.5821659 0.6115069 0.2789167 +#> [4201] 0.6234444 0.5963998 0.5315794 0.3556426 0.6349985 0.6231765 0.4628741 +#> [4208] 0.5352997 0.4345677 0.6173525 0.3958366 0.5437832 0.5319007 0.6327879 +#> [4215] 0.3799331 0.3649243 0.3455730 0.5361298 0.6025531 0.5849094 0.6233077 +#> [4222] 0.5879170 0.3787164 0.5570869 0.3864203 0.5231225 0.4420124 0.6089305 +#> [4229] 0.5915629 0.4611330 0.5461340 0.3948583 0.6271072 0.6299048 0.4518073 +#> [4236] 0.5324884 0.3593800 0.6225875 0.4667405 0.5125028 0.6116714 0.6114348 +#> [4243] 0.6044332 0.3782661 0.5953791 0.6345287 0.5888894 0.6366901 0.5159317 +#> [4250] 0.6150809 0.4696732 0.4777463 0.5189878 0.4536016 0.4584944 0.5126900 +#> [4257] 0.5193451 0.3611183 0.6091416 0.3472564 0.3719584 0.4319101 0.6357095 +#> [4264] 0.6333032 0.4653752 0.4400601 0.4621183 0.4426162 0.5661898 0.5839866 +#> [4271] 0.2645765 0.6324547 0.5594476 0.5191577 0.5995430 0.6167255 0.6301343 +#> [4278] 0.3524658 0.4655258 0.6077885 0.6091171 0.6223702 0.5859277 0.4272668 +#> [4285] 0.4019224 0.3721495 0.3183092 0.5095277 0.5667522 0.5270506 0.5940202 +#> [4292] 0.3186357 0.4287824 0.5165985 0.5190420 0.4599610 0.5566849 0.4905402 +#> [4299] 0.3971028 0.4803544 0.6310353 0.5494902 0.6298573 0.2492812 0.5510196 +#> [4306] 0.4532847 0.5763562 0.5491684 0.6313878 0.5619802 0.3931553 0.5650549 +#> [4313] 0.3122940 0.5692971 0.5984222 0.5319235 0.4091916 0.3653561 0.4828112 +#> [4320] 0.6251863 0.5925445 0.4778107 0.4343600 0.3202162 0.4033376 0.5844927 +#> [4327] 0.6365427 0.5905603 0.3286733 0.5488324 0.4756731 0.6112873 0.5458520 +#> [4334] 0.5400712 0.5455378 0.5113755 0.6216147 0.4718333 0.5960861 0.5579662 +#> [4341] 0.5280775 0.5785965 0.5354532 0.5313242 0.6315661 0.4114966 0.5460248 +#> [4348] 0.3384057 0.3755427 0.6293490 0.4234694 0.5346210 0.4891615 0.5293833 +#> [4355] 0.3134098 0.3298352 0.4334266 0.3053160 0.4333696 0.4498249 0.2942889 +#> [4362] 0.5267424 0.4718064 0.3426198 0.6175684 0.4980750 0.6028395 0.4555448 +#> [4369] 0.5794514 0.6230575 0.6140911 0.5940654 0.6164818 0.5969333 0.4840193 +#> [4376] 0.5507045 0.4063337 0.5953641 0.2969890 0.6296131 0.6339095 0.6316090 +#> [4383] 0.4610859 0.5707875 0.5960526 0.5532026 0.5864438 0.5058502 0.5067188 +#> [4390] 0.4656487 0.3934297 0.4464434 0.6126914 0.5586972 0.4549774 0.5205904 +#> [4397] 0.5917041 0.5376045 0.6115122 0.6137490 0.4728770 0.6367022 0.6354970 +#> [4404] 0.5907463 0.5212008 0.1453867 0.3099957 0.4632644 0.6153668 0.4882820 +#> [4411] 0.5779250 0.3791398 0.3276905 0.2058800 0.6148391 0.5804731 0.5633594 +#> [4418] 0.6062137 0.5431351 0.5378980 0.5741983 0.4739580 0.5654598 0.3985540 +#> [4425] 0.3648369 0.5797361 0.6302228 0.5637265 0.5317501 0.5631197 0.6249316 +#> [4432] 0.3670435 0.5421665 0.5177145 0.6150289 0.5506808 0.6158615 0.6363551 +#> [4439] 0.5011015 0.6174235 0.6170199 0.3096611 0.4519752 0.6253330 0.5339525 +#> [4446] 0.6227528 0.3997533 0.4141252 0.6303104 0.6356039 0.5432019 0.3171329 +#> [4453] 0.4081391 0.4183592 0.6346883 0.6242866 0.6357510 0.4238847 0.4617965 +#> [4460] 0.4594035 0.4140467 0.1655853 0.3882195 0.4241209 0.4714462 0.5742087 +#> [4467] 0.4309715 0.5563638 0.5703033 0.3659860 0.5588230 0.4385503 0.5708888 +#> [4474] 0.6321887 0.5283159 0.6262963 0.6319193 0.4950131 0.3416201 0.5309657 +#> [4481] 0.5743674 0.6292675 0.4299350 0.4132151 0.5252471 0.6360271 0.5352784 +#> [4488] 0.4776243 0.3584114 0.6347979 0.4082163 0.6320740 0.4619120 0.4345062 +#> [4495] 0.3689240 0.3254400 0.2629398 0.6329414 0.6178299 0.6355908 0.4694995 +#> [4502] 0.3577770 0.4467328 0.6316813 0.4552549 0.3014941 0.3506316 0.5509595 +#> [4509] 0.6165754 0.6155829 0.3952426 0.6231411 0.4941218 0.4262298 0.5909181 +#> [4516] 0.6275145 0.3327607 0.2413231 0.5083997 0.5487230 0.3510118 0.5847276 +#> [4523] 0.3109435 0.6236039 0.5061212 0.6203271 0.4380845 0.5102531 0.4588599 +#> [4530] 0.4352797 0.5615087 0.5676219 0.5649160 0.5573477 0.6164411 0.6157224 +#> [4537] 0.4667246 0.1955740 0.5856601 0.6099131 0.6133317 0.5434854 0.5968405 +#> [4544] 0.2203402 0.4824638 0.6181267 0.2903717 0.4198612 0.4820502 0.5962295 +#> [4551] 0.4932247 0.2393185 0.4020831 0.4982674 0.6057864 0.5034477 0.6279723 +#> [4558] 0.2713626 0.5424771 0.6195944 0.6105145 0.3590346 0.5005374 0.5442365 +#> [4565] 0.6204369 0.6278050 0.5157551 0.5195190 0.4862176 0.5613418 0.6348135 +#> [4572] 0.5259155 0.6052678 0.6311524 0.4202718 0.4789861 0.4021168 0.4907479 +#> [4579] 0.5068072 0.4774207 0.3757992 0.6309779 0.4676612 0.6339775 0.6156573 +#> [4586] 0.6313328 0.5741791 0.5496476 0.5669002 0.5787881 0.6360279 0.4448745 +#> [4593] 0.5080987 0.4768573 0.3220227 0.4328498 0.5398282 0.4244484 0.4984423 +#> [4600] 0.6403123 0.4432516 0.6304975 0.6029341 0.6167257 0.4375318 0.3654554 +#> [4607] 0.4385043 0.5980246 0.3912157 0.5945868 0.4851988 0.3245049 0.4874066 +#> [4614] 0.4018720 0.5553939 0.5500917 0.6129676 0.4642407 0.5101783 0.4742019 +#> [4621] 0.6219368 0.6026100 0.6321751 0.4695805 0.4160222 0.5568298 0.6113268 +#> [4628] 0.6275907 0.3325223 0.4086579 0.5625742 0.5788110 0.4819513 0.6060625 +#> [4635] 0.6131673 0.6287678 0.5920882 0.5902790 0.3348093 0.5930340 0.4435125 +#> [4642] 0.6325741 0.5520966 0.3211748 0.1983036 0.5341876 0.2085808 0.4727417 +#> [4649] 0.3336638 0.3900748 0.5214936 0.5706710 0.5013279 0.3858841 0.4766790 +#> [4656] 0.6126394 0.4846971 0.5788290 0.5629782 0.5684513 0.6294643 0.4517216 +#> [4663] 0.5416332 0.6209928 0.6234923 0.4370411 0.6141623 0.6227825 0.6302014 +#> [4670] 0.6323689 0.4290763 0.6347432 0.6317379 0.5784941 0.6336012 0.6191450 +#> [4677] 0.6293329 0.5522998 0.6335423 0.5039744 0.5731648 0.3763982 0.4044633 +#> [4684] 0.5580479 0.5856989 0.6026175 0.6234935 0.4355524 0.5003682 0.5378493 +#> [4691] 0.5577187 0.6307673 0.5808799 0.3715707 0.5496894 0.3419283 0.3590336 +#> [4698] 0.5013951 0.5741606 0.5343947 0.6021085 0.6279210 0.6115905 0.4816849 +#> [4705] 0.4895301 0.6288869 0.4722086 0.4253644 0.6293551 0.4861135 0.5725141 +#> [4712] 0.6128914 0.5499568 0.5766701 0.5737479 0.6013911 0.6201410 0.4055715 +#> [4719] 0.6103754 0.3503273 0.5079901 0.6256653 0.5440048 0.5223941 0.6327004 +#> [4726] 0.6297459 0.5307329 0.5378900 0.4627012 0.4004260 0.6346723 0.5944663 +#> [4733] 0.5505734 0.3934001 0.6132112 0.4062199 0.3738838 0.2694507 0.2024100 +#> [4740] 0.3773575 0.3970910 0.5674371 0.5168236 0.5356884 0.5632947 0.2549956 +#> [4747] 0.5893061 0.4354320 0.5173752 0.5339524 0.3419737 0.4814351 0.5846620 +#> [4754] 0.4827988 0.3627688 0.6336914 0.6265528 0.4631218 0.5312448 0.5600759 +#> [4761] 0.5098199 0.6259500 0.2941946 0.4874663 0.4620313 0.4456736 0.6235104 +#> [4768] 0.5985780 0.3608285 0.4104356 0.5682689 0.5566831 0.5499328 0.6050595 +#> [4775] 0.6291885 0.5017432 0.4141900 0.4269333 0.2958799 0.3414143 0.5880652 +#> [4782] 0.6352995 0.5762762 0.3546546 0.5179475 0.3122736 0.4354972 0.2533707 +#> [4789] 0.5122746 0.5035657 0.5989641 0.4978931 0.4229810 0.6090158 0.5080259 +#> [4796] 0.6292715 0.6023587 0.5267277 0.3833753 0.5481163 0.4867613 0.4449092 +#> [4803] 0.4936849 0.4781572 0.5758537 0.5427582 0.5534020 0.6247435 0.3017951 +#> [4810] 0.5540690 0.6078565 0.5394593 0.4751266 0.4510126 0.3561680 0.6199954 +#> [4817] 0.6353841 0.5369593 0.3569629 0.5326531 0.5731053 0.2659282 0.5882816 +#> [4824] 0.3637888 0.4868961 0.4664551 0.3748456 0.6262747 0.5601560 0.4942092 +#> [4831] 0.6097262 0.4618201 0.5521196 0.5914855 0.4951327 0.5597387 0.4215587 +#> [4838] 0.3279098 0.4866199 0.5289028
    +# how the calculation is done - first observation +fs_area(buildings[1,]) / fs_area(fs_mbc(buildings[1,])) +
    #> 0.573271 [1]
    +
    +
    + +
    + + +
    + + +
    +

    Site built with pkgdown 1.6.1.

    +
    + +
    +
    + + + + + + + + diff --git a/docs/reference/fs_varlist.html b/docs/reference/fs_varlist.html new file mode 100644 index 0000000..0130074 --- /dev/null +++ b/docs/reference/fs_varlist.html @@ -0,0 +1,193 @@ + + + + + + + + +Select variables — fs_varlist • foot + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    +
    + + +
    +

    Functions to construct and check for lists of variables for + foot.

    +
    + +
    fs_varlist(...)
    +
    +is.fs_varlist(x)
    + +

    Arguments

    + + + + + + + + + + +
    ...

    Variables to include together as parameters for summary functions.

    x

    object to be tested.

    + +

    Details

    + +

    This helper function is used by calculate_footstats to select + multiple columns or building characteristics to be used in a summary + function. This facilitates using functions that take multiple arguments.

    +

    See also

    + +

    [calculate_footstats] for examples of summary functions.

    + +
    + +
    + + +
    + + +
    +

    Site built with pkgdown 1.6.1.

    +
    + +
    +
    + + + + + + + + diff --git a/docs/reference/gen_nnindex.html b/docs/reference/gen_nnindex.html new file mode 100644 index 0000000..0eb5185 --- /dev/null +++ b/docs/reference/gen_nnindex.html @@ -0,0 +1,198 @@ + + + + + + + + +Generate a nearest neighbour index function — gen_nnindex • foot + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    +
    + + +
    +

    Creates a new instance of the fs_nnindex function and + initialises it with zone and unit information.

    +
    + +
    gen_nnindex(zone, zoneField = NULL, unit = "m")
    + +

    Arguments

    + + + + + + + + + + + + + + +
    zone

    A spatial polygon object of sf or sp type. If +omitted all observations in X are assumed to be within one zone and +the area of the minimum bounding circle is used for the nearest neighbour +index.

    zoneField

    (Optional) Column name of unique identifiers in zone +to use. If omitted, the 'zoneID' will be numbered 1:nrow(zone).

    unit

    character or units object to define distance. Default will +attempt to coerce units to meters.

    + +

    Details

    + +

    This is a function factory. It creates a partial function in order + to allow fs_nnindex to be used by the internal loop of + calculate_footstats. This function will generally not be used on its + own.

    + +
    + +
    + + +
    + + +
    +

    Site built with pkgdown 1.6.1.

    +
    + +
    +
    + + + + + + + + diff --git a/docs/reference/gridTiles.html b/docs/reference/gridTiles.html new file mode 100644 index 0000000..47db20e --- /dev/null +++ b/docs/reference/gridTiles.html @@ -0,0 +1,221 @@ + + + + + + + + +gridTiles — gridTiles • foot + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    +
    + + +
    +

    Split gridded data extents into subsets for parallel processing.

    +
    + +
    gridTiles(X, n = NULL, px = c(1000, 1000), overlap = 0)
    +
    +# S3 method for stars
    +gridTiles(X, n = NULL, px = c(1000, 1000), overlap = 0)
    +
    +# S3 method for RasterLayer
    +gridTiles(X, n = NULL, px = c(1000, 1000), overlap = 0)
    + +

    Arguments

    + + + + + + + + + + + + + + + + + + +
    X

    The gridded (raster or stars) object to find tiles

    n

    number of tiles. Can be a vector length 1 or of length 2 (rows, +columns)

    px

    number of pixels per side of a tile. Can be a vector of length 2 +(rows, column pixels). Ignored if n provided. Default is 1000.

    overlap

    number of pixels of overlap between internal tiles

    + +

    Value

    + +

    data.frame where each row is a set of tile indices and sizes.

      +
    • xl leftmost column index

    • +
    • yl lowest row index

    • +
    • xu rightmost column index

    • +
    • yu topmost row index

    • +
    • cropXsize number of columns in the tile

    • +
    • cropYsize number of rows in the tile

    • +
    + +

    @examples + data("kampala") + # example dataset + g <- kampala$mastergrid

    +

    gridTiles(g) + gridTiles(g, n=2) + gridTiles(g, px=c(5, 25))

    +

    Details

    + +

    gridTiles provides a convenient way for splitting a gridded + dataset into sub-datasets for cropping or processing individually. The + splitting uses the extent of the total grid, potentially including noData + cells.

    + +
    + +
    + + +
    + + +
    +

    Site built with pkgdown 1.6.1.

    +
    + +
    +
    + + + + + + + + diff --git a/docs/reference/index.html b/docs/reference/index.html new file mode 100644 index 0000000..0268203 --- /dev/null +++ b/docs/reference/index.html @@ -0,0 +1,349 @@ + + + + + + + + +Function reference • foot + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +

    Workflow wrapper functions

    +

    +
    +

    calculate_footstats()

    +

    calculate_footstats: Feature statistics of building footprints

    +

    calculate_bigfoot()

    +

    calculate_bigfoot: Gridded feature statistics for large sets of + building footprint polygons

    +

    Built-in morphology metrics

    +

    +
    +

    fs_area()

    +

    Area

    +

    fs_count()

    +

    Count of footprint locations per zone

    +

    fs_mbc()

    +

    Minimum bounding circle

    +

    fs_mbr()

    +

    Rotated minimum bounding rectangle

    +

    fs_nndist()

    +

    Nearest neighbour distance calculation

    +

    fs_perimeter()

    +

    Perimeter

    +

    fs_compact() fs_shape()

    +

    Compactness index

    +

    Built-in summary functions

    +

    +
    +

    fs_angle_entropy()

    +

    Building angle calculation

    +

    binary() count() cv() entropy() majority()

    +

    Built-in 'foot' functions

    +

    fs_nnindex()

    +

    Building Nearest Neighbour Index (NNI)

    +

    Utility functions

    +

    +
    +

    list_fs()

    +

    List footprint summary metrics

    +

    fs_varlist() is.fs_varlist()

    +

    Select variables

    +

    gridTiles()

    +

    gridTiles

    +

    zonalIndex()

    +

    Index buildings footprints to 'zones'

    +

    gen_nnindex()

    +

    Generate a nearest neighbour index function

    +

    Package files

    +

    +
    +

    foot

    +

    foot: An R package for calculating building footprint shape metrics

    +

    kampala

    +

    Kampala building footprint sample

    +
    + + +
    + + +
    + + +
    +

    Site built with pkgdown 1.6.1.

    +
    + +
    +
    + + + + + + + + diff --git a/docs/reference/kampala.html b/docs/reference/kampala.html new file mode 100644 index 0000000..9e248ba --- /dev/null +++ b/docs/reference/kampala.html @@ -0,0 +1,195 @@ + + + + + + + + +Kampala building footprint sample — kampala • foot + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    +
    + + +
    +

    Sample of building footprint polygons from a small region +of Kampala, Uganda with 4840 structures represented. In addition the +dataset contains a 3 arc-second grid to serve as a template for gridding +values and polygons of "administrative boundaries" and "survey clusters."

    +

    The building data were produced by Microsoft Bing Maps. The mastergrid is +aligned to gridded data produced by WorldPop. +The adminZones and cluster datasets are purely artificial and for demonstration +purposes only.

    +
    + +
    data(kampala)
    + + +

    Format

    + +

    A list with four objects.

      +
    • "buildings" - polygons of building footprints in sf format

    • +
    • "mastergrid" - geoTiff RasterLayer

    • +
    • "adminZones" - polygons in sf format for zonal statistics

    • +
    • "clusters" - 10 small polygons in sf format for sample sites

    • +
    + +

    Source

    + +

    Uganda-Tanzania Building Footprints

    + +
    + +
    + + +
    + + +
    +

    Site built with pkgdown 1.6.1.

    +
    + +
    +
    + + + + + + + + diff --git a/docs/reference/list_fs.html b/docs/reference/list_fs.html new file mode 100644 index 0000000..d759319 --- /dev/null +++ b/docs/reference/list_fs.html @@ -0,0 +1,259 @@ + + + + + + + + +List footprint summary metrics — list_fs • foot + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    +
    + + +
    +

    Helper function to list footprint characteristic names and + built-in summary functions.

    +
    + +
    list_fs(what = "all", how = "all")
    + +

    Arguments

    + + + + + + + + + + +
    what

    Character vector of names of characteristics to look up (e.g. +"area"). Alternatively, list 'all' or all characteristics except +nearest neighbour distances ('nodist').

    how

    Character vector of summary functions to look up (e.g. "mean") or +'all' available metrics.

    + +

    Value

    + +

    Vector or data.frame with selected footprint metric function + names and/or units.

    +

    Details

    + +

    Provides an easy look-up for the built-in function names, and + basic geometric characteristics used by foot. Supplying a "what" + characteristics finds all built-in functions. Conversely, supplying a "how" + function name returns are characteristics for which that summary is + available. Arguments are optional + and if both are omitted the full set of metrics will be returned.

    + +

    Examples

    +
    # get the full list of all available +list_fs() +
    #> cols funs +#> 1 angle cv +#> 2 angle entropy +#> 3 angle max +#> 4 angle mean +#> 5 angle median +#> 6 angle min +#> 7 angle sd +#> 8 area cv +#> 9 area max +#> 10 area mean +#> 11 area median +#> 12 area min +#> 13 area sd +#> 14 area sum +#> 15 compact cv +#> 16 compact max +#> 17 compact mean +#> 18 compact median +#> 19 compact min +#> 20 compact sd +#> 21 nndist cv +#> 22 nndist max +#> 23 nndist mean +#> 24 nndist median +#> 25 nndist min +#> 26 nndist nnindex +#> 27 nndist sd +#> 28 perimeter cv +#> 29 perimeter max +#> 30 perimeter mean +#> 31 perimeter median +#> 32 perimeter min +#> 33 perimeter sd +#> 34 perimeter sum +#> 35 settled binary +#> 36 settled count +#> 37 shape cv +#> 38 shape max +#> 39 shape mean +#> 40 shape median +#> 41 shape min +#> 42 shape sd
    +# get all summary functions for "area" +list_fs(what="area") +
    #> cols funs +#> 1 area cv +#> 2 area max +#> 3 area mean +#> 4 area median +#> 5 area min +#> 6 area sd +#> 7 area sum
    +# get all characteristics relevant for entropy +list_fs(how="entropy") +
    #> cols funs +#> 1 angle entropy
    +
    +
    + +
    + + +
    + + +
    +

    Site built with pkgdown 1.6.1.

    +
    + +
    +
    + + + + + + + + diff --git a/docs/reference/zonalIndex.html b/docs/reference/zonalIndex.html new file mode 100644 index 0000000..23c5621 --- /dev/null +++ b/docs/reference/zonalIndex.html @@ -0,0 +1,299 @@ + + + + + + + + +Index buildings footprints to 'zones' — zonalIndex • foot + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    +
    + + +
    +

    Find the area, grid cells, or other zone that a building polygon + overlaps or is located in. Implements an efficient spatial join by + intersection.

    +
    + +
    zonalIndex(X, zone, zoneField = NULL, method = "centroid", returnObject = TRUE)
    +
    +# S3 method for sf
    +zonalIndex(X, zone, zoneField = NULL, method = "centroid", returnObject = TRUE)
    +
    +# S3 method for sfc
    +zonalIndex(X, zone, zoneField = NULL, method = "centroid", returnObject = TRUE)
    +
    +# S3 method for sp
    +zonalIndex(X, zone, zoneField = NULL, method = "centroid", returnObject = TRUE)
    +
    +# S3 method for stars
    +zonalIndex(X, zone, zoneField = NULL, method = "centroid", returnObject = TRUE)
    +
    +# S3 method for raster
    +zonalIndex(X, zone, zoneField = NULL, method = "centroid", returnObject = TRUE)
    +
    +# S3 method for character
    +zonalIndex(X, zone, zoneField = NULL, method = "centroid", returnObject = TRUE)
    + +

    Arguments

    + + + + + + + + + + + + + + + + + + + + + + +
    X

    Spatial data (or path to file) with building footprint polygons

    zone

    Spatial data (or path to file) with polygon zones or a spatial +grid ("raster")

    zoneField

    (Optional) Column name of unique identifiers in zone +to use. If omitted, the 'zoneID' will be numbered 1:nrow(zone).

    method

    One of 'centroid', 'intersect', 'clip' to determine how +footprints are allocated to zones. See details. Default is +"centroid".

    returnObject

    Logical of whether to return an sf object of X with zonal +information. Default is TRUE which is generally preferred.

    + +

    Value

    + +

    'sf' object with attributes of X plus the unique zone ID or a + data.table with the row number to the record in X matched to + the zone IDs.

    +

    Details

    + +

    Zone assignments for building footprints can be done using three + possible methods, set by the method= parameter. Defining by + 'centroid' allocates and entire building and its characteristics to the + zone(s) which its centroid intersects. Centroids are defined by + st_centroid which could be outside the polygon shape. + This is the default mode. Defining zones by 'intersect' uses the geometric + binary predicate from st_intersects. This method will + include a whole building and its characteristics into all zones that it + intersects. Therefore a building could appear to be "counted" twice. The + final approach, 'clip', uses st_intersection to split + footprints so that only that area of the polygon intersecting the zone is + include. This method is more time consuming because the geometries are + modified.

    + +

    Examples

    +
    data("kampala", package="foot") + +buildings <- kampala$buildings +clusters <- kampala$clusters + +# assign zones and return a new 'sf' object +zonalIndex(buildings, clusters) +
    #> Simple feature collection with 348 features and 2 fields +#> geometry type: POLYGON +#> dimension: XY +#> bbox: xmin: 32.60631 ymin: 0.328119 xmax: 32.63543 ymax: 0.349785 +#> geographic CRS: WGS 84 +#> First 10 features: +#> FID_1 zoneID geometry +#> 1 6293 1 POLYGON ((32.60744 0.338575... +#> 2 17706 1 POLYGON ((32.60664 0.339347... +#> 3 29106 1 POLYGON ((32.60719 0.339276... +#> 4 53393 1 POLYGON ((32.60779 0.339691... +#> 5 57187 1 POLYGON ((32.60826 0.338663... +#> 6 59496 1 POLYGON ((32.60692 0.339387... +#> 7 68588 1 POLYGON ((32.60791 0.338896... +#> 8 68590 1 POLYGON ((32.60768 0.339633... +#> 9 72387 1 POLYGON ((32.60777 0.338962... +#> 10 78504 1 POLYGON ((32.60702 0.338636...
    +# assign all intersecting zones +zonalIndex(buildings, clusters, method="intersect") +
    #> Simple feature collection with 350 features and 2 fields +#> geometry type: POLYGON +#> dimension: XY +#> bbox: xmin: 32.60631 ymin: 0.328119 xmax: 32.63543 ymax: 0.349785 +#> geographic CRS: WGS 84 +#> First 10 features: +#> FID_1 zoneID geometry +#> 1 6293 1 POLYGON ((32.60744 0.338575... +#> 2 17706 1 POLYGON ((32.60664 0.339347... +#> 3 29106 1 POLYGON ((32.60719 0.339276... +#> 4 53393 1 POLYGON ((32.60779 0.339691... +#> 5 57187 1 POLYGON ((32.60826 0.338663... +#> 6 59496 1 POLYGON ((32.60692 0.339387... +#> 7 68588 1 POLYGON ((32.60791 0.338896... +#> 8 68590 1 POLYGON ((32.60768 0.339633... +#> 9 72387 1 POLYGON ((32.60777 0.338962... +#> 10 78504 1 POLYGON ((32.60702 0.338636...
    +# return only a table of indices - note column names +zonalIndex(buildings, clusters, zoneField="Id", returnObject=FALSE) +
    #> xID Id +#> 1: 97 1 +#> 2: 242 1 +#> 3: 390 1 +#> 4: 712 1 +#> 5: 762 1 +#> --- +#> 344: 3744 11 +#> 345: 4127 11 +#> 346: 4702 11 +#> 347: 4788 11 +#> 348: 4834 11
    +
    +
    + +
    + + +
    + + +
    +

    Site built with pkgdown 1.6.1.

    +
    + +
    +
    + + + + + + + + diff --git a/foot.Rproj b/foot.Rproj new file mode 100644 index 0000000..eaa6b81 --- /dev/null +++ b/foot.Rproj @@ -0,0 +1,18 @@ +Version: 1.0 + +RestoreWorkspace: Default +SaveWorkspace: Default +AlwaysSaveHistory: Default + +EnableCodeIndexing: Yes +UseSpacesForTab: Yes +NumSpacesForTab: 2 +Encoding: UTF-8 + +RnwWeave: Sweave +LaTeX: pdfLaTeX + +BuildType: Package +PackageUseDevtools: Yes +PackageInstallArgs: --no-multiarch --with-keep.source +PackageRoxygenize: rd,collate,namespace diff --git a/man/adjacentCells.Rd b/man/adjacentCells.Rd deleted file mode 100644 index 557c27d..0000000 --- a/man/adjacentCells.Rd +++ /dev/null @@ -1,38 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/get_adjacent.R -\name{adjacentCells} -\alias{adjacentCells} -\title{Raster adjacencies} -\usage{ -adjacentCells(r, cells, directions = 8, include = FALSE, dataTable = FALSE) -} -\arguments{ -\item{r}{A gridded, raster object on which to search.} - -\item{cells}{A vector of cell numbers in \code{r} to search for adjacencies.} - -\item{directions}{The dimension to define neighbouring cells by contiguity. -Accepted options include: 8, 4, "queen", "bishop", "rook" or a matrix. See -details. Default is 8 which is a queen's contiguity.} - -\item{include}{logical. Should the cell number of interest should be considered -adjacent to itself? Dfault is \code{FALSE}.} - -\item{dataTable}{logical. Should the processing and return value use \code{data.table}?} -} -\value{ -A table with cells numbers ("from") and their adjacent ("to"). -} -\description{ -Find adjacent grid cells in a regular raster dataset. -} -\details{ -The \code{directions} parameter defines neighbouring cells. This parameter -follows typical contiguity measures for lattice data (e.g. "queen" or "rook"), or -more complex adjacencies can be found by supplying a matrix. To use a matrix to define -adjacencies, the center value should be set to zero, all adjacent cells should be 1 and -any cells to ignore should be set to \code{NA}. -} -\seealso{ -\code{\link{make_circular_filter}} -} diff --git a/man/calculate_bigfoot.Rd b/man/calculate_bigfoot.Rd index 7675f60..c0654e8 100644 --- a/man/calculate_bigfoot.Rd +++ b/man/calculate_bigfoot.Rd @@ -10,74 +10,82 @@ \usage{ calculate_bigfoot( X, - metrics = "all", + what = "all", + how = "all", focalRadius = 0, - minArea = NULL, - maxArea = NULL, - controlUnits = NULL, - clip = FALSE, + controlZone = list(zoneName = "zoneID", method = "centroid"), + controlUnits = list(areaUnit = "m^2", perimUnit = "m", distUnit = "m"), + controlDist = list(maxSearch = 100, method = "centroid", unit = + controlUnits$distUnit), + filter = list(minArea = NULL, maxArea = NULL), template = NULL, tileSize = c(500, 500), parallel = TRUE, nCores = max(1, parallel::detectCores() - 1), - outputPath = tempdir(), + outputPath = getwd(), outputTag = NULL, tries = 100, - verbose = FALSE + verbose = TRUE ) \method{calculate_bigfoot}{sf}( X, - metrics = "all", + what = "all", + how = "all", focalRadius = 0, - minArea = NULL, - maxArea = NULL, - controlUnits = NULL, - clip = FALSE, + controlZone = list(zoneName = "zoneID", method = "centroid"), + controlUnits = list(areaUnit = "m^2", perimUnit = "m", distUnit = "m"), + controlDist = list(maxSearch = 100, method = "centroid", unit = + controlUnits$distUnit), + filter = list(minArea = NULL, maxArea = NULL), template = NULL, tileSize = c(500, 500), parallel = TRUE, nCores = max(1, parallel::detectCores() - 1), - outputPath = tempdir(), + outputPath = getwd(), outputTag = NULL, tries = 100, - verbose = FALSE + verbose = TRUE ) \method{calculate_bigfoot}{sp}( X, - metrics = "all", + what = "all", + how = "all", focalRadius = 0, - minArea = NULL, - maxArea = NULL, - controlUnits = NULL, - clip = FALSE, + controlZone = list(zoneName = "zoneID", method = "centroid"), + controlUnits = list(areaUnit = "m^2", perimUnit = "m", distUnit = "m"), + controlDist = list(maxSearch = 100, method = "centroid", unit = + controlUnits$distUnit), + filter = list(minArea = NULL, maxArea = NULL), template = NULL, tileSize = c(500, 500), parallel = TRUE, nCores = max(1, parallel::detectCores() - 1), - outputPath = tempdir(), + outputPath = getwd(), outputTag = NULL, tries = 100, - verbose = FALSE + verbose = TRUE ) \method{calculate_bigfoot}{character}( X, - metrics = "all", + what = "all", + how = "all", focalRadius = 0, - minArea = NULL, - maxArea = NULL, - controlUnits = NULL, - clip = FALSE, + controlZone = list(zoneName = "zoneID", method = "centroid"), + controlUnits = list(areaUnit = "m^2", perimUnit = "m", distUnit = "m"), + controlDist = list(maxSearch = 100, method = "centroid", unit = + controlUnits$distUnit), + filter = list(minArea = NULL, maxArea = NULL), template = NULL, tileSize = c(500, 500), parallel = TRUE, nCores = max(1, parallel::detectCores() - 1), - outputPath = tempdir(), + outputPath = getwd(), outputTag = NULL, tries = 100, - verbose = FALSE + verbose = TRUE ) } \arguments{ @@ -86,36 +94,47 @@ multiple spatial types, including \code{sf} and \code{sp}, or a filepath string to a file, or a list where each member provides a spatial object or a filepath string.} -\item{metrics}{character vector. Names of footprint statistics in the form of -"fs_area_mean", etc. Other options include \code{ALL} or \code{NODIST} to -calculate all available metrics and all except nearest neighbour distances, -respectively.} +\item{what}{list of strings naming the columns or built-in geometry measures to +calculate for each footprint. Other options include \code{'all'} or +\code{'nodist'} to calculate all available characteristics and all except +nearest-neighbour distance metrics.} + +\item{how}{list of strings naming functions to be used to calculate summary +statistics. The functions can be built-in functions (e.g. "mean","sd"), or +user-defined function names.} \item{focalRadius}{numeric. Distance in meters for a buffer around each template pixel. Creates a focal processing window for metrics.} -\item{minArea}{numeric. Minimum footprint area to filter \code{X}.} - -\item{maxArea}{numeric. Maximum footprint area to filter \code{X}.} +\item{controlZone}{(optional) named list. Setting controls passed on to +\code{\link[foot]{zonalIndex}}. Elements can include \code{zoneName} and +\code{method}.} \item{controlUnits}{(optional) named list. Elements can include \code{areaUnit}, \code{perimUnit}, and \code{distUnit}. The values for these items should be strings that can be coerced into a \code{units} object.} -\item{clip}{(optional). Logical. Should polygons which span pixel zones be -clipped? Default is \code{FALSE}.} +\item{controlDist}{(optional) named list to override default +options for distance calculations. Elements can include \code{maxSearch} +and \code{method}. Ignored if \code{metrics} does not include a distance +calculation. See \code{\link[foot]{fs_nndist}}.} + +\item{filter}{(optional) named list with \code{minArea} and \code{maxArea}. +These are numeric values to filter footprints prior to processing. Default +values are \code{NULL} and do not filter any records.} \item{template}{(optional). When creating a gridded output, a supplied \code{stars} or \code{raster} dataset to align the data.} \item{tileSize}{number of pixels per side of a tile. Can be a vector of -length 2 (rows, column pixels). Ignored if n provided. Default is 1000.} +length 2 (rows, column pixels). Default is \code{c(500, 500)}.} \item{parallel}{logical. Should a parallel backend be used to process the -tiles.} +tiles. Default is \code{TRUE}.} -\item{nCores}{number of CPU cores to use if \code{parallel} is \code{TRUE}.} +\item{nCores}{number of CPU cores to use if \code{parallel} is \code{TRUE}. +Default is 1 less than the available CPUs.} \item{outputPath}{(optional). When creating a gridded output, a path for the location of the output. Default is the temp directory.} @@ -127,7 +146,7 @@ beginning of the output name for the gridded files.} file. Default is 100.} \item{verbose}{logical. Should progress messages be printed. Default -\code{False}.} +\code{TRUE}.} } \value{ Invisible. Returns a vector of paths to the output files. @@ -152,14 +171,13 @@ calculate_bigfoot for the calculations. } \examples{ -data(kampala) +data("kampala", package="foot") buildings <- kampala$buildings templateGrid <- kampala$mastergrid calculate_bigfoot(X=buildings, - metrics=c("shape_mean", - "count", - "perim_total"), + what=list(list("shape"), list("perimeter")), + how=list(list("mean"), list("sum")), controlUnits=list(areaUnit="m^2"), minArea=50, # footprints must be larger than 50 m^2 maxArea=1000, # footprints must be smaller than 1000 m^2 @@ -174,3 +192,6 @@ outGrid <- raster::raster(file.path(tempdir(), "kampala_count.tif")) raster::plot(outGrid) } +\seealso{ +\link[foot]{calculate_footstats} +} diff --git a/man/calculate_footstats.Rd b/man/calculate_footstats.Rd index a4b13c3..9e98af5 100644 --- a/man/calculate_footstats.Rd +++ b/man/calculate_footstats.Rd @@ -6,169 +6,160 @@ \alias{calculate_footstats.sfc} \alias{calculate_footstats.sp} \alias{calculate_footstats.character} -\alias{calculate_footstats.list} \title{calculate_footstats: Feature statistics of building footprints} \usage{ calculate_footstats( X, - index = NULL, - metrics = "all", - minArea = NULL, - maxArea = NULL, - controlUnits = NULL, - clip = FALSE, - gridded = FALSE, - template = NULL, - outputPath = NULL, - outputTag = NULL, - driver = "GTiff", - verbose = FALSE + zone = NULL, + what = "all", + how = NULL, + controlZone = list(zoneName = "zoneID", method = "centroid"), + controlUnits = list(areaUnit = "m^2", perimUnit = "m", distUnit = "m"), + controlDist = list(maxSearch = 100, method = "centroid", unit = + controlUnits$distUnit), + filter = list(minArea = NULL, maxArea = NULL), + verbose = TRUE ) \method{calculate_footstats}{sf}( X, - index = NULL, - metrics = "all", - minArea = NULL, - maxArea = NULL, - controlUnits = NULL, - clip = FALSE, - gridded = FALSE, - template = NULL, - outputPath = NULL, - outputTag = NULL, - driver = "GTiff", - verbose = FALSE + zone = NULL, + what = "all", + how = NULL, + controlZone = list(zoneName = "zoneID", method = "centroid"), + controlUnits = list(areaUnit = "m^2", perimUnit = "m", distUnit = "m"), + controlDist = list(maxSearch = 100, method = "centroid", unit = + controlUnits$distUnit), + filter = list(minArea = NULL, maxArea = NULL), + verbose = TRUE ) \method{calculate_footstats}{sfc}( X, - index = NULL, - metrics = "all", - minArea = NULL, - maxArea = NULL, - controlUnits = NULL, - clip = FALSE, - gridded = FALSE, - template = NULL, - outputPath = NULL, - outputTag = NULL, - driver = "GTiff", - verbose = FALSE + zone = NULL, + what = "all", + how = NULL, + controlZone = list(zoneName = "zoneID", method = "centroid"), + controlUnits = list(areaUnit = "m^2", perimUnit = "m", distUnit = "m"), + controlDist = list(maxSearch = 100, method = "centroid", unit = + controlUnits$distUnit), + filter = list(minArea = NULL, maxArea = NULL), + verbose = TRUE ) \method{calculate_footstats}{sp}( X, - index = NULL, - metrics = "all", - minArea = NULL, - maxArea = NULL, - controlUnits = NULL, - clip = FALSE, - gridded = FALSE, - template = NULL, - outputPath = NULL, - outputTag = NULL, - driver = "GTiff", - verbose = FALSE + zone = NULL, + what = "all", + how = NULL, + controlZone = list(zoneName = "zoneID", method = "centroid"), + controlUnits = list(areaUnit = "m^2", perimUnit = "m", distUnit = "m"), + controlDist = list(maxSearch = 100, method = "centroid", unit = + controlUnits$distUnit), + filter = list(minArea = NULL, maxArea = NULL), + verbose = TRUE ) \method{calculate_footstats}{character}( X, - index = NULL, - metrics = "all", - minArea = NULL, - maxArea = NULL, - controlUnits = NULL, - clip = FALSE, - gridded = FALSE, - template = NULL, - outputPath = NULL, - outputTag = NULL, - driver = "GTiff", - verbose = FALSE -) - -\method{calculate_footstats}{list}( - X, - index = NULL, - metrics = "all", - minArea = NULL, - maxArea = NULL, - controlUnits = NULL, - clip = FALSE, - gridded = FALSE, - template = NULL, - outputPath = NULL, - outputTag = NULL, - driver = "GTiff", - verbose = FALSE + zone = NULL, + what = "all", + how = NULL, + controlZone = list(zoneName = "zoneID", method = "centroid"), + controlUnits = list(areaUnit = "m^2", perimUnit = "m", distUnit = "m"), + controlDist = list(maxSearch = 100, method = "centroid", unit = + controlUnits$distUnit), + filter = list(minArea = NULL, maxArea = NULL), + verbose = TRUE ) } \arguments{ \item{X}{object with building footprint polygons. This argument can take multiple spatial types, including \code{sf} and \code{sp}, or a filepath -string to a file, or a list where each member provides a spatial object or -a filepath string.} +string to a file.} -\item{index}{A spatial polygon file defining areas. Or a character or numeric -value identifying a column within \code{X} which provides a zonal index for -summarising values. Alternatively a vector of indices can be provided. If -omitted all observations with -\code{X} are assumed to be within one zone.} +\item{zone}{A spatial polygon file defining areas. Or a string identifying a +column within \code{X} which provides a zonal index for summarising values. +Alternatively a vector of indices (with \code{length(index)==nrow(X)}) can +be provided. If omitted, all observations in \code{X} are assumed to be +within one zone.} -\item{metrics}{character vector. Names of footprint statistics in the form of -"fs_area_mean", etc. Other options include \code{ALL} or \code{NODIST} to -calculate all available metrics and all except nearest neighbour distances, -respectively.} +\item{what}{list of strings naming the columns or built-in geometry measures to +calculate for each footprint. Other options include \code{'all'} or +\code{'nodist'} to calculate all available characteristics and all except +nearest-neighbour distance metrics.} -\item{minArea}{numeric. Minimum footprint area to filter \code{X}.} +\item{how}{list of strings naming functions to be used to calculate summary +statistics. The functions can be built-in functions (e.g. "mean","sd"), or +user-defined function names.} -\item{maxArea}{numeric. Maximum footprint area to filter \code{X}.} +\item{controlZone}{(optional) named list. Setting controls passed on to +\code{\link[foot]{zonalIndex}}. Elements can include \code{zoneName} and +\code{method}.} \item{controlUnits}{(optional) named list. Elements can include \code{areaUnit}, \code{perimUnit}, and \code{distUnit}. The values for these items should be strings that can be coerced into a \code{units} object.} -\item{clip}{(optional). Logical. Should polygons which span pixel zones be -clipped? Default is \code{FALSE}.} - -\item{gridded}{Should a gridded output be created? Default \code{FALSE}.} - -\item{template}{(optional). When creating a gridded output, a supplied -\code{stars} or \code{raster} dataset to align the data.} - -\item{outputPath}{(optional). When creating a gridded output, a path for the -location of the output.} +\item{controlDist}{(optional) named list to override default +options for distance calculations. Elements can include \code{maxSearch} +and \code{method}. Ignored if \code{metrics} does not include a distance +calculation. See \code{\link[foot]{fs_nndist}}.} -\item{outputTag}{(optional). A character string that will be added tagged to -the beginning of the output gridded files. If \code{X} is a \code{list}, -then the names of the list items will be used or a vector of tags of the -same length as \code{X} should be supplied.} - -\item{driver}{character. Currently supports geotiff ("GTiff").} +\item{filter}{(optional) named list with \code{minArea} and \code{maxArea}. +These are numeric values to filter footprints prior to processing. Default +values are \code{NULL} and do not filter any records.} \item{verbose}{logical. Should progress messages be printed. -Default \code{False}.} +Default \code{TRUE}.} } \value{ -a \code{data.table} with an 'index' column and named columns for each - footprint statistic. Alternatively, geoTiffs of +a \code{data.table} with an index column identifying the zones and + named columns for each footprint summary statistic. } \description{ Calculate groups of metrics for building footprint datasets } \details{ calculate_footstats + +\code{calculate_footstats} is a wrapper function combining several + internal functions from \code{foot}. It can calculate various geometric + measures for each footprint polygon, including area, perimeter, + compactness, shape, angle of rotation, and nearest neighbour distance. To + find the list of built-in characteristics and summary metrics, use + \code{list_fs()}. + + The \code{what} and \code{how} arguments are lists specifying the + characteristics and the summary metrics to calculated, respectively. Each + "how" function will be applied to each "what" characteristic. To apply a + metric to only a subset of characteristics, nested lists, with groups of + characteristics and functions can be supplied. See examples. + + The \code{control} arguments are sets of lists with named arguments that + pass on these parameters to other \code{foot} functions. These are + optional. } \examples{ -data(kampala) +data("kampala", package="foot") buildings <- kampala$buildings adminzones <- kampala$adminZones -calculate_footstats(buildings, - index=adminzones, - metrics=c("fs_area_mean","fs_area_cv")) +# no summary statistics, just geometry calculations +calculate_footstats(buildings, adminzones, what=list("area","perimeter")) +# average building footprint area +calculate_footstats(buildings, + zone=adminzones, + what="area", how="mean") + +# calculate multiple metrics - nested lists to group arguments +calculate_footstats(buildings, adminzones, + what=list(list("area"), list("perimeter")), + how=list(list("mean","sum"), list("sd","cv"))) + +} +\seealso{ +\link[foot]{zonalIndex}, \link[foot]{fs_nndist}, \link[foot]{list_fs} } diff --git a/man/circularFilter.Rd b/man/circularFilter.Rd deleted file mode 100644 index 148fec5..0000000 --- a/man/circularFilter.Rd +++ /dev/null @@ -1,23 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/filter.R -\name{circularFilter} -\alias{circularFilter} -\alias{make_circular_filter} -\title{Circular Filter} -\usage{ -make_circular_filter(size = 5) -} -\arguments{ -\item{size}{The length of the side of a square matrix. The circular window has a radius of size/2.} -} -\value{ -Matrix with values inside the circle set to 1, the center set to 0, and corners as NA. -make weights window -based on: https://stackoverflow.com/questions/54742340/r-extract-a-circle-from-a-matrix -} -\description{ -Make an approximately circular filter inside of an 'n x n' matrix. -} -\details{ -Filters for gridded data -} diff --git a/man/figures/REAsDME-Fig1-1.png b/man/figures/REAsDME-Fig1-1.png new file mode 100644 index 0000000..a996a03 Binary files /dev/null and b/man/figures/REAsDME-Fig1-1.png differ diff --git a/man/figures/REAsDME-Fig2-1.png b/man/figures/REAsDME-Fig2-1.png new file mode 100644 index 0000000..85283a3 Binary files /dev/null and b/man/figures/REAsDME-Fig2-1.png differ diff --git a/man/figures/REAsDME-Fig3-1.png b/man/figures/REAsDME-Fig3-1.png new file mode 100644 index 0000000..61e8eac Binary files /dev/null and b/man/figures/REAsDME-Fig3-1.png differ diff --git a/man/figures/REAsDME-Fig4-1.png b/man/figures/REAsDME-Fig4-1.png new file mode 100644 index 0000000..c7a1c0e Binary files /dev/null and b/man/figures/REAsDME-Fig4-1.png differ diff --git a/man/figures/REAsDME-Fig5-1.png b/man/figures/REAsDME-Fig5-1.png new file mode 100644 index 0000000..7ca4834 Binary files /dev/null and b/man/figures/REAsDME-Fig5-1.png differ diff --git a/man/figures/REAsDME-Fig6-1.png b/man/figures/REAsDME-Fig6-1.png new file mode 100644 index 0000000..4cf9a55 Binary files /dev/null and b/man/figures/REAsDME-Fig6-1.png differ diff --git a/man/figures/REAsDME-unnamed-chunk-11-1.png b/man/figures/REAsDME-unnamed-chunk-11-1.png new file mode 100644 index 0000000..b72a6a7 Binary files /dev/null and b/man/figures/REAsDME-unnamed-chunk-11-1.png differ diff --git a/man/figures/REAsDME-unnamed-chunk-12-1.png b/man/figures/REAsDME-unnamed-chunk-12-1.png new file mode 100644 index 0000000..c7b9a5c Binary files /dev/null and b/man/figures/REAsDME-unnamed-chunk-12-1.png differ diff --git a/man/figures/REAsDME-unnamed-chunk-13-1.png b/man/figures/REAsDME-unnamed-chunk-13-1.png new file mode 100644 index 0000000..7e360c6 Binary files /dev/null and b/man/figures/REAsDME-unnamed-chunk-13-1.png differ diff --git a/man/figures/REAsDME-unnamed-chunk-14-1.png b/man/figures/REAsDME-unnamed-chunk-14-1.png new file mode 100644 index 0000000..f644b4c Binary files /dev/null and b/man/figures/REAsDME-unnamed-chunk-14-1.png differ diff --git a/man/figures/REAsDME-unnamed-chunk-15-1.png b/man/figures/REAsDME-unnamed-chunk-15-1.png new file mode 100644 index 0000000..cf0267c Binary files /dev/null and b/man/figures/REAsDME-unnamed-chunk-15-1.png differ diff --git a/man/figures/REAsDME-unnamed-chunk-3-1.png b/man/figures/REAsDME-unnamed-chunk-3-1.png new file mode 100644 index 0000000..9edc044 Binary files /dev/null and b/man/figures/REAsDME-unnamed-chunk-3-1.png differ diff --git a/man/figures/REAsDME-unnamed-chunk-4-1.png b/man/figures/REAsDME-unnamed-chunk-4-1.png index b33ddab..f5b93d5 100644 Binary files a/man/figures/REAsDME-unnamed-chunk-4-1.png and b/man/figures/REAsDME-unnamed-chunk-4-1.png differ diff --git a/man/figures/REAsDME-unnamed-chunk-8-1.png b/man/figures/REAsDME-unnamed-chunk-8-1.png new file mode 100644 index 0000000..d605c31 Binary files /dev/null and b/man/figures/REAsDME-unnamed-chunk-8-1.png differ diff --git a/man/foot.Rd b/man/foot.Rd index 25cb39e..2f69a87 100644 --- a/man/foot.Rd +++ b/man/foot.Rd @@ -3,32 +3,38 @@ \docType{package} \name{foot} \alias{foot} -\title{foot: A package for calculating building footprint shape metrics} +\title{foot: An R package for calculating building footprint shape metrics} \description{ -The foot package provides functions to calculate summary statistics -of geometric measurements of building footprint polygons. Footprints -shapes representing buildings are becoming more widely available by -being detected and extracted from very high resolution satellite. Such -datasets are spatially detailed but often are unlabelled. However, -the size, shape, and distribution of buildings can suggest possible -land uses or differences in structure use, socieconomic status, etc. +The \code{foot} package provides functions to calculate summary statistics of +geometric measurements of building footprint polygons. Footprint shapes +representing buildings are becoming more widely available by being detected +and extracted from very high resolution satellite imagery. Such datasets are +spatially detailed but often are unlabelled. However, the size, shape, and +distribution of buildings can suggest possible land uses or differences in +structure use, socio-economic status, etc. } \details{ -The measurements in \code{foot} include: area, perimeter, nearest-neighbour -distance, angle of rotation for a bounding rectange, as well as a binary +The \code{foot} package is designed to provide a set of consistent and +flexible tools for processing 2D vector representations of buildings. The +functionality includes basic geometry and morphology measures, distance and +clustering metrics. These calculations are supported with helper functions +for spatial intersections and tiled reading/writing of data. + +The measurements in \code{foot} include: area, perimeter, nearest-neighbour +distance, angle of rotation for a bounding rectangle, as well as a binary indicator of structure presence and a count of structures. -These measures can be summarised as a mean, standard deivation, coefficient -of variation, the nearest neighbour index of clustering, or a (normalised) -entropy measure for the angles, and can be output as a table or as a -gridded dataset. +These measures can be summarised as a mean, standard deviation, coefficient +of variation, the nearest neighbour index of clustering, or a (normalised) +entropy measure for the angles. The output can be formatted as a data table +or as a gridded dataset. } \section{Helper functions}{ - -While each measurement function can be accessed as a standalone function, -the \code{foot} package provides convenience functions to wrap common -analysis steps together in a helper function, taking a list of measures -and parameters and returning a collected output. + The \code{foot} package provides convenience + functions (\code{\link[foot]{calculate_footstats}} and + \code{\link[foot]{calculate_bigfoot}}) to wrap common analysis steps + together, taking a list of measurements and parameters and returning a + collected output. In addition to bulk processing helper functions, there are additional utility functions supplied with the package to support identifying @@ -38,6 +44,7 @@ spatial data and providing efficient I/O and parallel processing. \section{Credits}{ -This work was undertaken by members of WorldPop at the University of Southampton. +This work was undertaken by members of the WorldPop Research Group at the +University of Southampton(\url{https://www.worldpop.org/}). } diff --git a/man/fs_angle_entropy.Rd b/man/fs_angle_entropy.Rd index ae67c0d..9c5747e 100644 --- a/man/fs_angle_entropy.Rd +++ b/man/fs_angle_entropy.Rd @@ -4,6 +4,7 @@ \alias{fs_angle_entropy} \alias{fs_angle_entropy.sp} \alias{fs_angle_entropy.sf} +\alias{fs_angle_entropy.sfc} \title{Building angle calculation} \usage{ fs_angle_entropy(X, index = NULL, col = NULL, normalize = TRUE) @@ -11,19 +12,24 @@ fs_angle_entropy(X, index = NULL, col = NULL, normalize = TRUE) \method{fs_angle_entropy}{sp}(X, index = NULL, col = NULL, normalize = TRUE) \method{fs_angle_entropy}{sf}(X, index = NULL, col = NULL, normalize = TRUE) + +\method{fs_angle_entropy}{sfc}(X, index = NULL, col = NULL, normalize = TRUE) } \arguments{ \item{X}{Spatial object with building footprint polygons} -\item{index}{A character or numeric value identifying a column within \code{X} -which provides a zonal index for summarising values. Alternatively a vector of -indices can be provided. If omitted all observations in \code{X} are assumed -to be within one zone.} +\item{index}{A string identifying a column within \code{X} which provides a +zonal index for summarising values. Alternatively a vector of indices can +be provided. If omitted, all observations in \code{X} are assumed to be +within one zone.} \item{col}{column name within \code{X} with pre-calculated area measures} \item{normalize}{A logical value indicating whether to normalize the entropy. Default is \code{TRUE}.} + +\item{unit}{character or \code{units} object to define area. Default is +\code{NULL} which will use the units of the spatial reference system} } \value{ \code{data.table} of zonal indices and values. @@ -33,9 +39,32 @@ Calculate the entropy of rotation angles for building footprint polygons within zones. } \details{ -This measure uses the angle of the minimum rotated rectangle enclosing -each footprint poygon. Entropy is an information criteria measure. When summarising -the angles of footprints, higher entropy values may suggest less formally planned -or zoned areas. The entropy calculation uses the common Shannon's Entropy. The -normalization step produces an indicator for how much a zone departs from a grid. +This measure uses the angle of the minimum rotated rectangle + enclosing each footprint polygon. Entropy is an information criteria + measure. When summarising the angles of footprints, higher entropy values + may suggest less formally planned or zoned areas. The entropy calculation + uses the common Shannon's Entropy. The normalization step produces an + indicator for how much a zone departs from a grid. This metric is based on + work by Boeing (2019). + + Note that this function is provided as a standalone calculation for + convenience. The same summary measure can be executed within + \code{calculate_footstats} by specifying \code{what='angle'} and + \code{how='entropy'}. +} +\examples{ +data("kampala", package="foot") +b <- kampala$buildings + +# assign random groups +idx <- sample(1:10, nrow(b), replace=T) + +angles <- fs_angle_entropy(b, index=idx, normalize=FALSE) +angle_norm <- fs_angle_entropy(b, index=idx, normalize=TRUE) + +} +\references{ +Boeing, Geoff (2019). "Urban spatial order: Street network + orientation, configuration, and entropy." Applied Network Science, 4(67), + \url{https://doi.org/10.1007/s41109-019-0189-1}. } diff --git a/man/fs_area_cv.Rd b/man/fs_area_cv.Rd deleted file mode 100644 index 18b80d8..0000000 --- a/man/fs_area_cv.Rd +++ /dev/null @@ -1,34 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/fs_area_cv.R -\name{fs_area_cv} -\alias{fs_area_cv} -\alias{fs_area_cv.sp} -\alias{fs_area_cv.sf} -\title{Building area coefficient of variation (CV)} -\usage{ -fs_area_cv(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_area_cv}{sp}(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_area_cv}{sf}(X, index = NULL, unit = NULL, col = NULL) -} -\arguments{ -\item{X}{Spatial object with building footprint polygons} - -\item{index}{A character or numeric value identifying a column within \code{X} -which provides a zonal index for summarising values. Alternatively a vector of -indices can be provided. If omitted all observations in \code{X} are assumed -to be within one zone.} - -\item{unit}{character or \code{units} object to define area. -Default is \code{NULL} which will use the units of the spatial reference system} - -\item{col}{column name within \code{X} with pre-calculated area measures} -} -\value{ -\code{data.table} of zonal indices and values -} -\description{ -Calculate and summarise selected metrics of building -footprint polygons within zones. -} diff --git a/man/fs_area_max.Rd b/man/fs_area_max.Rd deleted file mode 100644 index 4fbe491..0000000 --- a/man/fs_area_max.Rd +++ /dev/null @@ -1,22 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/fs_area_max.R -\name{fs_area_max} -\alias{fs_area_max} -\alias{fs_area_max.sp} -\alias{fs_area_max.sf} -\title{Building area maximum calculation} -\usage{ -fs_area_max(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_area_max}{sp}(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_area_max}{sf}(X, index = NULL, unit = NULL, col = NULL) -} -\value{ -\code{data.table} of zonal indices and values -} -\description{ -Calculate and summarise selected metrics of building -footprint polygons within zones. -#' @inheritParams fs_area_mean -} diff --git a/man/fs_area_mean.Rd b/man/fs_area_mean.Rd deleted file mode 100644 index d3346ab..0000000 --- a/man/fs_area_mean.Rd +++ /dev/null @@ -1,34 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/fs_area_mean.R -\name{fs_area_mean} -\alias{fs_area_mean} -\alias{fs_area_mean.sp} -\alias{fs_area_mean.sf} -\title{Building area mean calculation} -\usage{ -fs_area_mean(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_area_mean}{sp}(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_area_mean}{sf}(X, index = NULL, unit = NULL, col = NULL) -} -\arguments{ -\item{X}{Spatial object with building footprint polygons} - -\item{index}{A character or numeric value identifying a column within \code{X} -which provides a zonal index for summarising values. Alternatively a vector of -indices can be provided. If omitted all observations in \code{X} are assumed -to be within one zone.} - -\item{unit}{character or \code{units} object to define area. -Default is \code{NULL} which will use the units of the spatial reference system} - -\item{col}{column name within \code{X} with pre-calculated area measures} -} -\value{ -\code{data.table} of zonal indices and values -} -\description{ -Calculate and summarise selected metrics of building -footprint polygons within zones. -} diff --git a/man/fs_area_median.Rd b/man/fs_area_median.Rd deleted file mode 100644 index bba61a6..0000000 --- a/man/fs_area_median.Rd +++ /dev/null @@ -1,22 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/fs_area_median.R -\name{fs_area_median} -\alias{fs_area_median} -\alias{fs_area_median.sp} -\alias{fs_area_median.sf} -\title{Building perimeter mean calculation} -\usage{ -fs_area_median(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_area_median}{sp}(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_area_median}{sf}(X, index = NULL, unit = NULL, col = NULL) -} -\value{ -\code{data.table} of zonal indices and values -} -\description{ -Calculate and summarise selected metrics of building -footprint polygons within zones. -#' @inheritParams fs_area_mean -} diff --git a/man/fs_area_min.Rd b/man/fs_area_min.Rd deleted file mode 100644 index b1efd6a..0000000 --- a/man/fs_area_min.Rd +++ /dev/null @@ -1,22 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/fs_area_min.R -\name{fs_area_min} -\alias{fs_area_min} -\alias{fs_area_min.sp} -\alias{fs_area_min.sf} -\title{Building area minimum calculation} -\usage{ -fs_area_min(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_area_min}{sp}(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_area_min}{sf}(X, index = NULL, unit = NULL, col = NULL) -} -\value{ -\code{data.table} of zonal indices and values -} -\description{ -Calculate and summarise selected metrics of building -footprint polygons within zones. -#' @inheritParams fs_area_mean -} diff --git a/man/fs_area_sd.Rd b/man/fs_area_sd.Rd deleted file mode 100644 index d4c96ba..0000000 --- a/man/fs_area_sd.Rd +++ /dev/null @@ -1,34 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/fs_area_sd.R -\name{fs_area_sd} -\alias{fs_area_sd} -\alias{fs_area_sd.sp} -\alias{fs_area_sd.sf} -\title{Building area standard deviation calculation} -\usage{ -fs_area_sd(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_area_sd}{sp}(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_area_sd}{sf}(X, index = NULL, unit = NULL, col = NULL) -} -\arguments{ -\item{X}{Spatial object with building footprint polygons} - -\item{index}{A character or numeric value identifying a column within \code{X} -which provides a zonal index for summarising values. Alternatively a vector of -indices can be provided. If omitted all observations in \code{X} are assumed -to be within one zone.} - -\item{unit}{character or \code{units} object to define area. -Default is \code{NULL} which will use the units of the spatial reference system} - -\item{col}{column name within \code{X} with pre-calculated area measures} -} -\value{ -\code{data.table} of zonal indices and values -} -\description{ -Calculate and summarise selected metrics of building -footprint polygons within zones. -} diff --git a/man/fs_area_total.Rd b/man/fs_area_total.Rd deleted file mode 100644 index c9b78b3..0000000 --- a/man/fs_area_total.Rd +++ /dev/null @@ -1,34 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/fs_area_total.R -\name{fs_area_total} -\alias{fs_area_total} -\alias{fs_area_total.sp} -\alias{fs_area_total.sf} -\title{Building area total calculation} -\usage{ -fs_area_total(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_area_total}{sp}(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_area_total}{sf}(X, index = NULL, unit = NULL, col = NULL) -} -\arguments{ -\item{X}{Spatial object with building footprint polygons} - -\item{index}{A character or numeric value identifying a column within \code{X} -which provides a zonal index for summarising values. Alternatively a vector of -indices can be provided. If omitted all observations in \code{X} are assumed -to be within one zone.} - -\item{unit}{character or \code{units} object to define area. -Default is \code{NULL} which will use the units of the spatial reference system} - -\item{col}{column name within \code{X} with pre-calculated area measures} -} -\value{ -\code{data.table} of zonal indices and values -} -\description{ -Calculate and summarise selected metrics of building -footprint polygons within zones. -} diff --git a/man/fs_compact_mean.Rd b/man/fs_compact_mean.Rd deleted file mode 100644 index f488ec6..0000000 --- a/man/fs_compact_mean.Rd +++ /dev/null @@ -1,47 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/fs_compact_mean.R -\name{fs_compact_mean} -\alias{fs_compact_mean} -\alias{fs_compact_mean.sp} -\alias{fs_compact_mean.sf} -\title{Building compactness measures} -\source{ -Polsby, Daniel D., and Robert D. Popper. 1991. “The Third Criterion: - Compactness as a procedural safeguard against partisan gerrymandering.” - Yale Law & Policy Review 9 (2): 301–353. -} -\usage{ -fs_compact_mean(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_compact_mean}{sp}(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_compact_mean}{sf}(X, index = NULL, unit = NULL, col = NULL) -} -\arguments{ -\item{X}{Spatial object with building footprint polygons} - -\item{index}{A character or numeric value identifying a column within \code{X} -which provides a zonal index for summarising values. Alternatively a vector of -indices can be provided. If omitted all observations in \code{X} are assumed -to be within one zone.} - -\item{unit}{character or \code{units} object to define area. -Default is \code{NULL} which will use the units of the spatial reference system} - -\item{col}{column name within \code{X} with pre-calculated area measures} -} -\value{ -\code{data.table} of zonal indices and values -} -\description{ -Calculate and summarise selected metrics of building footprint - polygons within zones. -} -\details{ -The compactness measure is the Polsby-Popper test. This is - summarised as the mean for all footprints in the zone identified by - \code{index}. - - \deqn{ PP_z = \frac{4$\pi$ * A_z}{P_z^2} }, - where P and A are the perimeter and area of zone 'z', respectively. -} diff --git a/man/fs_compact_median.Rd b/man/fs_compact_median.Rd deleted file mode 100644 index 9ebfd6b..0000000 --- a/man/fs_compact_median.Rd +++ /dev/null @@ -1,47 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/fs_compact_median.R -\name{fs_compact_median} -\alias{fs_compact_median} -\alias{fs_compact_median.sp} -\alias{fs_compact_median.sf} -\title{Building compactness measures} -\source{ -Polsby, Daniel D., and Robert D. Popper. 1991. “The Third Criterion: - Compactness as a procedural safeguard against partisan gerrymandering.” - Yale Law & Policy Review 9 (2): 301–353. -} -\usage{ -fs_compact_median(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_compact_median}{sp}(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_compact_median}{sf}(X, index = NULL, unit = NULL, col = NULL) -} -\arguments{ -\item{X}{Spatial object with building footprint polygons} - -\item{index}{A character or numeric value identifying a column within \code{X} -which provides a zonal index for summarising values. Alternatively a vector of -indices can be provided. If omitted all observations in \code{X} are assumed -to be within one zone.} - -\item{unit}{character or \code{units} object to define area. -Default is \code{NULL} which will use the units of the spatial reference system} - -\item{col}{column name within \code{X} with pre-calculated area measures} -} -\value{ -\code{data.table} of zonal indices and values -} -\description{ -Calculate and summarise selected metrics of building footprint - polygons within zones. -} -\details{ -The compactness measure is the Polsby-Popper test. This is - summarised as the mean for all footprints in the zone identified by - \code{index}. - - \deqn{ PP_z = \frac{4$\pi$ * A_z}{P_z^2} }, - where P and A are the perimeter and area of zone 'z', respectively. -} diff --git a/man/fs_count.Rd b/man/fs_count.Rd index 95bc5d2..d4a6bae 100644 --- a/man/fs_count.Rd +++ b/man/fs_count.Rd @@ -15,12 +15,15 @@ fs_count(X, index = NULL, col = NULL) \arguments{ \item{X}{Spatial object with building footprint polygons} -\item{index}{A character or numeric value identifying a column within \code{X} -which provides a zonal index for summarising values. Alternatively a vector of -indices can be provided. If omitted all observations in \code{X} are assumed -to be within one zone.} +\item{index}{A string identifying a column within \code{X} which provides a +zonal index for summarising values. Alternatively a vector of indices can +be provided. If omitted, all observations in \code{X} are assumed to be +within one zone.} \item{col}{column name within \code{X} with pre-calculated area measures} + +\item{unit}{character or \code{units} object to define area. Default is +\code{NULL} which will use the units of the spatial reference system} } \value{ \code{data.table} of zonal indices and values. @@ -29,3 +32,9 @@ to be within one zone.} Calculate and summarise selected metrics of building footprints within zones. } +\details{ +Note that this function is provided as a standalone calculation for + convenience. The same summary measure can be executed within + \code{calculate_footstats} by specifying \code{what='settled'} and + \code{how='count'}. +} diff --git a/man/fs_functions.Rd b/man/fs_functions.Rd new file mode 100644 index 0000000..644ffaa --- /dev/null +++ b/man/fs_functions.Rd @@ -0,0 +1,39 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/fs_functions.R +\name{fs_functions} +\alias{fs_functions} +\alias{binary} +\alias{count} +\alias{cv} +\alias{entropy} +\alias{majority} +\title{Built-in 'foot' functions} +\usage{ +binary(x) + +count(x) + +cv(x) + +entropy(x) + +majority(x) +} +\arguments{ +\item{x}{numeric vector of values to summarize.} +} +\value{ +numeric value +} +\description{ +In addition to generic \code{R} functions (e.g. 'mean', 'max', + etc.), these functions are designed to provide simple access to common + calculations. +} +\details{ +These functions are designed to be used within + \code{\link[foot]{calculate_footstats}} which processes a \code{data.table} + by group ID. Therefore all functions take a vector of values and return a + single summary statistic. These functions are not likely to be used on + their own. +} diff --git a/man/fs_mbr.Rd b/man/fs_mbr.Rd index 8dffb38..09aa5db 100644 --- a/man/fs_mbr.Rd +++ b/man/fs_mbr.Rd @@ -13,7 +13,7 @@ fs_mbr(X, returnShape = FALSE) \item{X}{polygons of building footprints in type \code{sf}.} \item{returnShape}{logical. Should the function return the \code{sf} polygon -of the rotated bounding rectange or should it retun the angle (in degrees).} +of the rotated bounding rectangle or should it return the angle (in degrees).} } \value{ a numeric angle from 0 to 360 degrees or the rotated rectangle as a diff --git a/man/fs_nndist.Rd b/man/fs_nndist.Rd index 9c1ed5f..0843398 100644 --- a/man/fs_nndist.Rd +++ b/man/fs_nndist.Rd @@ -4,27 +4,57 @@ \alias{fs_nndist} \title{Nearest neighbour distance calculation} \usage{ -fs_nndist(X, Y, maxSearch = 100, unit = "m") +fs_nndist(X, Y, maxSearch = 100, method = "poly", unit = "m") } \arguments{ \item{X}{Spatial object of \code{sf} type, typically polygons or points.} \item{Y}{(Optional) Spatial object to measure distances to.} -\item{maxSearch}{Maximum search radius around \code{X} to search. Distance in -meters. Default is 100.} +\item{maxSearch}{Maximum radius around \code{X} to search. Distance in +meters. Default is 100. To ignore the search limit, set to `NULL`.} + +\item{method}{Either \code{'poly'} or \code{'centroid'} to assign a geometry +method. See details. Default is 'poly'.} \item{unit}{Character abbreviation for the units to return from the distance calculation.} } \description{ Helper function to provide a distance calculation between - spatial ojects. The distance to the first nearest neighbour found - (optionally within a maximum search radisu) is returned. + spatial objects. The distance to the first nearest neighbour found + (optionally within a maximum search radius) is returned. } \details{ If \code{Y} is omitted the nearest neighbour distances are found within \code{X}. Otherwise, the distance for each object in \code{X} to its - nearest neighbour in \code{Y} is returned. Providing a maximum search - radius is strongly advised to speed up the calcluation. + nearest neighbour in \code{Y} is returned. + + Use \code{method} to adjust which geometry of \code{X} and \code{Y} is used + for distance calculations. When \code{method='poly'} distances are measured + between polygon edges. When \code{method='centroid'}, the centroids of + building footprints are used instead. Centroid-based distance calculations + are faster. + + Providing a maximum search radius is strongly advised to speed up the + calculation. +} +\examples{ +data("kampala", package="foot") + +# get sample of buildings +buildings <- kampala$buildings +buildings <- buildings[sample(1:nrow(buildings), size=100, replace=F),] +clusters <- kampala$clusters + +# calculate distance between buildings in m +fs_nndist(buildings, unit="m") + +# calculate unrestricted distance +# between buildings and another set of points +fs_nndist(buildings, sf::st_centroid(clusters), maxSearch=NULL) + +# use footprint centroids +fs_nndist(buildings, method='centroid', unit='m') + } diff --git a/man/fs_nndist_mean.Rd b/man/fs_nndist_mean.Rd deleted file mode 100644 index 9d9b19f..0000000 --- a/man/fs_nndist_mean.Rd +++ /dev/null @@ -1,34 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/fs_NNdist_mean.R -\name{fs_nndist_mean} -\alias{fs_nndist_mean} -\alias{fs_nndist_mean.sp} -\alias{fs_nndist_mean.sf} -\title{Nearest neighbour mean distance} -\usage{ -fs_nndist_mean(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_nndist_mean}{sp}(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_nndist_mean}{sf}(X, index = NULL, unit = NULL, col = NULL) -} -\arguments{ -\item{X}{Spatial object with building footprint polygons} - -\item{index}{A character or numeric value identifying a column within \code{X} -which provides a zonal index for summarising values. Alternatively a vector of -indices can be provided. If omitted all observations in \code{X} are assumed -to be within one zone.} - -\item{unit}{character or \code{units} object to define area. -Default is \code{NULL} which will use the units of the spatial reference system} - -\item{col}{column name within \code{X} with pre-calculated area measures} -} -\value{ -\code{data.table} of zonal indices and values. -} -\description{ -Calculate and summarise selected metrics of building -footprint polygons within zones. -} diff --git a/man/fs_nndist_median.Rd b/man/fs_nndist_median.Rd deleted file mode 100644 index 8b81c72..0000000 --- a/man/fs_nndist_median.Rd +++ /dev/null @@ -1,34 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/fs_NNdist_median.R -\name{fs_nndist_median} -\alias{fs_nndist_median} -\alias{fs_nndist_median.sp} -\alias{fs_nndist_median.sf} -\title{Nearest neighbour median distance} -\usage{ -fs_nndist_median(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_nndist_median}{sp}(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_nndist_median}{sf}(X, index = NULL, unit = NULL, col = NULL) -} -\arguments{ -\item{X}{Spatial object with building footprint polygons} - -\item{index}{A character or numeric value identifying a column within \code{X} -which provides a zonal index for summarising values. Alternatively a vector of -indices can be provided. If omitted all observations in \code{X} are assumed -to be within one zone.} - -\item{unit}{character or \code{units} object to define area. -Default is \code{NULL} which will use the units of the spatial reference system} - -\item{col}{column name within \code{X} with pre-calculated area measures} -} -\value{ -\code{data.table} of zonal indices and values. -} -\description{ -Calculate and summarise selected metrics of building -footprint polygons within zones. -} diff --git a/man/fs_nndist_sd.Rd b/man/fs_nndist_sd.Rd deleted file mode 100644 index 8e3dcee..0000000 --- a/man/fs_nndist_sd.Rd +++ /dev/null @@ -1,34 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/fs_NNdist_sd.R -\name{fs_nndist_sd} -\alias{fs_nndist_sd} -\alias{fs_nndist_sd.sp} -\alias{fs_nndist_sd.sf} -\title{Nearest neighbour mean distance} -\usage{ -fs_nndist_sd(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_nndist_sd}{sp}(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_nndist_sd}{sf}(X, index = NULL, unit = NULL, col = NULL) -} -\arguments{ -\item{X}{Spatial object with building footprint polygons} - -\item{index}{A character or numeric value identifying a column within \code{X} -which provides a zonal index for summarising values. Alternatively a vector of -indices can be provided. If omitted all observations in \code{X} are assumed -to be within one zone.} - -\item{unit}{character or \code{units} object to define area. -Default is \code{NULL} which will use the units of the spatial reference system} - -\item{col}{column name within \code{X} with pre-calculated area measures} -} -\value{ -\code{data.table} of zonal indices and values. -} -\description{ -Calculate and summarise selected metrics of building -footprint polygons within zones. -} diff --git a/man/fs_nnindex.Rd b/man/fs_nnindex.Rd index f6be7d8..67d09b7 100644 --- a/man/fs_nnindex.Rd +++ b/man/fs_nnindex.Rd @@ -4,27 +4,30 @@ \alias{fs_nnindex} \alias{fs_nnindex.sp} \alias{fs_nnindex.sf} +\alias{fs_nnindex.sfc} \title{Building Nearest Neighbour Index (NNI)} \usage{ -fs_nnindex(X, index = NULL, unit = NULL, col = NULL) +fs_nnindex(X, zone = NULL, zoneField = NULL, unit = NULL) -\method{fs_nnindex}{sp}(X, index = NULL, unit = NULL, col = NULL) +\method{fs_nnindex}{sp}(X, zone = NULL, zoneField = NULL, unit = NULL) -\method{fs_nnindex}{sf}(X, index = NULL, unit = NULL, col = NULL) +\method{fs_nnindex}{sf}(X, zone = NULL, zoneField = NULL, unit = NULL) + +\method{fs_nnindex}{sfc}(X, zone = NULL, zoneField = NULL, unit = NULL) } \arguments{ \item{X}{Spatial object with building footprints or their centroid locations.} -\item{index}{A spatial polygon object of \code{sf} or \code{sp} type. If +\item{zone}{A spatial polygon object of \code{sf} or \code{sp} type. If omitted all observations in \code{X} are assumed to be within one zone and the area of the minimum bounding circle is used for the nearest neighbour index.} -\item{unit}{character or \code{units} object to define distance. Default is -NULL} +\item{zoneField}{(Optional) Column name of unique identifiers in \code{zone} +to use. If omitted, the 'zoneID' will be numbered \code{1:nrow(zone)}.} -\item{col}{column name or index within \code{X} with pre-calculated distance -measures.} +\item{unit}{character or \code{units} object to define distance. Default +\code{NULL} will attempt to coerce units to meters.} } \value{ \code{data.table} of zonal indices and values @@ -34,12 +37,21 @@ Calculate and summarise selected metrics of building footprint polygons within zones. } \details{ -The nearest neighbour index (NNI) is a measure of clustering. It - compares the observed mean neighbour distances with a hypothetical maximum - of dispersed observations given the area of the zone. Note that NNI is - sensitive to changes in the zone area. +The nearest neighbour index (NNI) is a measure of spatial + clustering. It compares the observed mean neighbour distances with a + hypothetical maximum of dispersed observations given the area of the zone. + Note that NNI is sensitive to changes in the zone area. + + \deqn{ NNI_z = \frac{\bar{NND_z}}{(0.5 * \sqrt{\frac{A_z}{n_z}}})}, where z + is the zone, A is the area, NND is the mean nearest neighbour distance, and + n is the count. The value of NNI can range from 0 (fully disperse) to 2.15 + (clustered), with values of 1 indicating spatial randomness. + + The function uses \code{fs_nndist} to calculate the distance between + centroids of the building footprints within the same spatial zone indicated + by \code{zone}. - \deqn{ NNI_z = \frac{\bar{NND_z}}{(0.5 * \sqrt{\frac{A_z}{n_z}}})}, - where z is the zone, A is the area, NND is the mean nearest neighbour - distance, and n is the count. + Note that this function is provided as a standalone calculation. The + summary measure can be executed from within \code{calculate_footstats} by + specifying \code{what='nndist'} and \code{how='nnindex'}. } diff --git a/man/fs_perim_cv.Rd b/man/fs_perim_cv.Rd deleted file mode 100644 index 73d5f5a..0000000 --- a/man/fs_perim_cv.Rd +++ /dev/null @@ -1,22 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/fs_perim_cv.R -\name{fs_perim_cv} -\alias{fs_perim_cv} -\alias{fs_perim_cv.sp} -\alias{fs_perim_cv.sf} -\title{Building perimeter coefficient of variation (CV)} -\usage{ -fs_perim_cv(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_perim_cv}{sp}(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_perim_cv}{sf}(X, index = NULL, unit = NULL, col = NULL) -} -\value{ -\code{data.table} of zonal indices and values -} -\description{ -Calculate and summarise selected metrics of building -footprint polygons within zones. -#' @inheritParams fs_area_mean -} diff --git a/man/fs_perim_max.Rd b/man/fs_perim_max.Rd deleted file mode 100644 index b1e34b2..0000000 --- a/man/fs_perim_max.Rd +++ /dev/null @@ -1,22 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/fs_perim_max.R -\name{fs_perim_max} -\alias{fs_perim_max} -\alias{fs_perim_max.sp} -\alias{fs_perim_max.sf} -\title{Building perimeter maximum calculation} -\usage{ -fs_perim_max(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_perim_max}{sp}(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_perim_max}{sf}(X, index = NULL, unit = NULL, col = NULL) -} -\value{ -\code{data.table} of zonal indices and values -} -\description{ -Calculate and summarise selected metrics of building -footprint polygons within zones. -#' @inheritParams fs_area_mean -} diff --git a/man/fs_perim_mean.Rd b/man/fs_perim_mean.Rd deleted file mode 100644 index e6c1942..0000000 --- a/man/fs_perim_mean.Rd +++ /dev/null @@ -1,22 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/fs_perim_mean.R -\name{fs_perim_mean} -\alias{fs_perim_mean} -\alias{fs_perim_mean.sp} -\alias{fs_perim_mean.sf} -\title{Building perimeter mean calculation} -\usage{ -fs_perim_mean(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_perim_mean}{sp}(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_perim_mean}{sf}(X, index = NULL, unit = NULL, col = NULL) -} -\value{ -\code{data.table} of zonal indices and values -} -\description{ -Calculate and summarise selected metrics of building -footprint polygons within zones. -#' @inheritParams fs_area_mean -} diff --git a/man/fs_perim_median.Rd b/man/fs_perim_median.Rd deleted file mode 100644 index 1e1b1c3..0000000 --- a/man/fs_perim_median.Rd +++ /dev/null @@ -1,22 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/fs_perim_median.R -\name{fs_perim_median} -\alias{fs_perim_median} -\alias{fs_perim_median.sp} -\alias{fs_perim_median.sf} -\title{Building perimeter median calculation} -\usage{ -fs_perim_median(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_perim_median}{sp}(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_perim_median}{sf}(X, index = NULL, unit = NULL, col = NULL) -} -\value{ -\code{data.table} of zonal indices and values -} -\description{ -Calculate and summarise selected metrics of building -footprint polygons within zones. -#' @inheritParams fs_area_mean -} diff --git a/man/fs_perim_min.Rd b/man/fs_perim_min.Rd deleted file mode 100644 index 1b6e45f..0000000 --- a/man/fs_perim_min.Rd +++ /dev/null @@ -1,22 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/fs_perim_min.R -\name{fs_perim_min} -\alias{fs_perim_min} -\alias{fs_perim_min.sp} -\alias{fs_perim_min.sf} -\title{Building perimeter minimum calculation} -\usage{ -fs_perim_min(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_perim_min}{sp}(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_perim_min}{sf}(X, index = NULL, unit = NULL, col = NULL) -} -\value{ -\code{data.table} of zonal indices and values -} -\description{ -Calculate and summarise selected metrics of building -footprint polygons within zones. -#' @inheritParams fs_area_mean -} diff --git a/man/fs_perim_sd.Rd b/man/fs_perim_sd.Rd deleted file mode 100644 index 106febc..0000000 --- a/man/fs_perim_sd.Rd +++ /dev/null @@ -1,22 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/fs_perim_sd.R -\name{fs_perim_sd} -\alias{fs_perim_sd} -\alias{fs_perim_sd.sp} -\alias{fs_perim_sd.sf} -\title{Building perimeter standard deviation calculation} -\usage{ -fs_perim_sd(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_perim_sd}{sp}(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_perim_sd}{sf}(X, index = NULL, unit = NULL, col = NULL) -} -\value{ -\code{data.table} of zonal indices and values -} -\description{ -Calculate and summarise selected metrics of building -footprint polygons within zones. -#' @inheritParams fs_area_mean -} diff --git a/man/fs_perim_total.Rd b/man/fs_perim_total.Rd deleted file mode 100644 index c692688..0000000 --- a/man/fs_perim_total.Rd +++ /dev/null @@ -1,22 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/fs_perim_total.R -\name{fs_perim_total} -\alias{fs_perim_total} -\alias{fs_perim_total.sp} -\alias{fs_perim_total.sf} -\title{Building perimeter total calculation} -\usage{ -fs_perim_total(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_perim_total}{sp}(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_perim_total}{sf}(X, index = NULL, unit = NULL, col = NULL) -} -\value{ -\code{data.table} of zonal indices and values -} -\description{ -Calculate and summarise selected metrics of building -footprint polygons within zones. -#' @inheritParams fs_area_mean -} diff --git a/man/fs_settled.Rd b/man/fs_settled.Rd deleted file mode 100644 index 62c24b6..0000000 --- a/man/fs_settled.Rd +++ /dev/null @@ -1,31 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/fs_settled_binary.R -\name{fs_settled} -\alias{fs_settled} -\alias{fs_settled.sp} -\alias{fs_settled.sf} -\title{Binary indicator of settled status} -\usage{ -fs_settled(X, index = NULL, col = NULL) - -\method{fs_settled}{sp}(X, index = NULL, col = NULL) - -\method{fs_settled}{sf}(X, index = NULL, col = NULL) -} -\arguments{ -\item{X}{Spatial object with building footprint polygons} - -\item{index}{A character or numeric value identifying a column within \code{X} -which provides a zonal index for summarising values. Alternatively a vector of -indices can be provided. If omitted all observations in \code{X} are assumed -to be within one zone.} - -\item{col}{column name within \code{X} with pre-calculated area measures} -} -\value{ -\code{data.table} of zonal indices and values. -} -\description{ -Calculate and summarise selected metrics of building -footprint polygons within zones. -} diff --git a/man/fs_shape.Rd b/man/fs_shape.Rd index 4cf9430..2632259 100644 --- a/man/fs_shape.Rd +++ b/man/fs_shape.Rd @@ -12,10 +12,13 @@ Polsby, Daniel D., and Robert D. Popper. 1991. “The Third Criterion: \usage{ fs_compact(X) -fs_shape(X) +fs_shape(X, unit = NULL) } \arguments{ \item{X}{polygons of building footprints of type \code{sf}.} + +\item{unit}{string indicating unit of measure. Passed to +\code{units::set_units}.} } \value{ numeric vector of compactness values ranging from 0 to 1 for each @@ -45,3 +48,13 @@ The shape index is calculated as the ratio of footprint polygon's The function first looks for pre-calculated values of area in a field called \code{fs_area}. If not present, the areas are calculated in m^2. } +\examples{ +data("kampala", package="foot") +buildings = kampala$buildings + +fs_shape(buildings) + +# how the calculation is done - first observation +fs_area(buildings[1,]) / fs_area(fs_mbc(buildings[1,])) + +} diff --git a/man/fs_shape_mean.Rd b/man/fs_shape_mean.Rd deleted file mode 100644 index b6de755..0000000 --- a/man/fs_shape_mean.Rd +++ /dev/null @@ -1,43 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/fs_shape_mean.R -\name{fs_shape_mean} -\alias{fs_shape_mean} -\alias{fs_shape_mean.sp} -\alias{fs_shape_mean.sf} -\title{Building shape measure} -\usage{ -fs_shape_mean(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_shape_mean}{sp}(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_shape_mean}{sf}(X, index = NULL, unit = NULL, col = NULL) -} -\arguments{ -\item{X}{Spatial object with building footprint polygons} - -\item{index}{A character or numeric value identifying a column within \code{X} -which provides a zonal index for summarising values. Alternatively a vector of -indices can be provided. If omitted all observations in \code{X} are assumed -to be within one zone.} - -\item{unit}{character or \code{units} object to define area. -Default is \code{NULL} which will use the units of the spatial reference system} - -\item{col}{column name within \code{X} with pre-calculated area measures} -} -\value{ -\code{data.table} of zonal indices and values -} -\description{ -Calculate and summarise selected metrics of building footprint - polygons within zones. -} -\details{ -The shape index is calculated as the ratio of footprint polygon's - area to the area of minimum bounding circle. The mbc is calculated using - \code{fs_mbc} and \code{lwgeom}. Values closer 1 suggest more rounded - shapes. - -The function first looks for pre-calculated values of area in a field called -\code{fs_area}. If not present, the areas are calculated in m^2. -} diff --git a/man/fs_shape_median.Rd b/man/fs_shape_median.Rd deleted file mode 100644 index 34c0eb6..0000000 --- a/man/fs_shape_median.Rd +++ /dev/null @@ -1,43 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/fs_shape_median.R -\name{fs_shape_median} -\alias{fs_shape_median} -\alias{fs_shape_median.sp} -\alias{fs_shape_median.sf} -\title{Building shape measure} -\usage{ -fs_shape_median(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_shape_median}{sp}(X, index = NULL, unit = NULL, col = NULL) - -\method{fs_shape_median}{sf}(X, index = NULL, unit = NULL, col = NULL) -} -\arguments{ -\item{X}{Spatial object with building footprint polygons} - -\item{index}{A character or numeric value identifying a column within \code{X} -which provides a zonal index for summarising values. Alternatively a vector of -indices can be provided. If omitted all observations in \code{X} are assumed -to be within one zone.} - -\item{unit}{character or \code{units} object to define area. -Default is \code{NULL} which will use the units of the spatial reference system} - -\item{col}{column name within \code{X} with pre-calculated area measures} -} -\value{ -\code{data.table} of zonal indices and values -} -\description{ -Calculate and summarise selected metrics of building footprint - polygons within zones. -} -\details{ -The shape index is calculated as the ratio of footprint polygon's - area to the area of minimum bounding circle. The mbc is calculated using - \code{fs_mbc} and \code{lwgeom}. Values closer 1 suggest more rounded - shapes. - -The function first looks for pre-calculated values of area in a field called -\code{fs_area}. If not present, the areas are calculated in m^2. -} diff --git a/man/fs_varlist.Rd b/man/fs_varlist.Rd new file mode 100644 index 0000000..d8f7215 --- /dev/null +++ b/man/fs_varlist.Rd @@ -0,0 +1,28 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/varlist.R +\name{fs_varlist} +\alias{fs_varlist} +\alias{is.fs_varlist} +\title{Select variables} +\usage{ +fs_varlist(...) + +is.fs_varlist(x) +} +\arguments{ +\item{...}{Variables to include together as parameters for summary functions.} + +\item{x}{object to be tested.} +} +\description{ +Functions to construct and check for lists of variables for + \code{foot}. +} +\details{ +This helper function is used by \code{calculate_footstats} to select + multiple columns or building characteristics to be used in a summary + function. This facilitates using functions that take multiple arguments. +} +\seealso{ +\code{[calculate_footstats]} for examples of summary functions. +} diff --git a/man/gen_nnindex.Rd b/man/gen_nnindex.Rd new file mode 100644 index 0000000..1dece14 --- /dev/null +++ b/man/gen_nnindex.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/fs_functions.R +\name{gen_nnindex} +\alias{gen_nnindex} +\title{Generate a nearest neighbour index function} +\usage{ +gen_nnindex(zone, zoneField = NULL, unit = "m") +} +\arguments{ +\item{zone}{A spatial polygon object of \code{sf} or \code{sp} type. If +omitted all observations in \code{X} are assumed to be within one zone and +the area of the minimum bounding circle is used for the nearest neighbour +index.} + +\item{zoneField}{(Optional) Column name of unique identifiers in \code{zone} +to use. If omitted, the 'zoneID' will be numbered \code{1:nrow(zone)}.} + +\item{unit}{character or \code{units} object to define distance. Default will +attempt to coerce units to meters.} +} +\description{ +Creates a new instance of the \code{fs_nnindex} function and + initialises it with zone and unit information. +} +\details{ +This is a function factory. It creates a partial function in order + to allow \code{fs_nnindex} to be used by the internal loop of + \code{calculate_footstats}. This function will generally not be used on its + own. +} diff --git a/man/get_fs_metrics.Rd b/man/get_fs_metrics.Rd deleted file mode 100644 index 5f4e8a9..0000000 --- a/man/get_fs_metrics.Rd +++ /dev/null @@ -1,43 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/list_fs.R -\name{get_fs_metrics} -\alias{get_fs_metrics} -\alias{list_fs} -\alias{get_fs_units} -\alias{get_fs_group} -\title{List footprint summary metrics} -\usage{ -get_fs_metrics(short_names, group = NULL) - -list_fs(col_names = NULL) - -get_fs_units(metrics = NULL) - -get_fs_group(metrics = NULL) -} -\arguments{ -\item{short_names}{Character vector of short names to lookup \code{fs_} -function names.} - -\item{group}{Character vector to return a group of metrics (e.g. "area").} - -\item{col_names}{Character vector to select columns from the table of -metrics.} - -\item{metrics}{Character vector of \code{fs_} function names to look up the -default units.} -} -\value{ -Vector or data.frame with selected footprint metric function names - and/or units. -} -\description{ -Helper functions to list footprint metric names and units -} -\details{ -These convenience function access the internal data in - \code{foot::fs_footprint_metrics}. They provide easy look-up access for the - function names, the default units of measurement, or to return columns. - Arguments are optional and if omitted the full set of metrics will be - returned. -} diff --git a/man/gridTiles.Rd b/man/gridTiles.Rd index b28106f..313925b 100644 --- a/man/gridTiles.Rd +++ b/man/gridTiles.Rd @@ -33,15 +33,22 @@ columns)} \item cropXsize number of columns in the tile \item cropYsize number of rows in the tile } + + @examples + data("kampala") + # example dataset + g <- kampala$mastergrid + + gridTiles(g) + gridTiles(g, n=2) + gridTiles(g, px=c(5, 25)) } \description{ Split gridded data extents into subsets for parallel processing. } \details{ -gridTiles - \code{gridTiles} provides a convenient way for splitting a gridded - dataset into subdatasets for cropping or processing individually. The + dataset into sub-datasets for cropping or processing individually. The splitting uses the extent of the total grid, potentially including noData cells. } diff --git a/man/list_fs.Rd b/man/list_fs.Rd new file mode 100644 index 0000000..c771650 --- /dev/null +++ b/man/list_fs.Rd @@ -0,0 +1,43 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/list_fs.R +\name{list_fs} +\alias{list_fs} +\title{List footprint summary metrics} +\usage{ +list_fs(what = "all", how = "all") +} +\arguments{ +\item{what}{Character vector of names of characteristics to look up (e.g. +"area"). Alternatively, list \code{'all'} or all characteristics except +nearest neighbour distances (\code{'nodist'}).} + +\item{how}{Character vector of summary functions to look up (e.g. "mean") or +\code{'all'} available metrics.} +} +\value{ +Vector or \code{data.frame} with selected footprint metric function + names and/or units. +} +\description{ +Helper function to list footprint characteristic names and + built-in summary functions. +} +\details{ +Provides an easy look-up for the built-in function names, and + basic geometric characteristics used by \code{foot}. Supplying a "what" + characteristics finds all built-in functions. Conversely, supplying a "how" + function name returns are characteristics for which that summary is + available. Arguments are optional + and if both are omitted the full set of metrics will be returned. +} +\examples{ +# get the full list of all available +list_fs() + +# get all summary functions for "area" +list_fs(what="area") + +# get all characteristics relevant for entropy +list_fs(how="entropy") + +} diff --git a/man/make_templateGrid.Rd b/man/make_templateGrid.Rd deleted file mode 100644 index 0def5bf..0000000 --- a/man/make_templateGrid.Rd +++ /dev/null @@ -1,35 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/write_grid.R -\name{make_templateGrid} -\alias{make_templateGrid} -\title{Writing gridded data to binary files} -\usage{ -make_templateGrid( - filename = NULL, - datatype = "FLT8S", - template = NULL, - interleave = "BSQ", - overwrite = FALSE -) -} -\arguments{ -\item{filename}{character string path to desired output file.} - -\item{template}{A filepath or a gridded data object to be used as a -template to define the output file's resolution and positioning.} - -\item{interleave}{character string for band interleaving. Currently only 'BSQ' supported.} - -\item{overwrite}{logical. Should the output file be overwritten if it already exists? -Default is \code{False}.} - -\item{dataType}{character string of \code{raster} data types. Default is \code{FLT8S}} -} -\description{ -Write data to binary files and structure as native \code{raster} files. -} -\details{ -These functions provide a lightweight interface to binary writing demonstrated -in the \code{\link[spatial.tools]{create_blank_raster}} package. The advantage of using binary files is -the opportunity to using memory mapping (\code{mmap}) to support parallel writing. -} diff --git a/man/make_templateHeader.Rd b/man/make_templateHeader.Rd deleted file mode 100644 index a325820..0000000 --- a/man/make_templateHeader.Rd +++ /dev/null @@ -1,46 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/write_grid.R -\name{make_templateHeader} -\alias{make_templateHeader} -\title{Construct headers for gridded data} -\usage{ -make_templateHeader( - filename, - nrows, - ncols, - xmin, - ymin, - xmax, - ymax, - proj, - datatype, - nlayers = 1, - interleave = "BSQ", - nodata -) -} -\arguments{ -\item{filename}{character string path to desired output file.} - -\item{nrows}{number of rows in the data} - -\item{ncols}{number of columns in the data} - -\item{xmin, ymin, xmax, ymax}{geographic extent coordinates of the output data} - -\item{proj}{Proj4String of a coordinate reference system.} - -\item{nlayers}{number of layers (multiband) in the gridded data. Default is 1.} - -\item{interleave}{character string for band interleaving. Currently only 'BSQ' supported.} - -\item{nodata}{Value to represent no data in the output.} -} -\description{ -Creates a .grd file with information on resolution and spatial extent for -use with a binary file (.gri). -} -\details{ -These gridded files are the native files used by the -\code{raster} package. The .gri (data) and .grd (header) files must have matching filenames. -} diff --git a/man/write_imageBinary.Rd b/man/write_imageBinary.Rd deleted file mode 100644 index 6be0158..0000000 --- a/man/write_imageBinary.Rd +++ /dev/null @@ -1,41 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/write_grid.R -\name{write_imageBinary} -\alias{write_imageBinary} -\title{Fast writing of binary files} -\usage{ -write_imageBinary(data, cellNumbers = 1:length(data), filename, mapMode) -} -\arguments{ -\item{data}{vector of} - -\item{cellNumbers}{vector of cell indices to update with data values} - -\item{filename}{character string of path to the output file} - -\item{mapMode}{character string of the \code{raster} data type.} -} -\description{ -\code{write_imageBinary} uses memory mapping functions to write -gridded data to a binary output format. Binary outputs can be read natively -but some software, or with the addition of header files, be converted to -formats easily read by most software. -} -\details{ -The function uses \code{\link[mmap]{mmap}} create and update a -binary output file on the hard drive. - -The mapMode converts the \code{raster} data type strings to a \code{CType} used -by \code{mmap}. -\itemize{ -\item "LOG1S" \code{mmap::logi8()} -\item "INT1S" \code{mmap::int8()} -\item "INT1U" \code{mmap::uint8()} -\item "INT2S" \code{mmap::int16()} -\item "INT2U" \code{mmap::uint16()} -\item "INT4S" \code{mmap::int32()} -\item "INT4U" \code{NA} -\item "INT4S" \code{mmap::real32()} -\item "INT8S" \code{mmap::real64()} -} -} diff --git a/man/zonalIndex.Rd b/man/zonalIndex.Rd index fa38f93..a267216 100644 --- a/man/zonalIndex.Rd +++ b/man/zonalIndex.Rd @@ -10,19 +10,19 @@ \alias{zonalIndex.character} \title{Index buildings footprints to 'zones'} \usage{ -zonalIndex(X, zone, zoneField = NULL, returnObject = TRUE, clip = FALSE) +zonalIndex(X, zone, zoneField = NULL, method = "centroid", returnObject = TRUE) -\method{zonalIndex}{sf}(X, zone, zoneField = NULL, returnObject = TRUE, clip = FALSE) +\method{zonalIndex}{sf}(X, zone, zoneField = NULL, method = "centroid", returnObject = TRUE) -\method{zonalIndex}{sfc}(X, zone, zoneField = NULL, returnObject = TRUE, clip = FALSE) +\method{zonalIndex}{sfc}(X, zone, zoneField = NULL, method = "centroid", returnObject = TRUE) -\method{zonalIndex}{sp}(X, zone, zoneField = NULL, returnObject = TRUE, clip = FALSE) +\method{zonalIndex}{sp}(X, zone, zoneField = NULL, method = "centroid", returnObject = TRUE) -\method{zonalIndex}{stars}(X, zone, zoneField = NULL, returnObject = TRUE, clip = FALSE) +\method{zonalIndex}{stars}(X, zone, zoneField = NULL, method = "centroid", returnObject = TRUE) -\method{zonalIndex}{raster}(X, zone, zoneField = NULL, returnObject = TRUE, clip = FALSE) +\method{zonalIndex}{raster}(X, zone, zoneField = NULL, method = "centroid", returnObject = TRUE) -\method{zonalIndex}{character}(X, zone, zoneField = NULL, returnObject = TRUE, clip = FALSE) +\method{zonalIndex}{character}(X, zone, zoneField = NULL, method = "centroid", returnObject = TRUE) } \arguments{ \item{X}{Spatial data (or path to file) with building footprint polygons} @@ -30,21 +30,54 @@ zonalIndex(X, zone, zoneField = NULL, returnObject = TRUE, clip = FALSE) \item{zone}{Spatial data (or path to file) with polygon zones or a spatial grid ("raster")} -\item{zoneField}{(Optional) Unique identifier for each zone} +\item{zoneField}{(Optional) Column name of unique identifiers in \code{zone} +to use. If omitted, the 'zoneID' will be numbered \code{1:nrow(zone)}.} -\item{returnObject}{Logical of whether to return an sf object of X with zonal -information. Default \code{TRUE}.} +\item{method}{One of \code{'centroid', 'intersect', 'clip'} to determine how +footprints are allocated to zones. See details. Default is +\code{"centroid"}.} -\item{clip}{Logical of whether polygons of X which span multiple zones should -be clipped to the zone. If not clipped, then whole building footprints are -linked to each zone. Default \code{FALSE}} +\item{returnObject}{Logical of whether to return an sf object of X with zonal +information. Default is \code{TRUE} which is generally preferred.} } \value{ 'sf' object with attributes of \code{X} plus the unique zone ID or a - \code{data.table} with the ID to the records in \code{X} and the zone IDs. + \code{data.table} with the row number to the record in \code{X} matched to + the zone IDs. } \description{ Find the area, grid cells, or other zone that a building polygon overlaps or is located in. Implements an efficient spatial join by intersection. } +\details{ +Zone assignments for building footprints can be done using three + possible methods, set by the \code{method=} parameter. Defining by + 'centroid' allocates and entire building and its characteristics to the + zone(s) which its centroid intersects. Centroids are defined by + \code{\link[sf]{st_centroid}} which could be outside the polygon shape. + This is the default mode. Defining zones by 'intersect' uses the geometric + binary predicate from \code{\link[sf]{st_intersects}}. This method will + include a whole building and its characteristics into all zones that it + intersects. Therefore a building could appear to be "counted" twice. The + final approach, 'clip', uses \code{\link[sf]{st_intersection}} to split + footprints so that only that area of the polygon intersecting the zone is + include. This method is more time consuming because the geometries are + modified. +} +\examples{ +data("kampala", package="foot") + +buildings <- kampala$buildings +clusters <- kampala$clusters + +# assign zones and return a new 'sf' object +zonalIndex(buildings, clusters) + +# assign all intersecting zones +zonalIndex(buildings, clusters, method="intersect") + +# return only a table of indices - note column names +zonalIndex(buildings, clusters, zoneField="Id", returnObject=FALSE) + +} diff --git a/pkg_build.R b/pkg_build.R index 303b30d..fd984b3 100644 --- a/pkg_build.R +++ b/pkg_build.R @@ -13,7 +13,9 @@ usethis::use_package("parallel") usethis::use_package("foreach") usethis::use_package("filelock") # usethis::use_package("purrr") +usethis::use_package("iterators") usethis::use_package("units") +usethis::use_package("mmap") # library("roxygen2") # package documentation @@ -28,5 +30,8 @@ devtools::build() devtools::install() devtools::reload(devtools::inst('foot')) .rs.restartR() + +rmarkdown::render("README.Rmd") +pkgdown::build_site() - +devtools::build_vignettes() diff --git a/vignettes/bigfoot.Rmd b/vignettes/bigfoot.Rmd index a8ec55e..bd89792 100644 --- a/vignettes/bigfoot.Rmd +++ b/vignettes/bigfoot.Rmd @@ -1,6 +1,6 @@ --- title: "Gridded footprint calculation layers" -date: 'June 11, 2020' +date: '`r format(Sys.Date(), "%B %d, %Y")`' description: > Building footprints or polygons representing the outline shape of structures can provide a unique data source for studying urban areas at high spatial @@ -14,29 +14,31 @@ vignette: > %\VignetteEncoding{UTF-8} --- - +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>", + tidy.opts=list(width.cutoff=80), + tidy=TRUE +) +``` The `foot` package was developed by WorldPop at the University of Southampton ([www.worldpop.org](https://www.worldpop.org)) to support geometric calculations and zonal summaries of measures from building footprint polygons. The -`vignette(footsteps)` provides an introduction to package and the functionality -of calculating and summarising morphology measures. This vignette builds on -those methods and demonstrates a more advanced workflow to produce gridded -summaries of buildings measures and how to handle large data sets. - -This vignette use a portion of Ordnance Survey's OS OpenMap Local data of -building footprint polygons. The polygons cover the area of Southampton. These -data were originally processed and made available by Alasdair Rae -([http://ajrae.staff.shef.ac.uk/buildings/](http://ajrae.staff.shef.ac.uk/buildings/)) -under the [OS OpenData License](http://os.uk/opendata/licence). The data contain -Ordnance Survey data © Crown copyright and database right 2018. - - -```r +`vignette("footsteps", package="foot")` provides an introduction to package and +the functionality of calculating and summarising morphology measures. This +vignette builds on those methods and demonstrates a more advanced workflow to +produce gridded summaries of buildings measures. In particular, the focus is on +using `foot::calculate_bigfoot()` in order how to handle very large (i.e. +national-scale) datasets of building footprints. + +```{r setup} library(foot) ``` ## Calculations with `foot` + The central function for producing gridded layers is `calculate_bigfoot`. It is designed to support processing large sets of building polygons into gridded (GeoTiff) summary layers. The procedure is intended to be more memory-efficient @@ -44,253 +46,240 @@ by splitting the processing area into "tiles" which can then be handled individually or in parallel. This function works as a wrapper to `calculate_footstats` as well as several helper functions within the `foot` package for managing the input/output and creating and indexing spatial zones. +Because it extends `calculate_footstats`, this function takes the same `what=` +and `how=` arguments to define the summary operations. Likewise the available +metrics are available from `list_fs()`. By default the function performs calculations in parallel (which can be changed -with the argument `parallel=FALSE`). To monitor the supplied values and +with the argument `parallel=FALSE`). Users can adjust the number of computing +cores used with the argument `nCores=`) To monitor the supplied values and processing steps, set `verbose=TRUE`. ### Main inputs -Users need to supply: + +Users need to supply `calculate_bigfoot()` with: * A path to a file of building footprints in a spatial vector data format (e.g. `.gpkg`, `.shp`) * A filepath to a template gridded dataset specifying the extent and resolution for the outputs (e.g. `.tif`) -For example. +For example: +```{r include=FALSE} +# load data +data("kampala", package="foot") -```r -# local file paths for demonstration -dir <- "/home/jochem/Documents/projects/0_data" -bldgPath <- file.path(dir, "soton_buildings.gpkg") -sotonGrid <- file.path(dir, "soton_borough_100m.tif") +buildings <- kampala$buildings +adminzones <- kampala$adminZones +mgrid <- kampala$mastergrid ``` -While `R` objects can be supplied for these parameters, it is recommended to -supply character strings of paths to the files. `calculate_bigfoot` will only -read the subset of data needed for a particular processing step. This is -intended to allow users to process much larger datasets than can be held in -memory. +While `R` objects can be supplied for the function parameters (as will be shown +in this example), it is recommended to supply character strings of paths to the +files. `calculate_bigfoot` will only read the subset of data needed for a +particular processing step. This is intended to allow users to process much +larger datasets than can be held in memory. ### Basic calculations + With the key inputs for the file paths set, the processing can be executed with a single function. - -```r +```{r message=FALSE, warning=FALSE, tidy=FALSE} # basic function call with default parameters -calculate_bigfoot(X = bldgPath, template = sotonGrid, metrics = "settled", verbose = TRUE) -#> Setting up cluster -#> Begin parallel tile processing -#> -#> Finished processing all tiles: 2020-06-11 14:52:55 +calculate_bigfoot(X=buildings, + template=mgrid, + what="settled", how="binary", + parallel=FALSE, verbose=TRUE) ``` +The documentation and default parameters are available from +`?foot::calculate_bigfoot`. + +The arguments `what` and `how` are the same as for `calculate_footstats`. They +provide a character or list list of character names or characteristics (i.e. +columns within the building footprint attributes) and summary functions, +respectively. The available metrics can be retrieved with `list_fs()`. ### Specifying outputs -By default the outputs of `bigfoot` are saved as GeoTiffs in `R`'s `tempdir()` -which is changed for each session. Each grid is named by the short name of -the calculated metric. +By default the outputs of `bigfoot` are saved as GeoTiffs in the user's working +directory (`getwd()`). Each grid is named by the combination of characteristics +and summary function. The example below shows how to retrieve an output of +`calculate_bigfoot`. -```r -# retrieve the gridded output -outGrid <- raster::raster(file.path(tempdir(), "settled.tif")) +```{r Fig1, fig.cap="Binary settled raster at 100m resolution", fig.height=6, fig.width=6} +# retrieve the gridded outputs +outGrid <- raster::raster(file.path(getwd(), "settled_binary.tif")) raster::plot(outGrid) ``` -![Binary settled raster for Southampton at 100m resolution](figure/Fig1-1.png) - -Users can specify an output path to another folder location. Additionally a -"tag" can be specified as a parameter to the function. The tag is appended to -the beginning of each file name. This can be useful for identifying different -outputs. +Users can specify an output path to another folder location (`outputPath=`). +Additionally a "tag" can be specified as a parameter to the function +(`outputTag=`). The tag is appended to the beginning of each file name with an +underscore. This can be useful for identifying different outputs. These options +are demonstrated in the code block below. + +```{r, tidy=FALSE} +# basic function call specifying output parameters +calculate_bigfoot(X=buildings, + template=mgrid, + what="settled", how="binary", + outputPath=tempdir(), + outputTag="TAG", + parallel=FALSE, verbose=TRUE) +``` ### Multiple metrics + As with `calculate_footstats` multiple metrics and summary statistics can be -supplied to `bigfoot` as a vector of strings. The list of metrics is available -in `foot::fs_footprint_metrics` or it can be retrieved with several look-up -functions (see `?get_fs_metrics`). - - -```r -# re-running the basic call -calculate_bigfoot(X=bldgPath, - metrics=c("settled","area_cv"), # multiple metrics - template=sotonGrid, - outputPath=tempdir(), # output folder location as a string - outputTag="soton") # filename tag +supplied to `bigfoot` as a list or nested lists grouping characteristics and +functions. + +```{r message=FALSE, warning=FALSE, tidy=FALSE} +# re-running the basic call with multiple metrics +calculate_bigfoot(X=buildings, + template=mgrid, + what=list(list("settled"), list("area")), + how=list(list("binary"), list("cv")), + outputPath=tempdir(), + outputTag="TAG", parallel=FALSE) ``` - -```r -# retrieve the gridded outputs must add the 'tag' to the filename -outGrid <- raster::raster(file.path(tempdir(), "soton_area_cv.tif")) +```{r Fig2, fig.cap="Coeff. of variation in building area at 100m resolution", fig.height=6, fig.width=6} +# retrieve the gridded outputs +# Note: must add the "tag" to the filename +outGrid <- raster::raster(file.path(tempdir(), "TAG_area_cv.tif")) raster::plot(outGrid) +plot(sf::st_geometry(buildings), fill=NA, add=T) ``` -![Coeff. of variation in building area for Southampton at 100m resolution](figure/Fig2-1.png) - ### Focal window statistics + In the examples above, the footprint statistics are summarised for buildings -which intersect the pixels of the template grid. Internally this is handled by -`zonalIndex()`. However, it is also possible to calculate and summarise building -features within a local, circular window. The output is still associated with -each template grid cell, similar to a moving window analysis. The focal radius -distance is always specified in meters. +whose centroid intersect the pixels of the template grid. Internally this is +handled by `zonalIndex()`. The method for assigning footprints to spatial zones +can be adjusted using `controlZone` options. + +It is also possible to extend those zones beyond the individual pixels and to +calculate and summarise building footprints from within a local, circular window +centred on each pixel. The output is still associated with each template grid +cell. This process is similar to a moving window or filter analysis. The focal +radius distance is specified in meters. + +A hypothetical example of using a 300 m focal radius around a pixel centroid to +construct a local zone and using building centroids is shown below. +```{r, Fig3, fig.cap="Example of 300 m focal radius. Selected footprints are highlighted in red", echo=F, message=FALSE, warning=FALSE} +cent <- sf::st_centroid(sf::st_as_sfc(sf::st_bbox(mgrid))) + +cent <- sf::st_transform(cent, 32636) +cBuff <- sf::st_buffer(cent, 300) +cBuff <- sf::st_transform(cBuff, 4326) + +bCirc <- zonalIndex(buildings, zone=sf::st_as_sf(cBuff)) +plot(sf::st_geometry(cBuff), lwd=3, lty="dashed", border="black") +plot(sf::st_geometry(buildings), col="grey", add=T) +plot(sf::st_geometry(bCirc), col="red", add=T) +plot(sf::st_geometry(cBuff), lwd=3, lty="dashed", border="black", add=T) -```r +``` + +Below is an example of applying a 300 m focal radius in action for a gridded +output layer. + +```{r message=FALSE, warning=FALSE, tidy=FALSE} # moving focal window calculations -calculate_bigfoot(X=bldgPath, - metrics=c("settled", - "area_mean", - "perim_total"), +calculate_bigfoot(X=buildings, + template=mgrid, + what=list(list("area"), list("perimeter")), + how=list(list("mean"), list("sum")), focalRadius=300, # buffer radius in meters - template=sotonGrid, outputPath=tempdir(), - outputTag="soton") + outputTag="TAG", parallel=FALSE) ``` +Creating the focal window is handled by `calculate_bigfoot`. The focal window is +also taken into consideration by the function when creating processing tiles - +the footprints are extract from outside the tile, in neighbouring areas, to +prevent edge effects Note that when a focal radius is specified, this value is always appended to the -end of the file names so that the output can be identified. - -```r -# note that the filename includes both a tag and a focal radius value -outGrid <- raster::raster(file.path(tempdir(), "soton_perim_total_300.tif")) +end of the file names so that the outputs can be identified. +```{r Fig4, fig.cap="Total building perimeter in a 300m radius window. Output at a 100m resolution", fig.height=6, fig.width=6} +# Note that the filename includes both a tag and a focal radius value +outGrid <- raster::raster(file.path(tempdir(), "TAG_perimeter_sum_300.tif")) raster::plot(outGrid) ``` -![Total building perimeter in a 300m radius window. Output at a 100m resolution](figure/Fig3-1.png) - - ## Options and finer control + The `calculate_bigfoot` function is set up with default values that should work under most conditions; however, there is additional flexibility for users to specify alternative parameters. ### Specifying geometry units + To override the default units used in the geometry calculations, a named list of unit strings can be supplied to the `controlUnits` argument. This list can -contain named items for `areaUnit`, `perimUnit`, and `distUnit`. The value of -each item should be coercible with `units::as_units`. +contain named items for `areaUnit`, `perimUnit`, and `distUnit`. The default +values are meters ("m") and square meters ("m^2") The value of each item should +be coercible with `units::as_units`. - -```r +```{r fig.height=6, fig.width=6, message=FALSE, warning=FALSE, Fig5, fig.cap="Total building perimeter in KM", tidy=FALSE} # change the default units used to calculate area and distance -calculate_bigfoot(X=bldgPath, - metrics=c("area_mean", - "perim_total"), +calculate_bigfoot(X=buildings, + template=mgrid, + what=list(list("area"), list("perimeter")), + how=list(list("mean"), list("sum")), + controlZone=list("method"="intersect"), # join by intersection controlUnits=list(areaUnit="m^2", # change default units perimUnit="km"), - template=sotonGrid, outputPath=tempdir(), - outputTag="soton", + outputTag="TAG", parallel=FALSE, verbose=TRUE) -#> -#> Tile: 1 of 1 -#> Reading layer `soton_buildings' from data source `/home/jochem/Documents/projects/0_data/soton_buildings.gpkg' using driver `GPKG' -#> Simple feature collection with 31289 features and 10 fields -#> geometry type: MULTIPOLYGON -#> dimension: XYZ -#> bbox: xmin: 436985.8 ymin: 109485.5 xmax: 447734.5 ymax: 117564.1 -#> z_range: zmin: 0 zmax: 0 -#> projected CRS: OSGB 1936 / British National Grid -#> Warning in st_cast.sf(Xsub, "POLYGON"): repeating attributes for all sub-geometries for which they may not be constant -#> Pre-calculating footprint areas -#> Pre-calculating footprint perimeters -#> Linking to GEOS 3.8.0, GDAL 3.0.4, PROJ 6.3.1 -#> WARNING: different compile-time and runtime versions for GEOS found: -#> Linked against: 3.8.0-CAPI-1.13.1 compiled against: 3.8.1-CAPI-1.13.3 -#> It is probably a good idea to reinstall sf, and maybe rgeos and rgdal too -#> Generating zonal index -#> Creating index -#> Selectinig metrics -#> -#> Calculating 2 metrics ... -#> fs_area_mean -#> fs_perim_total -#> Finished calculating metrics. -#> Writing output tiles -#> Finished writing grids -#> -#> Finished processing all tiles: 2020-06-11 14:53:27 # plot the total perimeter, measured in kilometres -outGrid <- raster::raster(file.path(tempdir(), "soton_perim_total.tif")) +outGrid <- raster::raster(file.path(tempdir(), "TAG_perimeter_sum.tif")) raster::plot(outGrid) ``` -![Total building perimeter in KM](figure/Fig4-1.png) - -In the above example, note that the units for the nearest neighbour distance -were not specified in the `controlUnits` list. The default value was still used. - ### Filtering buildings + In some settings it may be preferable to exclude very small and/or very large building footprint polygons. The lower and upper bounds for filtering can be -specified with `minArea` and `maxArea` arguments. The values for these filters -are in the same units specified by `controlUnits` or the default value for area -calculations. Note that an "area" footprint statistic does not need to be -requested. - - -```r -calculate_bigfoot(X=bldgPath, - metrics=c("shape_mean", - "count", - "perim_total"), +specified with `minArea` and `maxArea` in the `filter` argument. The values for +these filters are in the same units specified by `controlUnits` or the default +value for area calculations. Note that an "area" footprint statistic does not +need to be requested as this characteristic is automatically calculated to +enable filtering. + +```{r message=FALSE, warning=FALSE, tidy=FALSE} +# Filtering: # footprints must be larger than 50 m^2 and smaller than 1000 m^2 +calculate_bigfoot(X=buildings, + template=mgrid, + what=list(list("shape"), list("settled"), list("perimeter")), + how=list(list("mean"), list("count"), list("sum")), controlUnits=list(areaUnit="m^2"), - minArea=50, # footprints must be larger than 50 m^2 - maxArea=1000, # footprints must be smaller than 1000 m^2 - template=sotonGrid, + filter=list(minArea=50, maxArea=1000), outputPath=tempdir(), - outputTag="soton", parallel=FALSE, verbose=TRUE) -#> -#> Tile: 1 of 1 -#> Reading layer `soton_buildings' from data source `/home/jochem/Documents/projects/0_data/soton_buildings.gpkg' using driver `GPKG' -#> Simple feature collection with 31289 features and 10 fields -#> geometry type: MULTIPOLYGON -#> dimension: XYZ -#> bbox: xmin: 436985.8 ymin: 109485.5 xmax: 447734.5 ymax: 117564.1 -#> z_range: zmin: 0 zmax: 0 -#> projected CRS: OSGB 1936 / British National Grid -#> Warning in st_cast.sf(Xsub, "POLYGON"): repeating attributes for all sub-geometries for which they may not be constant -#> Pre-calculating footprint areas -#> Pre-calculating footprint perimeters -#> Filtering features larger than 50 -#> Filtering features smaller than 1000 -#> Generating zonal index -#> Creating index -#> Selectinig metrics -#> -#> Calculating 3 metrics ... -#> fs_count -#> fs_perim_total -#> fs_shape_mean -#> Finished calculating metrics. -#> Writing output tiles -#> Finished writing grids -#> -#> Finished processing all tiles: 2020-06-11 14:53:56 ``` -In the map of the results, note the much smaller number of structures in pixels -around the central business districts and southwestern edge of the study region. - -```r -outGrid <- raster::raster(file.path(tempdir(), "soton_count.tif")) +In the map of the results below, each pixel is the count of footprints present. +Note the smaller number of structures and sparseness of structures in pixels +around the centre portions of the image. This corresponds with the a business +district and industrial areas with fewer, but larger structures. +```{r Fig6, fig.cap="Count of buildings with area >50 m^2 and <1000 m^2", fig.height=6, fig.width=6} +outGrid <- raster::raster(file.path(tempdir(), "settled_count.tif")) raster::plot(outGrid) ``` -![Count of buildings with area >50 m^2 and <1000 m^2](figure/Fig5-1.png) - ### Tile size + The size of the processing tiles, specified in pixel dimensions (rows, columns) can be an important factor in the efficiency of the calculations. Smaller tile regions result in fewer building footprints being read/processed at one time, @@ -299,95 +288,27 @@ default value is 500 pixels. For the small demonstration shown here that results in one tile for the whole region. To show multiple tile processing, a small size is supplied and the processing is done in parallel with verbose output. - -```r -calculate_bigfoot(X=bldgPath, - template=sotonGrid, - metrics=c("settled","compact_mean"), - tileSize=c(100, 75), # rows x columns in pixels +```{r message=FALSE, warning=FALSE, tidy=FALSE} +calculate_bigfoot(X=buildings, + template=mgrid, + what="compact", + how="mean", + tileSize=c(20, 20), # rows x columns in pixels parallel=FALSE, verbose=TRUE) -#> -#> Tile: 1 of 2 -#> Reading layer `soton_buildings' from data source `/home/jochem/Documents/projects/0_data/soton_buildings.gpkg' using driver `GPKG' -#> Simple feature collection with 19510 features and 10 fields -#> geometry type: MULTIPOLYGON -#> dimension: XYZ -#> bbox: xmin: 436985.8 ymin: 109485.5 xmax: 444270.8 ymax: 117564.1 -#> z_range: zmin: 0 zmax: 0 -#> projected CRS: OSGB 1936 / British National Grid -#> Warning in st_cast.sf(Xsub, "POLYGON"): repeating attributes for all sub-geometries for which they may not be constant -#> Pre-calculating footprint areas -#> Pre-calculating footprint perimeters -#> Generating zonal index -#> Creating index -#> Selectinig metrics -#> -#> Calculating 2 metrics ... -#> fs_settled -#> fs_compact_mean -#> Finished calculating metrics. -#> Writing output tiles -#> Finished writing grids -#> -#> Tile: 2 of 2 -#> Reading layer `soton_buildings' from data source `/home/jochem/Documents/projects/0_data/soton_buildings.gpkg' using driver `GPKG' -#> Simple feature collection with 11872 features and 10 fields (with 1 geometry empty) -#> geometry type: MULTIPOLYGON -#> dimension: XYZ -#> bbox: xmin: 444148.1 ymin: 109493.6 xmax: 447734.5 ymax: 116557.5 -#> z_range: zmin: 0 zmax: 0 -#> projected CRS: OSGB 1936 / British National Grid -#> Warning in st_cast.sf(Xsub, "POLYGON"): repeating attributes for all sub-geometries for which they may not be constant -#> Pre-calculating footprint areas -#> Pre-calculating footprint perimeters -#> Generating zonal index -#> Creating index -#> Selectinig metrics -#> -#> Calculating 2 metrics ... -#> fs_settled -#> fs_compact_mean -#> Finished calculating metrics. -#> Writing output tiles -#> Finished writing grids -#> -#> Finished processing all tiles: 2020-06-11 14:54:05 ``` +## Next steps -*** +This vignette has provided an overview of how to create gridded outputs layers +summarising building footprint morphology measures. This workflow uses +`calculate_bigfoot` and is designed to work for large sets of data through tiled +read/writing and processing these tiles in parallel. The `bigfoot` functionality +extends and makes use of `footstats`. Both of these functions can take +use-supplied and custom summary functions. This advanced usage is demonstrated +in `vignette("cobbler", package="foot")`. -```r +*** +```{r} sessionInfo() -#> R version 3.6.3 (2020-02-29) -#> Platform: x86_64-pc-linux-gnu (64-bit) -#> Running under: Ubuntu 20.04 LTS -#> -#> Matrix products: default -#> BLAS: /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.9.0 -#> LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.9.0 -#> -#> locale: -#> [1] LC_CTYPE=en_GB.UTF-8 LC_NUMERIC=C LC_TIME=en_GB.UTF-8 LC_COLLATE=en_GB.UTF-8 LC_MONETARY=en_GB.UTF-8 -#> [6] LC_MESSAGES=en_GB.UTF-8 LC_PAPER=en_GB.UTF-8 LC_NAME=C LC_ADDRESS=C LC_TELEPHONE=C -#> [11] LC_MEASUREMENT=en_GB.UTF-8 LC_IDENTIFICATION=C -#> -#> attached base packages: -#> [1] stats graphics grDevices utils datasets methods base -#> -#> other attached packages: -#> [1] sf_0.9-2 foot_0.3.0 knitr_1.28 -#> -#> loaded via a namespace (and not attached): -#> [1] Rcpp_1.0.4.6 lattice_0.20-40 prettyunits_1.1.1 class_7.3-16 ps_1.3.2 assertthat_0.2.1 rprojroot_1.3-2 -#> [8] digest_0.6.25 foreach_1.4.8 R6_2.4.1 backports_1.1.5 evaluate_0.14 e1071_1.7-3 highr_0.8 -#> [15] pillar_1.4.3 rlang_0.4.5 rstudioapi_0.11 data.table_1.12.8 callr_3.4.3 raster_3.1-5 desc_1.2.0 -#> [22] devtools_2.3.0 rgdal_1.4-8 stringr_1.4.0 compiler_3.6.3 xfun_0.12 pkgconfig_2.0.3 pkgbuild_1.0.6 -#> [29] mmap_0.6-19 tidyselect_1.0.0 tibble_3.0.1 codetools_0.2-16 fansi_0.4.1 crayon_1.3.4 dplyr_0.8.5 -#> [36] withr_2.1.2 grid_3.6.3 lwgeom_0.2-3 lifecycle_0.2.0 DBI_1.1.0 magrittr_1.5 formatR_1.7 -#> [43] units_0.6-6 KernSmooth_2.23-16 cli_2.0.2 stringi_1.4.6 fs_1.3.1 remotes_2.1.1 doParallel_1.0.15 -#> [50] sp_1.4-1 testthat_2.3.2 ellipsis_0.3.0 vctrs_0.2.4 iterators_1.0.12 tools_3.6.3 glue_1.4.0 -#> [57] purrr_0.3.3 processx_3.4.2 abind_1.4-5 pkgload_1.0.2 parallel_3.6.3 yaml_2.2.1 sessioninfo_1.1.1 -#> [64] stars_0.4-1 classInt_0.4-3 memoise_1.1.0 usethis_1.6.1 ``` diff --git a/vignettes/bigfoot.Rmd.orig b/vignettes/bigfoot.Rmd.orig deleted file mode 100644 index 1ea4a46..0000000 --- a/vignettes/bigfoot.Rmd.orig +++ /dev/null @@ -1,245 +0,0 @@ ---- -title: "Gridded footprint calculation layers" -date: '`r format(Sys.Date(), "%B %d, %Y")`' -description: > - Building footprints or polygons representing the outline shape of structures - can provide a unique data source for studying urban areas at high spatial - resolutions. This vignette will demonstrate how to use the `foot` package to - produce high-spatial resolution gridded data layers of geometric and - morphological measures, potentially for large regions. -output: rmarkdown::html_vignette -vignette: > - %\VignetteIndexEntry{Gridded footprint calculation layers} - %\VignetteEngine{knitr::rmarkdown} - %\VignetteEncoding{UTF-8} ---- - -```{r, include = FALSE} -knitr::opts_chunk$set( - collapse = TRUE, - comment = "#>", - tidy.opts=list(width.cutoff=80), - tidy=TRUE -) -``` - -The `foot` package was developed by WorldPop at the University of Southampton -([www.worldpop.org](https://www.worldpop.org)) to support geometric calculations -and zonal summaries of measures from building footprint polygons. The -`vignette(footsteps)` provides an introduction to package and the functionality -of calculating and summarising morphology measures. This vignette builds on -those methods and demonstrates a more advanced workflow to produce gridded -summaries of buildings measures and how to handle large data sets. - -This vignette use a portion of Ordnance Survey's OS OpenMap Local data of -building footprint polygons. The polygons cover the area of Southampton. These -data were originally processed and made available by Alasdair Rae -([http://ajrae.staff.shef.ac.uk/buildings/](http://ajrae.staff.shef.ac.uk/buildings/)) -under the [OS OpenData License](http://os.uk/opendata/licence). The data contain -Ordnance Survey data © Crown copyright and database right 2018. - -```{r setup} -library(foot) -``` - -## Calculations with `foot` -The central function for producing gridded layers is `calculate_bigfoot`. It is -designed to support processing large sets of building polygons into gridded -(GeoTiff) summary layers. The procedure is intended to be more memory-efficient -by splitting the processing area into "tiles" which can then be handled -individually or in parallel. This function works as a wrapper to -`calculate_footstats` as well as several helper functions within the `foot` -package for managing the input/output and creating and indexing spatial zones. - -By default the function performs calculations in parallel (which can be changed -with the argument `parallel=FALSE`. To monitor the supplied values and -processing steps, set `verbose=TRUE`. - -### Main inputs -Users need to supply: - -* A path to a file of building footprints in a spatial vector data format (e.g. -`.gpkg`, `.shp`) -* A filepath to a template gridded dataset specifying the extent and resolution -for the outputs (e.g. `.tif`) - -For example. -```{r} -# local file paths for demonstration -dir <- "/home/jochem/Documents/projects/0_data" -bldgPath <- file.path(dir, "soton_buildings.gpkg") -sotonGrid <- file.path(dir, "soton_borough_100m.tif") -``` - -While `R` objects can be supplied for these parameters, it is recommended to -supply character strings of paths to the files. `calculate_bigfoot` will only -read the subset of data needed for a particular processing step. This is -intended to allow users to process much larger datasets than can be held in -memory. - -### Basic calculations -With the key inputs for the file paths set, the processing can be executed with -a single function. - -```{r} -# basic function call with default parameters -calculate_bigfoot(X=bldgPath, - template=sotonGrid, - metrics="settled", - verbose=TRUE) -``` - -### Specifying outputs -By default the outputs of `bigfoot` are saved as GeoTiffs in `R`'s `tempdir()` -which is changed for the each session. Each grid is named by the short name of -the calculated metric. - -```{r Fig1, fig.cap="Binary settled raster for Southampton at 100m resolution", fig.height=6, fig.width=6} -# retrieve the gridded outputs -outGrid <- raster::raster(file.path(tempdir(), "settled.tif")) - -raster::plot(outGrid) -``` - -Users can specify an output path to another folder location. Additionally a -"tag" can be specified as a parameter to the function. The tag is appended to -the beginning of each file name. This can be useful for identifying different -outputs. - -### Multiple metrics -As with `calculate_footstats` multiple metrics and summary statistics can be -supplied to `bigfoot` as a vector of strings. The list of metrics is available -in `foot::fs_footprint_metrics` or it can be retrieved with several look-up -functions (see `?get_fs_metrics`). - -```{r} -# re-running the basic call -calculate_bigfoot(X=bldgPath, - metrics=c("settled","area_cv"), # multiple metrics - template=sotonGrid, - outputPath=tempdir(), # output folder location as a string - outputTag="soton") # filename tag -``` -```{r Fig2, fig.cap="Coeff. of variation in building area for Southampton at 100m resolution", fig.height=6, fig.width=6} -# retrieve the gridded outputs -# must add the "tag" to the filename -outGrid <- raster::raster(file.path(tempdir(), "soton_area_cv.tif")) - -raster::plot(outGrid) -``` - -### Focal window statistics -In the examples above, the footprint statistics are summarised for buildings -which intersect the pixels of the template grid. Internally this is handled by -`zonalIndex()`. However, it is also possible to calculate and summarise building -features within a local, circular window. The output is still associated with -each template grid cell, similar to a moving window analysis. The focal radius -distance is always specified in meters. - -```{r} -# moving focal window calculations -calculate_bigfoot(X=bldgPath, - metrics=c("settled", - "area_mean", - "perim_total"), - focalRadius=300, # buffer radius in meters - template=sotonGrid, - outputPath=tempdir(), - outputTag="soton") -``` - -Note that when a focal radius is specified, this value is always appended to the -end of the file names so that the outputs can be identified. -```{r Fig3, fig.cap="Total building perimeter in a 300m radius window. Output at a 100m resolution", fig.height=6, fig.width=6} -# note that the filename includes both a tag and a focal radius value -outGrid <- raster::raster(file.path(tempdir(), "soton_perim_total_300.tif")) - -raster::plot(outGrid) -``` - - -## Options and finer control -The `calculate_bigfoot` function is set up with default values that should work -under most conditions; however, there is additional flexibility for users to -specify alternative parameters. - -### Specifying geometry units -To override the default units used in the geometry calculations, a named list of -unit strings can be supplied to the `controlUnits` argument. This list can -contain named items for `areaUnit`, `perimUnit`, and `distUnit`. The value of -each item should be coercible with `units::as_units`. - -```{r Fig4, fig.cap="Total building perimeter in KM", fig.height=6, fig.width=6} -# change the default units used to calculate area and distance -calculate_bigfoot(X=bldgPath, - metrics=c("area_mean", - "perim_total"), - controlUnits=list(areaUnit="m^2", # change default units - perimUnit="km"), - template=sotonGrid, - outputPath=tempdir(), - outputTag="soton", - parallel=FALSE, - verbose=TRUE) - -# plot the total perimeter, measured in kilometres -outGrid <- raster::raster(file.path(tempdir(), "soton_perim_total.tif")) -raster::plot(outGrid) -``` -In the above example, note that the units for the nearest neighbour distance -were not specified in the `controlUnits` list. The default value was still used. - -### Filtering buildings -In some settings it may be preferable to exclude very small and/or very large -building footprint polygons. The lower and upper bounds for filtering can be -specified with `minArea` and `maxArea` arguments. The values for these filters -are in the same units specified by `controlUnits` or the default value for area -calculations. Note that an "area" footprint statistic does not need to be -requested. - -```{r} -calculate_bigfoot(X=bldgPath, - metrics=c("shape_mean", - "count", - "perim_total"), - controlUnits=list(areaUnit="m^2"), - minArea=50, # footprints must be larger than 50 m^2 - maxArea=1000, # footprints must be smaller than 1000 m^2 - template=sotonGrid, - outputPath=tempdir(), - outputTag="soton", - parallel=FALSE, - verbose=TRUE) -``` - -In the map of the results, note the much smaller number of structures in pixels -around the central business districts and southwestern edge of the study region. -```{r Fig5, fig.cap="Count of buildings with area >50 m^2 and <1000 m^2", fig.height=6, fig.width=6} -outGrid <- raster::raster(file.path(tempdir(), "soton_count.tif")) - -raster::plot(outGrid) -``` - -### Tile size -The size of the processing tiles, specified in pixel dimensions (rows, columns) -can be an important factor in the efficiency of the calculations. Smaller tile -regions result in fewer building footprints being read/processed at one time, -but there is an overhead computational cost of reading/writing files. The -default value is 500 pixels. For the small demonstration shown here that results -in one tile for the whole region. To show multiple tile processing, a small size -is supplied and the processing is done in parallel with verbose output. - -```{r} -calculate_bigfoot(X=bldgPath, - template=sotonGrid, - metrics=c("settled","compact_mean"), - tileSize=c(100, 75), # rows x columns in pixels - parallel=FALSE, - verbose=TRUE) -``` - -*** -```{r} -sessionInfo() -``` - diff --git a/vignettes/cobbler.Rmd b/vignettes/cobbler.Rmd new file mode 100644 index 0000000..e92d512 --- /dev/null +++ b/vignettes/cobbler.Rmd @@ -0,0 +1,238 @@ +--- +title: "Using custom summary functions in `foot`" +date: '`r format(Sys.Date(), "%B %d, %Y")`' +description: > + Building footprints or polygons representing the outline shape of structures + can provide a unique data source for studying urban areas at high spatial + resolutions. This vignette will demonstrate how to supply custom functions to + the `foot` package to extend the range of potential summary metrics. +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{Using custom summary functions in `foot`} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, c1, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>", + tidy.opts=list(width.cutoff=80), + tidy=TRUE +) +``` + +```{r setup, include=FALSE} +knitr::opts_chunk$set(echo = TRUE) +``` + +The `foot` package was developed by WorldPop at the University of Southampton +([www.worldpop.org](https://www.worldpop.org)) to support geometric calculations +and summaries of measures from building footprint polygons. This vignette +demonstrates how users can extend the basic functionality of +`calculate_footstats` and `calculate_bigfoot` by making and supplying their own +functions to summarise footprint characteristics. For an introduction to the +package, see `vignette("footsteps")`. + +```{r, c3} +library(foot) +``` + +## Calculations with `foot` + +As noted in the introductory vignettes, `foot` primarily uses +`calculate_footstats` to calculate and summarise metrics. Internally this +function uses `data.table` in order to handle large sets of building footprints +and efficiently summarise them. The attributes to be summarised (`what`) are +supplied to function names (`how`). These internal structures also allow for +user-defined functions to be specified. + +### Data preparation + +To demonstrate using custom functions, we will first add some additional +attribute "data" to the footprints which we will use. + +```{r, c4} +data("kampala", package="foot") + +buildings <- kampala$buildings +adminzones <- kampala$adminZones + +# Adding random data +# categorical variable +buildings$category <- sample(LETTERS[1:5], size=nrow(buildings), replace=T) +# continuous variable +buildings$mult <- sample(rnorm(nrow(buildings), mean=10, sd=2)) +``` + +We can use any attributes of the footprints within `calculate_footstats` and +`calculate_bigfoot`, not only the built-in morphology measures listed by +`list_fs(what='all')`. + +```{r, c5, tidy=F} +# summarising a new data value +calculate_footstats(buildings, + adminzones, + what="mult", # new attribute to summarise + how="mean", + verbose=F) +``` + +### Additional built-in functions + +The internal `foot` functions are documented in `?fs_functions`; however, these +functions are intended to be used within the wrapper functions of `foot` and are +rarely intended to be used as standalone functions. One built-in summary +function, not applied by default, is `majority`. It is designed to summarise +categorical data. This function is available for users in the same manner of +specifying the `how` argument. + +```{r, c6, tidy=FALSE} +# get the majority category in each zone +calculate_footstats(buildings, + adminzones, + what="category", + how="majority", + verbose=F) +``` + +The `majority` function is similar to the idea of supplying a user-defined +function which is demonstrated in the next section. + +## User-defined summary functions + +Creating functions for use with `foot` follows the same procedures and syntax +for functions in `R` in general. They must be declared with `<- function()` and +they must be available within the environment where `foot` functions are being +used. When the functions are used internally by `calculate_footstats`, they are +applied to footprints by zone index. Therefore they should return a single, +summary value since the function for that group of footprints in the zone. + +The name of the function is what is passed to `foot` as an argument to `how`. +The argument(s) to the custom function can be named anything, but they will +typically be values present within the footprint attributes to be summarised. + +The example below shows a simple function that calculates the sum of the square +root of the values. We will apply it to 'area', and `foot` will automatically +pre-calculate this characteristic since it is not present in the column names of +the footprints. + +```{r, c7, tidy=FALSE} +# simple function example 1 +f1 <- function(v){ + units(v) <- NULL # ignore units in our function + return(sum(sqrt(v))) +} + +# applying custom summary function to area +calculate_footstats(buildings, + adminzones, + what="area", how="f1", + verbose=F) +``` + +Although this function was just used to process area, the function can be used +for any continuous value. It can also be used on multiple characteristics or +combined with other lists of functions, just like any other built-in function in +`foot`. + +```{r, c8, tidy=FALSE} +calculate_footstats(buildings, + adminzones, + what=list("area","perimeter"), how="f1", + verbose=F) +``` + +## Functions with multiple arguments + +In some instances, a custom function may need to make use of two or more +characteristics from within the building footprint datasets. The built-in +functions in `foot` are primarily designed to work with a single value (e.g. +area or perimeter). + +While it may sometimes be quicker to pre-calculate the combination, it could be +advantageous to use a function, particularly in `calculate_bigfoot` where +smaller subsets of a large building footprint dataset are processed. To make +sure multiple attributes are supplied to the summary function, the arguments in +`what` should be specified using a special type (`fs_varlist`). The `fs_varlist` +creates a nested list within the internal processing to keep the argument +together. Keep in mind that the arguments are passed to the summary function by +**position**, not be name, so the order within `fs_varlist` must match the order +of parameters that the function is expecting. + +### Creating a Perimeter/Area ratio + +An example of a custom function using two characteristics is the average +perimeter-area ratio. We can compare this to the built-in function which uses a +Polsby-Popper metric (`fs_compact`). + +```{r, c9, tidy=FALSE} +# average perimeter-area ratio +pa <- function(p, a){ + return(mean(p / a)) +} + +# used to summarise within zones +# note that fs_varlist is still within a list +calculate_footstats(buildings, + adminzones, + what=list(list("compact"), fs_varlist("area","perimeter")), + how=list(list("mean"), list("pa")), + verbose=T + ) +``` + +## Accessing `R` objects other than the footprints + +A more complicated scenario exists when a user-defined function needs to access +data which is not an attribute of the footprints dataset. In order to access the +non-footprint data, a partial function must be created first and then supplied +to calculation function. + +In the example below, a simple constant value is supplied to a summary function; +however, the idea extends to any object in the `R` environment. This process is +how the nearest neighbour index is calculated in `foot` by drawing on the +spatial zones object as well as the footprints. + +```{r, c10, tidy=F} +# external "data" +d1 <- 0.001 + +# This will NOT work because argument 'd' is not found +# f2 <- function(x, d){ +# return(sum(d * x)) +# } +# +# calculate_footstats(buildings, adminzones, what="area", how="f2", verbose=T) + +# Instead... +# example of creating a partial function +gen_f3 <- function(d){ + force(d) # must include + function(x){ + return(sum(d * x)) + } +} + +# generate the function and initialise it with `d1` from above. +f3 <- gen_f3(d1) + +# this now uses the generated function, and `d` is found +calculate_footstats(buildings, + adminzones, + what="area", + how="f3", + verbose=F + ) +``` + +In this vignette, the `foot` package has been extended to incorporate +user-defined functions. These functions can use one or more values from within +the footprints, or even access other objects in the environment. While the +examples used `calculate_footstats`, the same approaches can be used to create +new gridded summary metrics with `calculate_bigfoot`. + +*** +```{r, c11} +sessionInfo() +``` diff --git a/vignettes/compact_mean.tif b/vignettes/compact_mean.tif new file mode 100644 index 0000000..da4e64d Binary files /dev/null and b/vignettes/compact_mean.tif differ diff --git a/vignettes/figure/Fig1-1.png b/vignettes/figure/Fig1-1.png deleted file mode 100644 index 4674c75..0000000 Binary files a/vignettes/figure/Fig1-1.png and /dev/null differ diff --git a/vignettes/figure/Fig2-1.png b/vignettes/figure/Fig2-1.png deleted file mode 100644 index 29d7433..0000000 Binary files a/vignettes/figure/Fig2-1.png and /dev/null differ diff --git a/vignettes/figure/Fig3-1.png b/vignettes/figure/Fig3-1.png deleted file mode 100644 index 61732bc..0000000 Binary files a/vignettes/figure/Fig3-1.png and /dev/null differ diff --git a/vignettes/figure/Fig5-1.png b/vignettes/figure/Fig5-1.png deleted file mode 100644 index 98c0f35..0000000 Binary files a/vignettes/figure/Fig5-1.png and /dev/null differ diff --git a/vignettes/footsteps.Rmd b/vignettes/footsteps.Rmd index f19ca2a..f95270b 100644 --- a/vignettes/footsteps.Rmd +++ b/vignettes/footsteps.Rmd @@ -14,7 +14,7 @@ vignette: > %\VignetteEncoding{UTF-8} --- -```{r, include = FALSE} +```{r, fs1, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", @@ -23,36 +23,25 @@ knitr::opts_chunk$set( ) ``` -While remote sensing has long been used to monitor urbanisation patterns, in -recent years there has been an increasing availability in finer resolution -satellite data covering large areas of the globe. This very high resolution -imagery (often <1 m spatial resolution), combined with increased computing power -is producing new datasets on urban areas. In particular, machine learning -algorithms are being applied to detect, automatically extract, and map full sets -of building features in a scene. These automated methods add to the manually -digitised information such as from [OpenStreetMap](http://www.openstreetmap.org) -and the property datasets available from some city governments. - -Such building footprint datasets provide a new level of detail on urban areas, -particularly in places which might otherwise lack detailed maps on cities and -rapidly growing areas. Despite their spatial detail, building footprints -typically lack any other attribute information to help differentiate land uses -or other neighbourhood characteristics. However, the size, shape, orientation, -and clustering of structures produces a spatial pattern that can suggest areas -of different land use or activities. +The `foot` package was developed by WorldPop at the University of Southampton +([www.worldpop.org](https://www.worldpop.org)) to provide a set of consistent +and flexible tools for processing 2D vector representations of buildings (e.g. +"footprints") and calculating urban morphology measurements. The functionality +includes basic geometry and morphology characteristics, distance and clustering +metrics. These calculations are supported with helper functions for spatial +intersections and tiled reading/writing of data. ```{r setup} library(foot) ``` -The `foot` package was developed by WorldPop at the University of Southampton -([www.worldpop.org](https://www.worldpop.org)) to support geometric calculations -and zonal summaries of measures from building footprint polygons. This vignette -will demonstrate some of the core functionality in the package, including: +This vignette will demonstrate some of the core functionality in the package, +including: * The available measurements and summary statistics -* How to define different types of spatial zones for summaries -* Example workflows to produce tabular and gridded output +* How to define different types of spatial zones for area-level summaries +* Calculating multiple summary metrics for a set of spatial areas +* Example workflows to produce outputs using `foot::calculate_footstats()`. To demonstrate the package, this vignette will use a supplied sample of building footprint polygons produced by Microsoft Bing Maps @@ -61,8 +50,9 @@ from an area in Kampala, Uganda. These footprints have been reprocessed into a spatial data format which can be read with `sf`. ## Load the sample dataset -```{r} -data("kampala") + +```{r, fs3} +data("kampala", package="foot") buildings <- kampala$buildings adminzones <- kampala$adminZones @@ -83,7 +73,7 @@ created for demonstration purposes only. For more information, see `?kampala`. -```{r echo=FALSE, fig.cap="Sample buildings and zones in Kampala", fig.height=6, fig.width=6, message=FALSE, warning=FALSE} +```{r, fsFig1, echo=FALSE, fig.cap="Sample buildings and zones in Kampala", fig.height=6, fig.width=6, message=FALSE, warning=FALSE} plot(sf::st_geometry(buildings), col=sf::sf.colors(12, categorical=TRUE), border=NA, axes=TRUE) @@ -92,196 +82,220 @@ plot(sf::st_geometry(adminzones), border="red", add=T) ``` ## Calculations with `foot` -All function names for calculating metrics begin with `fs_` (short for -"footprint statistic"). The second part of the name includes the group of -measures (e.g. area or perimeter), and finally an abbreviation for the summary -statistic (e.g. 'cv' for coefficient of variation or 'sd' for standard -deviation). -### Available metrics and summary measures -Currently implemented are measures for: +The core functions of `foot` are provided by `calculate_footstats()`. The +operations include: 1) calculating geometry measures for each footprint, 2) +summarising one or more geometry measures of footprints within zones. The +simplest usage involves supplying a set of building footprint polygons and the +desired characteristics to calculate. All operations return the same format - a +`data.table`, optionally with a column for the index and the named column for +the summarised footprint metric(s). -* presence (binary and count) -* area -* perimeter -* Euclidean nearest neighbour distance -* nearest neighbour index -* angle of rotation -* compactness and shape indices +```{r, fs5, message=FALSE, warning=FALSE} +# the area and perimeter of each building footprint +built_area <- calculate_footstats(buildings, what=list("area","perimeter")) + + head(built_area) +``` -A table of metrics is available in the package `foot::fs_footprint_metrics`. -Helper functions are also available to look up function names, groupings, and -units. See `?get_fs_metrics`. +To summarise the footprint characteristics over all building polygons, the name +of a summary function, or a list of multiple functions can be supplied. +```{r, fs6} +# the average and standard deviation of all building areas +calculate_footstats(buildings, what="area", how=list("mean","sd")) +``` -## Basic usage -The simplest usage of `foot` involves supplying a set of building footprints to -the desired metric and summary function. +### Available metrics and summary measures -```{r} -# mean of all footprint areas -built_area <- fs_area_mean(buildings) +Currently implemented are measures for: -built_area +* building presence +* area +* perimeter +* nearest neighbour distance +* angle of rotation +* compactness and shape -# standard deviation of perimeters -perim_sd <- fs_perim_sd(buildings) +A table of metrics and other packaged summary function names available for each +is available using `list_fs()`. The results of this function provide "cols" and +"funs" which can be passed as `what` and `how` arguments, respectively, to +`calculate_footstats`. -perim_sd +```{r, fs7} +# get all available built-in functions for perimeter calculations +argsList <- list_fs(what="perimeter") -# median of areas -area_med <- fs_area_median(buildings, unit="m^2") -area_med +calculate_footstats(buildings, what=argsList$cols, how=argsList$funs) ``` With no other argument supplied, all the footprints are treated as coming from -the same zone for the summary. Default unit values are used for geometry -measurements, unless otherwise specified (see third example). All functions -return the same format - a `data.table` with a column for the index and the -named column for the summarised footprint metric. - -There is also an option to supply pre-calculated column to each function with -the `col=` argument. If a pre-calculated geometry value exists, that field can -be used or, if it is absent, the appropriate geometries needed for the metric -will be calculated. - -All functions follow the same parameter template, and, because the first -argument is always data, all the functions allow for "piping" syntax. - -```{r, tidy=F} -# random sample of buildings -result <- buildings %>% - dplyr::filter(FID_1 %in% sample(FID_1, 1000)) %>% - fs_area_mean() - -# show the resulting table -result -``` +the same spatial zone for any summary function. A later section describes the +process for identifying zones. + +## Additional characteristics and geometry measures -Most low-level geometry calculations rely on `sf` and `lwgeom` and users need to -have recent versions of these packages installed. +Whenever possible, `foot` makes use of generic functions within `R`. Most +low-level geometry calculations rely on `sf` and `lwgeom` and users need to have +recent versions of these packages installed. There are other stand-alone +functions within `foot` to support more complex or less-common measurements. ### Nearest neighbour distances -Distances can be calculated between footprints (represented as points or as -polygons), between footprints and other objects, and/or within a maximum search -distance. -```{r} +Distances can be calculated between footprints, or between footprints and other +spatial objects. The distances can be measured edge-to-edge (`method="poly"`) or +the centroids of the building footprints can be used (`method="centroid"`). + +```{r, fs8} # nearest distance for the first 10 buildings to any other building # measured between polygon edges fs_nndist(buildings[1:10,], buildings, maxSearch=200, unit="m") -# omitting 'Y' measures distance among the footprints supplied -fs_nndist(buildings[1:10,], maxSearch=NULL) # unrestricted distance +# omitting argument 'Y' measures distance among the footprints supplied +# setting maxSearch=NULL removes the search restriction +fs_nndist(buildings[1:10,], method="centroid", maxSearch=NULL) ``` Note that distance calculations are slower for polygons and for unprojected -coordinate systems. It is recommended that a maximum search radius is always -used. Internally the calculations are done with a `data.table` to benefit from -multithreading capabilities. +coordinate systems. The centroid-based calculations are fast. It is recommended +that a maximum search radius is always used. Internally the calculations are +done with a `data.table` to benefit from multi-threading capabilities. ### Rotation angles + A less conventional geometric measure is derived from the rotated bounding -rectangle. This is the rectangle enclosing a polygon (or set of points) which -has been rotated to minimise the area. In contrast, a "bounding box" for a shape -is always oriented along the x and y axes. +rectangle. This is the rectangle enclosing a footprint polygon which has been +rotated to minimise the area. In contrast, a "bounding box" for a polygon is +always oriented along the x and y axes. -```{r} +```{r, fsFig2} # To obtain the rotated rectangle as a spatial object -mbr <- fs_mbr(buildings[5,], returnShape=T) +mbr <- fs_mbr(buildings[4502,], returnShape=T) -plot(sf::st_geometry(buildings[5,])) -plot(mbr, border="red", add=T) +plot(sf::st_geometry(buildings[4502,]), col="grey") +plot(mbr, border="red", lwd=2, add=T) -# Or obtain just the angle -fs_mbr(buildings[5,], returnShape=F) +# Or obtain just the angle measure +fs_mbr(buildings[4502,], returnShape=F) ``` The angles can be summarised as an entropy measure and normalised to describe -how much the angles of structures depart from a regular grid pattern (available -in `fs_angle_entropy()`). - +how much the angles of a set of structures depart from a regular grid pattern +(available in `calculate_footstats` where `how="entropy"`). ## Creating and supplying zone indices -Rather than treating all features as a single summary zone, it's more common to -want to summarise metrics within smaller areas. There are several ways to supply -information on the zones. + +Rather than treating all footprints features as belonging to a single summary +zone, it's more common to want to summarise metrics within smaller areas. There +are several ways to supply information on the zones. ### Index by vector -A vector of indices for a zone can be supplied to the functions as a column -name, column number, or passed as a standalone vector object. The length of a + +A vector of indices for the zones can be supplied to `foot` functions as a 1) +column name within the footprints, 2) a standalone numeric vector of indices, or +3) a spatial polygons object to join to the building footprints. The length of a vector of indices must match the number of building polygons. -```{r} +```{r, fs10} # create a vector of ten random zones -id <- sample(1:10, nrow(buildings), replace=T) -buildings$id <- id # add it to the buildings object +idx <- sample(1:10, nrow(buildings), replace=T) +buildings$id <- idx # add it to the buildings object table(buildings$id) # splitting observations into 10 groups -# pass the index by name -fs_area_total(buildings, index="id") # note default units +# 1. pass the index by name +colnames(buildings) +calculate_footstats(buildings, "id", what="area", how="mean", verbose=FALSE) -# pass the index by column number -fs_count(buildings, index=3) +# 2. pass the index as a standalone vector +calculate_footstats(buildings, idx, what="settled", how="count", verbose=FALSE) -# pass a separate vector object -print(fs_angle_entropy(buildings, index=id, normalize=T)) +# 3. pass a separate spatial object of zones +calculate_footstats(buildings, zone=adminzones, what="angle", how="entropy", verbose=FALSE) ``` -### Index by shape +### Index by zone shapes + Rather than supplying a pre-calculated column or vector of zonal indices, -buildings can be assigned a zone based on a spatial join. Creating an index from -a spatial object returns a field called "zoneID" which other calculation -functions in `foot` can use. +buildings can be assigned a zone based on a spatial join. When the index is +created in the building footprints, it will be named "zoneID" or a +user-specified name. + +```{r, fs11, message=FALSE, warning=FALSE} +# examine the other objects loaded from supplied data +head(adminzones) +head(clusters) -```{r message=FALSE, warning=FALSE} # Return a table of index values based on administrative zone polygons +# using the standalone function within `foot` zID <- zonalIndex(buildings, adminzones, returnObject=F) - zID # the xID column are row numbers + head(zID, 10) # the xID column are row numbers to object X -# Alternatively, join the zones to create and return a new spatial object -# A custom zone name can be added but must be supplied to the summary functions +# Alternatively (and preferably), join zones to create a new footprint object +# A custom zone name can be used but must be specificed to the summary functions zObj <- zonalIndex(buildings, clusters, zoneField="Id", returnObject=T) zObj ``` When using a new spatial object which has been joined to its zones, remember to -supply the name of the zone field if a custom name was used. -```{r fig.height=6, fig.width=6} -# use the new object -zarea <- fs_area_mean(zObj, index="Id") -clusters <- merge(clusters, zarea, by.x="Id", by.y="index") - plot(clusters["fs_area_ha_mean"]) +supply the name of the zone field to `calculate_foostats`. + +```{r, fsFig3, fig.height=6, fig.width=6} +# use the new object and zone field "Id" in a summary calculation +colnames(zObj) + +zarea <- calculate_footstats(zObj, zone="Id", what="area", how="mean", verbose=F) +clusters <- merge(clusters, zarea, by="Id") + plot(sf::st_geometry(adminzones)) + plot(clusters["area_mean"], add=T) + ``` The `zonalIndex` function works by spatial intersection. This produces some -(potentially useful) side effects that users need to be aware of. +(potentially useful) side effects that users need to be aware of. Specifically, +note that if a building footprint intersects more than 1 zone it will be +duplicated and associated to all intersecting zones. -The default behaviour is for any intersecting structure to be included in the zone. -```{r warning=FALSE} +The default behaviour (see `method`) is to assign a building to a zone based on +its **centroid**. +```{r, fsFig4, warning=FALSE} # Note the selected structures extend beyond the cluster boundary plot(sf::st_geometry(clusters)[[6]]) plot(sf::st_geometry(buildings), add=T) plot(sf::st_geometry(zObj[zObj$Id==6,]), col="red", add=T) +plot(sf::st_geometry(sf::st_centroid(zObj[zObj$Id==6,])), pch=16, add=T) +``` + +Alternatively, an intersection can be used to assign footprints to any zones +which are intersected. The whole footprint is associated, even if the shape is +not "contained" by the zone. +```{r, fsFig5, warning=FALSE} +# Note the selected structures extend beyond the cluster boundary +zInt <- zonalIndex(buildings, clusters, zoneField="Id", method="intersect") + +plot(sf::st_geometry(clusters)[[6]]) +plot(sf::st_geometry(buildings), add=T) +plot(sf::st_geometry(zInt[zInt$Id==6,]), col="red", add=T) ``` -Alternatively, the intersection can return a clipped set of buildings. -```{r message=FALSE, warning=FALSE} -zClip <- zonalIndex(buildings, clusters, zoneField="Id", clip=T) +Finally, the intersection can return a clipped set of buildings. +```{r, fsFig6, message=FALSE, warning=FALSE} +zClip <- zonalIndex(buildings, clusters, zoneField="Id", method="clip") plot(sf::st_geometry(clusters)[[6]]) plot(sf::st_geometry(buildings), add=T) plot(sf::st_geometry(zClip[zClip$Id==6,]), col="red", add=T) ``` -This option clips the footprints via intersection, potentially leaving small -slivers of structures in the zone which will affect the feature statistics. +This third option clips the footprints via intersection, potentially leaving +small slivers of structures in the zone which will affect the feature +statistics. -An additional side effect of the intersection is that overlapping zones are -allowed and this duplicates and associates the footprints into both zones. +An additional side effect of the intersection operation is that overlapping +zones are allowed, and this can duplicate and associate footprints into both +(overlapping) zones. -```{r message=FALSE, warning=FALSE} +```{r, fsFig7, message=FALSE, warning=FALSE} # create a temporary shape by shifting one cluster newClusters <- st_sfc(sf::st_geometry(clusters)[[1]], sf::st_cast(sf::st_geometry(clusters)[[1]] + c(.001,.0001), "POLYGON"), @@ -290,7 +304,7 @@ newClusters <- st_sfc(sf::st_geometry(clusters)[[1]], newClusters <- st_sf(geometry=newClusters, crs=sf::st_crs(clusters)) -newObj <- zonalIndex(buildings, newClusters, clip=T) +newObj <- zonalIndex(buildings, newClusters, method="clip") # areas of overlap are in a purple hue plot(sf::st_geometry(newClusters)) @@ -303,146 +317,84 @@ These side effects are allowed because they allow for flexibility to support types of "focal" summaries of statistics and to produce a true gridded measure of footprint metrics. - ## Calculating multiple metrics + The `calculate_footstats()` function provides a convenient wrapper to the -individual footprint statistic functions and other helper functions. The -function accepts a variety of input formats (see `?calculate_footstats`). -Multiple metrics can be calculated for the same sets of buildings and zones. +individual footprint statistics and as well to `zonalIndex`. The function +accepts a variety of input formats (see `?calculate_footstats`). Multiple +metrics can be calculated for the same sets of buildings and zones. -```{r, tidy=F} -# Creates zonal index and calculates multiple metrics +```{r, fs17, tidy=F} +# Creates a zonal index and calculates multiple metrics +# Use the intersection method define zones results <- calculate_footstats(buildings, - adminzones, - metrics=c("fs_area_mean","fs_area_cv"), - gridded=F) - print(results) + zone=adminzones, + what="area", + how=list("mean","cv"), + controlZone=list(method="intersect"), + verbose=F + ) + results ``` -Note that this function uses the default arguments and units for creating zones -and calculating metrics. For the requested metrics the standard deviation is -required for the CV calculation. This dependency is added and handled -internally by `calculate_footstats`. Also note that one zone has only one -structure in it, so NA values are returned for the CV. - -The argument `metrics` will accept a string or a vector of strings for specific -metric names. Users may also supply `"ALL"` or `"NODIST"` to calculate all -available metrics or all bar the nearest neighbour distance-related ones, -respectively. - - -## Gridded outputs -Often the desired output is a gridded dataset of a measure, rather than -summarising metrics to polygon zones. A gridded dataset can be achieved with the -`foot` functions in several ways. - -(@) The simplest method is to attach buildings (and summary measures) to grid -cells by their centroid point location. A fast approach is to use cell numbers -as a zonal index. -```{r, fig.width=6, fig.height=6, tidy=F} -mgrid <- kampala$mastergrid # ~100m raster layer - -# get the cell numbers from the grid -cellIDs <- raster::cellFromXY(mgrid, - sf::st_coordinates(sf::st_centroid(buildings))) -# use these numbers as the zone -gridRes <- calculate_footstats(buildings, - index=cellIDs, - metrics=c("fs_perim_total", - "fs_area_total", - "fs_count", - "fs_settled"), - gridded=F) -# buildings lacking a grid cell are NA -gridRes <- na.omit(gridRes) - gridRes - -# create a blank raster and fill with summary measures -outSettled <- mgrid -outSettled[] <- NA -# `$index` are the cell numbers from the raster -outSettled[gridRes$index] <- gridRes$fs_area_ha_total - -outCount <- mgrid -outCount[] <- NA -outCount[gridRes$index] <- gridRes$fs_count - -raster::plot(outCount) -plot(sf::st_geometry(buildings), add=T) -``` - -(@) Pixel-level calculation. While centroid-based calculations are fast, -sometimes it may be necessary to know the true amount of settlement *per pixel*. -The zonal index functions can be used to intersect buildings to grid squares, -and then supply those "pixels" as zones to the feature calculations as shown. -```{r message=FALSE, warning=FALSE, tidy=F} -# convert the grid to a `stars` object and then `sf` to create polygons from the grid cells -sgrid <- stars::st_as_stars(mgrid) -sgrid <- sf::st_as_sf(sgrid) - -# add a cell number ID to the grid polygons -sgrid$cellIDs <- raster::cellFromXY(mgrid, sf::st_coordinates(sf::st_centroid(sgrid))) - -# create a zonal index on the building objects -pxZone <- zonalIndex(buildings, - sgrid, - "cellIDs", - returnObject=T, - clip=T) - -pxResult <- calculate_footstats(pxZone, - index="cellIDs", - metrics=c("fs_area_total", "fs_settled")) - print(pxResult) -``` - -As before, the metrics are linked to the grid by their cell numbers. The results -can also be merged to the `stars` or `sf` representation of the grid and quickly -rasterized based on geometry. The difference between using centroids or pixels -is most visible with large structures. -```{r, fig.cap="Centroid-based (left) and a pixel-based (right) map of total building area", fig.show='hold', fig.width=6} -# create output -pxSettled <- mgrid -pxSettled[] <- NA -pxSettled[pxResult$index] <- pxResult$fs_area_ha_total - -# compare centroid and pixel settled maps -par(mfrow=c(1,2)) -raster::plot(outSettled, zlim=c(0,1), xlim=c(32.623, 32.626), ylim=c(0.338, 0.342)) -plot(sf::st_geometry(buildings), add=T) -raster::plot(pxSettled, zlim=c(0,1), xlim=c(32.623, 32.626), ylim=c(0.338, 0.342)) -plot(sf::st_geometry(buildings), add=T) +Multiple metrics can be applied to specific groups of characteristics by +providing nested lists of metrics and summary functions. The argument `what` +will accept a string or a list of strings for specific metric names. Users may +also supply `"all"` or `"nodist"` to calculate all available metrics or all bar +the nearest neighbour distance-related ones, respectively. Excluding the +distance-related metrics can speed up the calculations due to the long-running. +Other performance improvements can be to set +`controlDistance=list("method"="centroid")`, which uses centroid-to-centroid +nearest neighbour distances rather than polygon edge-to-edge. See also, +`?fs_nndist`. + +```{r, fs18, tidy=F} +# Use nested lists to group characteristics and summary functions +results <- calculate_footstats(buildings, + zone=adminzones, + what=list(list("area","perimeter"), + list("settled")), + how=list(list("sum","cv"), + list("count")), + controlZone=list(method="centroid"), + verbose=F + ) + results ``` -(@) Write gridded data to GeoTiff files. Using `calculate_footstats` a template -raster and an output location can be specified. The template can be a `raster` -or `stars` object. Each metric will be written to a separate grid. This approach -builds on using an `sf` object to define the summary zone directly in the call -to the wrapper function. -```{r, fig.width=6, fig.height=6, tidy=F} -outputPath <- tempdir() - -# re-using the sf pixels as "zones" -calculate_footstats(buildings, - index=sgrid, - metrics=c("fs_area_mean", "fs_settled"), - gridded=TRUE, - template=mgrid, # using a rasterlayer - outputPath=outputPath # or a temp directory is created - ) - -# get the output -raster::plot(raster::raster(paste0(outputPath, "/fs_area_ha_mean.tif"))) -plot(st_geometry(buildings), add=T) +### Filtering buildings + +In some settings it may be preferable to exclude very small and/or very large +building footprint polygons. The lower and upper bounds for filtering can be +specified with `minArea` and `maxArea` in the `filter` argument. The values for +these filters are in the same units specified by `controlUnits` or the default +value for area calculations. Note that an "area" footprint statistic does not +need to be requested as this characteristic is automatically calculated to +enable filtering. + +```{r, fs19, tidy=FALSE} +# Filtering: # footprints must be larger than 50 m^2 and smaller than 1000 m^2 +calculate_footstats(buildings, + what="perimeter", + how=list("mean", "sum"), + controlUnits=list(areaUnit="m^2"), + filter=list(minArea=50, maxArea=1000), + verbose=FALSE) ``` -The above approaches work fine for small study areas, but may be too slow over -large areas and with many footprint features. Producing country-scale gridded -datasets with tools from `foot` is addressed in the second vignette ("bigfoot"). +## Next steps +The `calculate_footstats` function provides the core functionality for +calculating and summarising the characteristics of building footprints. It also +wraps the functionality of assigning footprints to zones based on different +spatial joining techniques. To go further with `foot` the concept of footprint +morphology calculations can be extended to created gridded data. See +`vignette("bigfoot", package="foot")`. Additionally, users can specify their own +custom summary functions to be used with `calculate_footstats`. This and other +advanced options are covered in `vignette("cobbler", package="foot")`. *** -```{r} +```{r, fs20} sessionInfo() ``` diff --git a/vignettes/settled_binary.tif b/vignettes/settled_binary.tif new file mode 100644 index 0000000..6022ec5 Binary files /dev/null and b/vignettes/settled_binary.tif differ diff --git a/wd/bigfoot.html b/wd/bigfoot.html new file mode 100644 index 0000000..ea9f11f --- /dev/null +++ b/wd/bigfoot.html @@ -0,0 +1,778 @@ + + + + + + + + + + + + + + +Gridded footprint calculation layers + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +

    The foot package was developed by WorldPop at the University of Southampton (www.worldpop.org) to support geometric calculations and zonal summaries of measures from building footprint polygons. The vignette("footsteps", package="foot") provides an introduction to package and the functionality of calculating and summarising morphology measures. This vignette builds on those methods and demonstrates a more advanced workflow to produce gridded summaries of buildings measures. In particular, the focus is on using foot::calculate_bigfoot() in order how to handle very large (i.e.  national-scale) datasets of building footprints.

    +
    library(foot)
    +
    +

    Calculations with foot

    +

    The central function for producing gridded layers is calculate_bigfoot. It is designed to support processing large sets of building polygons into gridded (GeoTiff) summary layers. The procedure is intended to be more memory-efficient by splitting the processing area into “tiles” which can then be handled individually or in parallel. This function works as a wrapper to calculate_footstats as well as several helper functions within the foot package for managing the input/output and creating and indexing spatial zones. Because it extends calculate_footstats, this function takes the same what= and how= arguments to define the summary operations. Likewise the available metrics are available from list_fs().

    +

    By default the function performs calculations in parallel (which can be changed with the argument parallel=FALSE). Users can adjust the number of computing cores used with the argument nCores=) To monitor the supplied values and processing steps, set verbose=TRUE.

    +
    +

    Main inputs

    +

    Users need to supply calculate_bigfoot() with:

    +
      +
    • A path to a file of building footprints in a spatial vector data format (e.g. .gpkg, .shp)
    • +
    • A filepath to a template gridded dataset specifying the extent and resolution for the outputs (e.g. .tif)
    • +
    +

    For example:

    +

    While R objects can be supplied for the function parameters (as will be shown in this example), it is recommended to supply character strings of paths to the files. calculate_bigfoot will only read the subset of data needed for a particular processing step. This is intended to allow users to process much larger datasets than can be held in memory.

    +
    +
    +

    Basic calculations

    +

    With the key inputs for the file paths set, the processing can be executed with a single function.

    +
    # basic function call with default parameters
    +calculate_bigfoot(X=buildings,
    +                  template=mgrid,
    +                  what="settled", how="binary",
    +                  verbose=TRUE)
    +#> trying to read file: C:\Users\Admin\Documents\GitHub\foot\wd\in\kampala_grid.tif
    +#> Selecting metrics 
    +#> Setting control values. 
    +#> Creating template output grids 
    +#> Creating list of processing tiles 
    +#> Setting up cluster...
    +#> Begin parallel tile processing: 2020-10-23 12:38:01
    +#> 
    +#> Finished processing all tiles: 2020-10-23 12:38:03
    +

    The documentation and default parameters are available from ?foot::calculate_bigfoot.

    +

    The arguments what and how are the same as for calculate_footstats. They provide a character or list list of character names or characteristics (i.e. columns within the building footprint attributes) and summary functions, respectively. The available metrics can be retrieved with list_fs().

    +
    +
    +

    Specifying outputs

    +

    By default the outputs of bigfoot are saved as GeoTiffs in the user’s working directory (getwd()). Each grid is named by the combination of characteristics and summary function. The example below shows how to retrieve an output of calculate_bigfoot.

    +
    # retrieve the gridded outputs
    +outGrid <- raster::raster(file.path(getwd(), "settled_binary.tif"))
    +
    +raster::plot(outGrid)
    +
    +Binary settled raster at 100m resolution +

    +Binary settled raster at 100m resolution +

    +
    +

    Users can specify an output path to another folder location (outputPath=). Additionally a “tag” can be specified as a parameter to the function (outputTag=). The tag is appended to the beginning of each file name with an underscore. This can be useful for identifying different outputs. These options are demonstrated in the code block below.

    +
    # basic function call specifying output parameters
    +calculate_bigfoot(X=buildings,
    +                  template=mgrid,
    +                  what="settled", how="binary",
    +                  outputPath=tempdir(),
    +                  outputTag="TAG",
    +                  verbose=TRUE)
    +#> trying to read file: C:\Users\Admin\Documents\GitHub\foot\wd\in\kampala_grid.tif
    +#> Selecting metrics 
    +#> Setting control values. 
    +#> Creating template output grids 
    +#> Creating list of processing tiles 
    +#> Setting up cluster...
    +#> Begin parallel tile processing: 2020-10-23 12:38:04
    +#> 
    +#> Finished processing all tiles: 2020-10-23 12:38:06
    +
    +
    +

    Multiple metrics

    +

    As with calculate_footstats multiple metrics and summary statistics can be supplied to bigfoot as a list or nested lists grouping characteristics and functions.

    +
    # re-running the basic call with multiple metrics
    +calculate_bigfoot(X=buildings,
    +                  template=mgrid, 
    +                  what=list(list("settled"), list("area")),
    +                  how=list(list("binary"), list("cv")),
    +                  outputPath=tempdir(),  
    +                  outputTag="TAG")  
    +#> trying to read file: C:\Users\Admin\Documents\GitHub\foot\wd\in\kampala_grid.tif
    +#> Selecting metrics 
    +#> Setting control values. 
    +#> Creating template output grids 
    +#> Creating list of processing tiles 
    +#> Setting up cluster...
    +#> Begin parallel tile processing: 2020-10-23 12:38:06
    +#> 
    +#> Finished processing all tiles: 2020-10-23 12:38:10
    +
    # retrieve the gridded outputs Note: must add the 'tag' to the filename
    +outGrid <- raster::raster(file.path(tempdir(), "TAG_area_cv.tif"))
    +
    +raster::plot(outGrid)
    +plot(sf::st_geometry(buildings), fill = NA, add = T)
    +
    +Coeff. of variation in building area at 100m resolution +

    +Coeff. of variation in building area at 100m resolution +

    +
    +
    +
    +

    Focal window statistics

    +

    In the examples above, the footprint statistics are summarised for buildings whose centroid intersect the pixels of the template grid. Internally this is handled by zonalIndex(). The method for assigning footprints to spatial zones can be adjusted using controlZone options.

    +

    It is also possible to extend those zones beyond the individual pixels and to calculate and summarise building footprints from within a local, circular window centred on each pixel. The output is still associated with each template grid cell. This process is similar to a moving window or filter analysis. The focal radius distance is specified in meters.

    +A hypothetical example of using a 300 m focal radius around a pixel centroid to construct a local zone and using building centroids is shown below. +
    +Example of 300 m focal radius. Selected footprints are highlighted in red +

    +Example of 300 m focal radius. Selected footprints are highlighted in red +

    +
    +

    Below is an example of applying a 300 m focal radius in action for a gridded output layer.

    +
    # moving focal window calculations
    +calculate_bigfoot(X=buildings,
    +                  template=mgrid,
    +                  what=list(list("area"), list("perimeter")),
    +                  how=list(list("mean"), list("sum")),
    +                  focalRadius=300,  # buffer radius in meters
    +                  outputPath=tempdir(), 
    +                  outputTag="TAG")
    +#> trying to read file: C:\Users\Admin\Documents\GitHub\foot\wd\in\kampala_grid.tif
    +#> Selecting metrics 
    +#> Setting control values. 
    +#> Creating template output grids 
    +#> Creating list of processing tiles 
    +#> Setting up cluster...
    +#> Begin parallel tile processing: 2020-10-23 12:38:13
    +#> 
    +#> Finished processing all tiles: 2020-10-23 12:38:23
    +

    Creating the focal window is handled by calculate_bigfoot. The focal window is also taken into consideration by the function when creating processing tiles - the footprints are extract from outside the tile, in neighbouring areas, to prevent edge effects

    +

    Note that when a focal radius is specified, this value is always appended to the end of the file names so that the outputs can be identified.

    +
    # Note that the filename includes both a tag and a focal radius value
    +outGrid <- raster::raster(file.path(tempdir(), "TAG_perimeter_sum_300.tif"))
    +
    +raster::plot(outGrid)
    +
    +Total building perimeter in a 300m radius window. Output at a 100m resolution +

    +Total building perimeter in a 300m radius window. Output at a 100m resolution +

    +
    +
    +
    +
    +

    Options and finer control

    +

    The calculate_bigfoot function is set up with default values that should work under most conditions; however, there is additional flexibility for users to specify alternative parameters.

    +
    +

    Specifying geometry units

    +

    To override the default units used in the geometry calculations, a named list of unit strings can be supplied to the controlUnits argument. This list can contain named items for areaUnit, perimUnit, and distUnit. The default values are meters (“m”) and square meters (“m^2”) The value of each item should be coercible with units::as_units.

    +
    # change the default units used to calculate area and distance
    +calculate_bigfoot(X=buildings,
    +                  template=mgrid, 
    +                  what=list(list("area"), list("perimeter")),
    +                  how=list(list("mean"), list("sum")),
    +                  controlZone=list("method"="intersect"), # join by intersection
    +                  controlUnits=list(areaUnit="m^2",  # change default units
    +                                    perimUnit="km"),
    +                  outputPath=tempdir(),  
    +                  outputTag="TAG",
    +                  parallel=FALSE,
    +                  verbose=TRUE)  
    +#> trying to read file: C:\Users\Admin\Documents\GitHub\foot\wd\in\kampala_grid.tif
    +#> Selecting metrics 
    +#> Setting control values. 
    +#> Creating template output grids 
    +#> Creating list of processing tiles 
    +#> 
    +#> Tile: 1 of 1
    +#> Reading footprints 
    +#> Reading template grid 
    +#> Selecting metrics 
    +#> Setting control values. 
    +#> Creating zonal index 
    +#> Pre-calculating areas 
    +#> Pre-calculating perimeters 
    +#> 
    +#> Calculating  2  metrics ... 
    +#>    area mean  
    +#>    perimeter sum  
    +#> Finished calculating metrics. 
    +#> Writing output tiles 
    +#> Finished writing grids
    +#> 
    +#> Finished processing all tiles: 2020-10-23 12:38:26
    +
    +# plot the total perimeter, measured in kilometres
    +outGrid <- raster::raster(file.path(tempdir(), "TAG_perimeter_sum.tif"))
    +raster::plot(outGrid)
    +
    +Total building perimeter in KM +

    +Total building perimeter in KM +

    +
    +
    +
    +

    Filtering buildings

    +

    In some settings it may be preferable to exclude very small and/or very large building footprint polygons. The lower and upper bounds for filtering can be specified with minArea and maxArea in the filter argument. The values for these filters are in the same units specified by controlUnits or the default value for area calculations. Note that an “area” footprint statistic does not need to be requested as this characteristic is automatically calculated to enable filtering.

    +
    # Filtering: # footprints must be larger than 50 m^2 and smaller than 1000 m^2
    +calculate_bigfoot(X=buildings,
    +                  template=mgrid, 
    +                  what=list(list("shape"), list("settled"), list("perimeter")),
    +                  how=list(list("mean"), list("count"), list("sum")),
    +                  controlUnits=list(areaUnit="m^2"),
    +                  filter=list(minArea=50, maxArea=1000),
    +                  outputPath=tempdir(),  
    +                  parallel=FALSE,
    +                  verbose=TRUE)  
    +#> trying to read file: C:\Users\Admin\Documents\GitHub\foot\wd\in\kampala_grid.tif
    +#> Selecting metrics 
    +#> Setting control values. 
    +#> Creating template output grids 
    +#> Creating list of processing tiles 
    +#> 
    +#> Tile: 1 of 1
    +#> Reading footprints 
    +#> Reading template grid 
    +#> Selecting metrics 
    +#> Setting control values. 
    +#> Creating zonal index 
    +#> Pre-calculating areas 
    +#>  Filtering features larger than 50 
    +#>  Filtering features smaller than 1000 
    +#> Pre-calculating perimeters 
    +#> Pre-calculating shape 
    +#> 
    +#> Calculating  3  metrics ... 
    +#>    shape mean  
    +#>    settled count  
    +#>    perimeter sum  
    +#> Finished calculating metrics. 
    +#> Writing output tiles 
    +#> Finished writing grids
    +#> 
    +#> Finished processing all tiles: 2020-10-23 12:38:33
    +

    In the map of the results below, each pixel is the count of footprints present. Note the smaller number of structures and sparseness of structures in pixels around the centre portions of the image. This corresponds with the a business district and industrial areas with fewer, but larger structures.

    +
    outGrid <- raster::raster(file.path(tempdir(), "settled_count.tif"))
    +
    +raster::plot(outGrid)
    +
    +Count of buildings with area >50 m^2 and <1000 m^2 +

    +Count of buildings with area >50 m^2 and <1000 m^2 +

    +
    +
    +
    +

    Tile size

    +

    The size of the processing tiles, specified in pixel dimensions (rows, columns) can be an important factor in the efficiency of the calculations. Smaller tile regions result in fewer building footprints being read/processed at one time, but there is an overhead computational cost of reading/writing files. The default value is 500 pixels. For the small demonstration shown here that results in one tile for the whole region. To show multiple tile processing, a small size is supplied and the processing is done in parallel with verbose output.

    +
    calculate_bigfoot(X=buildings,
    +                  template=mgrid,
    +                  what="compact",
    +                  how="mean",
    +                  tileSize=c(20, 20),  # rows x columns in pixels
    +                  parallel=FALSE,
    +                  verbose=TRUE)
    +#> trying to read file: C:\Users\Admin\Documents\GitHub\foot\wd\in\kampala_grid.tif
    +#> Selecting metrics 
    +#> Setting control values. 
    +#> Creating template output grids 
    +#> Creating list of processing tiles 
    +#> 
    +#> Tile: 1 of 4
    +#> Reading footprints 
    +#> Reading template grid 
    +#> Selecting metrics 
    +#> Setting control values. 
    +#> Creating zonal index 
    +#> Pre-calculating compactness 
    +#> 
    +#> Calculating  1  metrics ... 
    +#>    compact mean  
    +#> Finished calculating metrics. 
    +#> Writing output tiles 
    +#> Finished writing grids
    +#> 
    +#> Tile: 2 of 4
    +#> Reading footprints 
    +#> Reading template grid 
    +#> Selecting metrics 
    +#> Setting control values. 
    +#> Creating zonal index 
    +#> Pre-calculating compactness 
    +#> 
    +#> Calculating  1  metrics ... 
    +#>    compact mean  
    +#> Finished calculating metrics. 
    +#> Writing output tiles 
    +#> Finished writing grids
    +#> 
    +#> Tile: 3 of 4
    +#> Reading footprints 
    +#> Reading template grid 
    +#> Selecting metrics 
    +#> Setting control values. 
    +#> Creating zonal index 
    +#> Pre-calculating compactness 
    +#> 
    +#> Calculating  1  metrics ... 
    +#>    compact mean  
    +#> Finished calculating metrics. 
    +#> Writing output tiles 
    +#> Finished writing grids
    +#> 
    +#> Tile: 4 of 4
    +#> Reading footprints 
    +#> Reading template grid 
    +#> Selecting metrics 
    +#> Setting control values. 
    +#> Creating zonal index 
    +#> Pre-calculating compactness 
    +#> 
    +#> Calculating  1  metrics ... 
    +#>    compact mean  
    +#> Finished calculating metrics. 
    +#> Writing output tiles 
    +#> Finished writing grids
    +#> 
    +#> Finished processing all tiles: 2020-10-23 12:38:39
    +
    +
    +
    +

    Next steps

    +

    This vignette has provided an overview of how to create gridded outputs layers summarising building footprint morphology measures. This workflow uses calculate_bigfoot and is designed to work for large sets of data through tiled read/writing and processing these tiles in parallel. The bigfoot functionality extends and makes use of footstats. Both of these functions can take use-supplied and custom summary functions. This advanced usage is demonstrated in vignette("cobbler", package="foot").

    +
    +
    sessionInfo()
    +#> R version 3.6.3 (2020-02-29)
    +#> Platform: x86_64-pc-linux-gnu (64-bit)
    +#> Running under: Ubuntu 20.04.1 LTS
    +#> 
    +#> Matrix products: default
    +#> BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.9.0
    +#> LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.9.0
    +#> 
    +#> locale:
    +#>  [1] LC_CTYPE=en_GB.UTF-8       LC_NUMERIC=C              
    +#>  [3] LC_TIME=en_GB.UTF-8        LC_COLLATE=en_GB.UTF-8    
    +#>  [5] LC_MONETARY=en_GB.UTF-8    LC_MESSAGES=en_GB.UTF-8   
    +#>  [7] LC_PAPER=en_GB.UTF-8       LC_NAME=C                 
    +#>  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
    +#> [11] LC_MEASUREMENT=en_GB.UTF-8 LC_IDENTIFICATION=C       
    +#> 
    +#> attached base packages:
    +#> [1] stats     graphics  grDevices utils     datasets  methods   base     
    +#> 
    +#> other attached packages:
    +#> [1] sf_0.9-6     raster_3.1-5 sp_1.4-1     foot_0.5    
    +#> 
    +#> loaded via a namespace (and not attached):
    +#>  [1] Rcpp_1.0.5         highr_0.8          pillar_1.4.3       compiler_3.6.3    
    +#>  [5] formatR_1.7        class_7.3-16       iterators_1.0.13   tools_3.6.3       
    +#>  [9] digest_0.6.25      lattice_0.20-40    evaluate_0.14      tibble_3.0.1      
    +#> [13] lifecycle_0.2.0    pkgconfig_2.0.3    rlang_0.4.8        foreach_1.5.1     
    +#> [17] DBI_1.1.0          filelock_1.0.2     rgdal_1.4-8        yaml_2.2.1        
    +#> [21] parallel_3.6.3     xfun_0.18          e1071_1.7-4        stringr_1.4.0     
    +#> [25] dplyr_0.8.5        knitr_1.30         vctrs_0.2.4        tidyselect_1.0.0  
    +#> [29] classInt_0.4-3     grid_3.6.3         glue_1.4.0         data.table_1.13.2 
    +#> [33] R6_2.4.1           rmarkdown_2.1      purrr_0.3.4        magrittr_1.5      
    +#> [37] stars_0.4-3        codetools_0.2-16   htmltools_0.4.0    ellipsis_0.3.0    
    +#> [41] units_0.6-7        abind_1.4-5        assertthat_0.2.1   KernSmooth_2.23-16
    +#> [45] stringi_1.4.6      doParallel_1.0.16  lwgeom_0.2-5       crayon_1.3.4
    +
    + + + + +
    + + + + + + + + + + + + + + + diff --git a/wd/cobbler.html b/wd/cobbler.html new file mode 100644 index 0000000..c2f4de8 --- /dev/null +++ b/wd/cobbler.html @@ -0,0 +1,796 @@ + + + + + + + + + + + + + + +Using custom summary functions in foot + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +

    The foot package was developed by WorldPop at the University of Southampton (www.worldpop.org) to support geometric calculations and summaries of measures from building footprint polygons. This vignette demonstrates how users can extend the basic functionality of calculate_footstats and calculate_bigfoot by making and supplying their own functions to summarise footprint characteristics. For an introduction to the package, see vignette("footsteps").

    +
    library(foot)
    +
    +

    Calculations with foot

    +

    As noted in the introductory vignettes, foot primarily uses calculate_footstats to calculate and summarise metrics. Internally this function uses data.table in order to handle large sets of building footprints and efficiently summarise them. The attributes to be summarised (what) are supplied to function names (how). These internal structures also allow for user-defined functions to be specified.

    +
    +

    Data preparation

    +

    To demonstrate using custom functions, we will first add some additional attribute “data” to the footprints which we will use.

    +
    data("kampala", package = "foot")
    +
    +buildings <- kampala$buildings
    +adminzones <- kampala$adminZones
    +
    +# Adding random data categorical variable
    +buildings$category <- sample(LETTERS[1:5], size = nrow(buildings), replace = T)
    +# continuous variable
    +buildings$mult <- sample(rnorm(nrow(buildings), mean = 10, sd = 2))
    +

    We can use any attributes of the footprints within calculate_footstats and calculate_bigfoot, not only the built-in morphology measures listed by list_fs(what='all').

    +
    # summarising a new data value
    +calculate_footstats(buildings,
    +                    adminzones,
    +                    what="mult", # new attribute to summarise
    +                    how="mean",
    +                    verbose=F)
    +#>     zoneID mult_mean
    +#>  1:      1  9.394396
    +#>  2:      2 10.237926
    +#>  3:      3 10.110397
    +#>  4:      4 10.117005
    +#>  5:      5  9.964926
    +#>  6:      6 10.318522
    +#>  7:      7 10.687371
    +#>  8:      8  9.972020
    +#>  9:      9 10.270748
    +#> 10:     10 10.354755
    +#> 11:     11  9.890088
    +#> 12:     12 10.170518
    +#> 13:     13 10.318211
    +#> 14:     14  9.796102
    +#> 15:     15 10.312584
    +#> 16:     16 10.008451
    +#> 17:     17 10.021033
    +#> 18:     18 10.015408
    +#> 19:     19 10.028203
    +#> 20:     20  9.989476
    +#> 21:     21 10.070256
    +#> 22:     22 10.192587
    +#> 23:     23  9.945407
    +#> 24:     24  9.838046
    +#> 25:     25 10.905053
    +#> 26:     26 10.115110
    +#> 27:     27  9.688971
    +#> 28:     29  9.904658
    +#> 29:     30 10.121832
    +#> 30:     31 11.806161
    +#> 31:     32 10.146562
    +#> 32:     33  9.411071
    +#> 33:     34 10.092890
    +#>     zoneID mult_mean
    +
    +
    +

    Additional built-in functions

    +

    The internal foot functions are documented in ?fs_functions; however, these functions are intended to be used within the wrapper functions of foot and are rarely intended to be used as standalone functions. One built-in summary function, not applied by default, is majority. It is designed to summarise categorical data. This function is available for users in the same manner of specifying the how argument.

    +
    # get the majority category in each zone
    +calculate_footstats(buildings, 
    +                    adminzones, 
    +                    what="category", 
    +                    how="majority", 
    +                    verbose=F)
    +#>     zoneID category_majority
    +#>  1:      1                 A
    +#>  2:      2                 C
    +#>  3:      3                 B
    +#>  4:      4                 B
    +#>  5:      5                 D
    +#>  6:      6                 D
    +#>  7:      7                 E
    +#>  8:      8                 A
    +#>  9:      9                 A
    +#> 10:     10                 C
    +#> 11:     11                 E
    +#> 12:     12                 E
    +#> 13:     13                 A
    +#> 14:     14                 B
    +#> 15:     15                 B
    +#> 16:     16                 B
    +#> 17:     17                 A
    +#> 18:     18                 C
    +#> 19:     19                 E
    +#> 20:     20                 A
    +#> 21:     21                 B
    +#> 22:     22                 A
    +#> 23:     23                 D
    +#> 24:     24                 B
    +#> 25:     25                 D
    +#> 26:     26                 E
    +#> 27:     27                 B
    +#> 28:     29                 B
    +#> 29:     30                 C
    +#> 30:     31                 C
    +#> 31:     32                 A
    +#> 32:     33                 C
    +#> 33:     34                 D
    +#>     zoneID category_majority
    +

    The majority function is similar to the idea of supplying a user-defined function which is demonstrated in the next section.

    +
    +
    +
    +

    User-defined summary functions

    +

    Creating functions for use with foot follows the same procedures and syntax for functions in R in general. They must be declared with <- function() and they must be available within the environment where foot functions are being used. When the functions are used internally by calculate_footstats, they are applied to footprints by zone index. Therefore they should return a single, summary value since the function for that group of footprints in the zone.

    +

    The name of the function is what is passed to foot as an argument to how. The argument(s) to the custom function can be named anything, but they will typically be values present within the footprint attributes to be summarised.

    +

    The example below shows a simple function that calculates the sum of the square root of the values. We will apply it to ‘area’, and foot will automatically pre-calculate this characteristic since it is not present in the column names of the footprints.

    +
    # simple function example 1
    +f1 <- function(v){
    +  units(v) <- NULL # ignore units in our function
    +  return(sum(sqrt(v)))
    +}
    +
    +# applying custom summary function to area
    +calculate_footstats(buildings,
    +                    adminzones,
    +                    what="area", how="f1",
    +                    verbose=F)
    +#> Linking to GEOS 3.8.0, GDAL 3.0.4, PROJ 6.3.1
    +#> WARNING: different compile-time and runtime versions for GEOS found:
    +#> Linked against: 3.8.0-CAPI-1.13.1  compiled against: 3.8.1-CAPI-1.13.3
    +#> It is probably a good idea to reinstall sf, and maybe rgeos and rgdal too
    +#>     zoneID    area_f1
    +#>  1:      1  364.18909
    +#>  2:      2 1823.23957
    +#>  3:      3  871.63637
    +#>  4:      4 1966.54994
    +#>  5:      5  867.92154
    +#>  6:      6  822.51869
    +#>  7:      7  485.66087
    +#>  8:      8 1194.46899
    +#>  9:      9 3048.19689
    +#> 10:     10  385.24272
    +#> 11:     11 1404.13211
    +#> 12:     12  294.77870
    +#> 13:     13   73.79837
    +#> 14:     14  730.25610
    +#> 15:     15 2014.66636
    +#> 16:     16 1154.35602
    +#> 17:     17 1133.49662
    +#> 18:     18 3472.56857
    +#> 19:     19 5905.48635
    +#> 20:     20 5262.66216
    +#> 21:     21  998.62900
    +#> 22:     22  722.37587
    +#> 23:     23 3441.00032
    +#> 24:     24 3882.17755
    +#> 25:     25  435.66830
    +#> 26:     26 3426.43477
    +#> 27:     27 2283.48433
    +#> 28:     29 1910.57791
    +#> 29:     30  989.77450
    +#> 30:     31   11.92210
    +#> 31:     32 1913.14204
    +#> 32:     33  309.58519
    +#> 33:     34 9839.29584
    +#>     zoneID    area_f1
    +

    Although this function was just used to process area, the function can be used for any continuous value. It can also be used on multiple characteristics or combined with other lists of functions, just like any other built-in function in foot.

    +
    calculate_footstats(buildings,
    +                    adminzones,
    +                    what=list("area","perimeter"), how="f1",
    +                    verbose=F)
    +#>     zoneID    area_f1 perimeter_f1
    +#>  1:      1  364.18909   183.488651
    +#>  2:      2 1823.23957  1048.879091
    +#>  3:      3  871.63637   426.068457
    +#>  4:      4 1966.54994   968.615609
    +#>  5:      5  867.92154   412.069737
    +#>  6:      6  822.51869   337.518569
    +#>  7:      7  485.66087   171.066602
    +#>  8:      8 1194.46899   515.464525
    +#>  9:      9 3048.19689  1911.775834
    +#> 10:     10  385.24272   226.579855
    +#> 11:     11 1404.13211   857.273283
    +#> 12:     12  294.77870   214.216882
    +#> 13:     13   73.79837    35.239420
    +#> 14:     14  730.25610   500.492421
    +#> 15:     15 2014.66636  1407.986177
    +#> 16:     16 1154.35602   835.883724
    +#> 17:     17 1133.49662   716.240827
    +#> 18:     18 3472.56857  2477.699320
    +#> 19:     19 5905.48635  3199.202606
    +#> 20:     20 5262.66216  2994.324208
    +#> 21:     21  998.62900   552.056838
    +#> 22:     22  722.37587   400.124081
    +#> 23:     23 3441.00032  1548.754569
    +#> 24:     24 3882.17755  2167.419552
    +#> 25:     25  435.66830   207.129346
    +#> 26:     26 3426.43477  1360.146927
    +#> 27:     27 2283.48433  1300.818322
    +#> 28:     29 1910.57791  1117.788513
    +#> 29:     30  989.77450   569.928890
    +#> 30:     31   11.92210     6.938055
    +#> 31:     32 1913.14204  1042.133260
    +#> 32:     33  309.58519   155.175160
    +#> 33:     34 9839.29584  6005.424628
    +#>     zoneID    area_f1 perimeter_f1
    +
    +
    +

    Functions with multiple arguments

    +

    In some instances, a custom function may need to make use of two or more characteristics from within the building footprint datasets. The built-in functions in foot are primarily designed to work with a single value (e.g. area or perimeter).

    +

    While it may sometimes be quicker to pre-calculate the combination, it could be advantageous to use a function, particularly in calculate_bigfoot where smaller subsets of a large building footprint dataset are processed. To make sure multiple attributes are supplied to the summary function, the arguments in what should be specified using a special type (fs_varlist). The fs_varlist creates a nested list within the internal processing to keep the argument together. Keep in mind that the arguments are passed to the summary function by position, not be name, so the order within fs_varlist must match the order of parameters that the function is expecting.

    +
    +

    Creating a Perimeter/Area ratio

    +

    An example of a custom function using two characteristics is the average perimeter-area ratio. We can compare this to the built-in function which uses a Polsby-Popper metric (fs_compact).

    +
    # average perimeter-area ratio
    +pa <- function(p, a){
    +  return(mean(p / a))
    +}
    +
    +# used to summarise within zones
    +# note that fs_varlist is still within a list
    +calculate_footstats(buildings,
    +                    adminzones,
    +                    what=list(list("compact"), fs_varlist("area","perimeter")),
    +                    how=list(list("mean"), list("pa")),
    +                    verbose=T
    +                   )
    +#> Selecting metrics 
    +#> Setting control values. 
    +#> Creating zonal index 
    +#> Pre-calculating areas 
    +#> Pre-calculating perimeters 
    +#> Pre-calculating compactness 
    +#> 
    +#> Calculating  2  metrics ... 
    +#>    compact mean  
    +#>    area perimeter pa  
    +#> Finished calculating metrics.
    +#>     zoneID compact_mean area_perimeter_pa
    +#>  1:      1    0.5991569      3.610827 [m]
    +#>  2:      2    0.6575220      2.821102 [m]
    +#>  3:      3    0.6126862      3.640346 [m]
    +#>  4:      4    0.6272569      3.572513 [m]
    +#>  5:      5    0.6053632      3.772161 [m]
    +#>  6:      6    0.6255586      5.090459 [m]
    +#>  7:      7    0.5447738      6.750857 [m]
    +#>  8:      8    0.5516914      4.786082 [m]
    +#>  9:      9    0.6113972      2.338425 [m]
    +#> 10:     10    0.6219757      2.535005 [m]
    +#> 11:     11    0.5777758      2.484360 [m]
    +#> 12:     12    0.6591031      1.771451 [m]
    +#> 13:     13    0.5629929      3.321843 [m]
    +#> 14:     14    0.7122782      2.109547 [m]
    +#> 15:     15    0.7247100      1.988679 [m]
    +#> 16:     16    0.6982459      1.869702 [m]
    +#> 17:     17    0.6518730      2.353175 [m]
    +#> 18:     18    0.6869924      1.842260 [m]
    +#> 19:     19    0.6686708      3.282259 [m]
    +#> 20:     20    0.6810544      2.956515 [m]
    +#> 21:     21    0.6926037      3.095947 [m]
    +#> 22:     22    0.7113827      3.226212 [m]
    +#> 23:     23    0.6151105      4.069236 [m]
    +#> 24:     24    0.6682211      3.074468 [m]
    +#> 25:     25    0.6304162      4.012065 [m]
    +#> 26:     26    0.6118970      5.404871 [m]
    +#> 27:     27    0.6831782      2.964538 [m]
    +#> 28:     29    0.6616867      2.660880 [m]
    +#> 29:     30    0.6448110      2.882803 [m]
    +#> 30:     31    0.7708405      2.952773 [m]
    +#> 31:     32    0.6358119      3.116430 [m]
    +#> 32:     33    0.5847305      3.728961 [m]
    +#> 33:     34    0.6413189      2.529615 [m]
    +#>     zoneID compact_mean area_perimeter_pa
    +
    +
    +
    +

    Accessing R objects other than the footprints

    +

    A more complicated scenario exists when a user-defined function needs to access data which is not an attribute of the footprints dataset. In order to access the non-footprint data, a partial function must be created first and then supplied to calculation function.

    +

    In the example below, a simple constant value is supplied to a summary function; however, the idea extends to any object in the R environment. This process is how the nearest neighbour index is calculated in foot by drawing on the spatial zones object as well as the footprints.

    +
    # external "data"
    +d1 <- 0.001
    +
    +# This will NOT work because argument 'd' is not found
    +# f2 <- function(x, d){
    +#   return(sum(d * x))
    +# }
    +# 
    +# calculate_footstats(buildings, adminzones, what="area", how="f2", verbose=T)
    +
    +# Instead...
    +# example of creating a partial function
    +gen_f3 <- function(d){
    +  force(d) # must include
    +  function(x){
    +    return(sum(d * x))
    +  }
    +}
    +
    +# generate the function and initialise it with `d1` from above.
    +f3 <- gen_f3(d1)
    +
    +# this now uses the generated function, and `d` is found
    +calculate_footstats(buildings, 
    +                    adminzones, 
    +                    what="area", 
    +                    how="f3", 
    +                    verbose=F
    +                   )
    +#>     zoneID           area_f3
    +#>  1:      1   8.4545661 [m^2]
    +#>  2:      2  29.7585314 [m^2]
    +#>  3:      3  25.7286600 [m^2]
    +#>  4:      4  63.8357113 [m^2]
    +#>  5:      5  26.7296259 [m^2]
    +#>  6:      6  35.7683531 [m^2]
    +#>  7:      7  28.3878563 [m^2]
    +#>  8:      8  44.5041537 [m^2]
    +#>  9:      9  48.1457825 [m^2]
    +#> 10:     10   7.5599164 [m^2]
    +#> 11:     11  24.2123780 [m^2]
    +#> 12:     12   3.1098042 [m^2]
    +#> 13:     13   2.6464756 [m^2]
    +#> 14:     14   6.7120641 [m^2]
    +#> 15:     15  19.2269062 [m^2]
    +#> 16:     16   9.9002318 [m^2]
    +#> 17:     17  14.7525754 [m^2]
    +#> 18:     18  36.8954355 [m^2]
    +#> 19:     19  96.4900399 [m^2]
    +#> 20:     20  80.3556287 [m^2]
    +#> 21:     21  18.0633304 [m^2]
    +#> 22:     22  10.1969657 [m^2]
    +#> 23:     23 132.6730618 [m^2]
    +#> 24:     24  61.8347854 [m^2]
    +#> 25:     25  11.6430956 [m^2]
    +#> 26:     26 150.2087190 [m^2]
    +#> 27:     27  33.8168355 [m^2]
    +#> 28:     29  37.0078380 [m^2]
    +#> 29:     30  15.2761021 [m^2]
    +#> 30:     31   0.1421365 [m^2]
    +#> 31:     32  35.7328487 [m^2]
    +#> 32:     33   7.1172864 [m^2]
    +#> 33:     34 146.9351905 [m^2]
    +#>     zoneID           area_f3
    +

    In this vignette, the foot package has been extended to incorporate user-defined functions. These functions can use one or more values from within the footprints, or even access other objects in the environment. While the examples used calculate_footstats, the same approaches can be used to create new gridded summary metrics with calculate_bigfoot.

    +
    +
    sessionInfo()
    +#> R version 3.6.3 (2020-02-29)
    +#> Platform: x86_64-pc-linux-gnu (64-bit)
    +#> Running under: Ubuntu 20.04.1 LTS
    +#> 
    +#> Matrix products: default
    +#> BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.9.0
    +#> LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.9.0
    +#> 
    +#> locale:
    +#>  [1] LC_CTYPE=en_GB.UTF-8       LC_NUMERIC=C              
    +#>  [3] LC_TIME=en_GB.UTF-8        LC_COLLATE=en_GB.UTF-8    
    +#>  [5] LC_MONETARY=en_GB.UTF-8    LC_MESSAGES=en_GB.UTF-8   
    +#>  [7] LC_PAPER=en_GB.UTF-8       LC_NAME=C                 
    +#>  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
    +#> [11] LC_MEASUREMENT=en_GB.UTF-8 LC_IDENTIFICATION=C       
    +#> 
    +#> attached base packages:
    +#> [1] stats     graphics  grDevices utils     datasets  methods   base     
    +#> 
    +#> other attached packages:
    +#> [1] sf_0.9-6 foot_0.5
    +#> 
    +#> loaded via a namespace (and not attached):
    +#>  [1] Rcpp_1.0.5         pillar_1.4.3       compiler_3.6.3     formatR_1.7       
    +#>  [5] class_7.3-16       iterators_1.0.13   tools_3.6.3        digest_0.6.25     
    +#>  [9] evaluate_0.14      tibble_3.0.1       lifecycle_0.2.0    pkgconfig_2.0.3   
    +#> [13] rlang_0.4.8        foreach_1.5.1      DBI_1.1.0          filelock_1.0.2    
    +#> [17] yaml_2.2.1         parallel_3.6.3     xfun_0.18          e1071_1.7-4       
    +#> [21] stringr_1.4.0      dplyr_0.8.5        knitr_1.30         vctrs_0.2.4       
    +#> [25] tidyselect_1.0.0   classInt_0.4-3     grid_3.6.3         glue_1.4.0        
    +#> [29] data.table_1.13.2  R6_2.4.1           rmarkdown_2.1      purrr_0.3.4       
    +#> [33] magrittr_1.5       stars_0.4-3        codetools_0.2-16   htmltools_0.4.0   
    +#> [37] ellipsis_0.3.0     units_0.6-7        abind_1.4-5        assertthat_0.2.1  
    +#> [41] KernSmooth_2.23-16 stringi_1.4.6      doParallel_1.0.16  lwgeom_0.2-5      
    +#> [45] crayon_1.3.4
    +
    + + + + +
    + + + + + + + + + + + + + + + diff --git a/wd/footsteps.html b/wd/footsteps.html new file mode 100644 index 0000000..c274553 --- /dev/null +++ b/wd/footsteps.html @@ -0,0 +1,946 @@ + + + + + + + + + + + + + + +Basic building footprint calculations + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +

    The foot package was developed by WorldPop at the University of Southampton (www.worldpop.org) to provide a set of consistent and flexible tools for processing 2D vector representations of buildings (e.g. “footprints”) and calculating urban morphology measurements. The functionality includes basic geometry and morphology characteristics, distance and clustering metrics. These calculations are supported with helper functions for spatial intersections and tiled reading/writing of data.

    +
    library(foot)
    +

    This vignette will demonstrate some of the core functionality in the package, including:

    +
      +
    • The available measurements and summary statistics
    • +
    • How to define different types of spatial zones for area-level summaries
    • +
    • Calculating multiple summary metrics for a set of spatial areas
    • +
    • Example workflows to produce outputs using foot::calculate_footstats().
    • +
    +

    To demonstrate the package, this vignette will use a supplied sample of building footprint polygons produced by Microsoft Bing Maps (Source) from an area in Kampala, Uganda. These footprints have been reprocessed into a spatial data format which can be read with sf.

    +
    +

    Load the sample dataset

    +
    data("kampala", package = "foot")
    +
    +buildings <- kampala$buildings
    +adminzones <- kampala$adminZones
    +clusters <- kampala$clusters
    +

    The sample dataset is a list of four named objects:

    +
      +
    • “buildings” - polygons of building footprints in sf format. Contains 8480 records.
    • +
    • “mastergrid” - geoTiff RasterLayer aligned to WorldPop datalayers. This will be used as a template for producing other gridded outputs
    • +
    • “adminZones” - 34 polygons in sf format for zonal statistics
    • +
    • “clusters” - 10 small polygons in sf format for sample sites
    • +
    +

    Note that the adminZones and cluster boundaries are purely artificial and were created for demonstration purposes only.

    +

    For more information, see ?kampala.

    +
    +Sample buildings and zones in Kampala +

    +Sample buildings and zones in Kampala +

    +
    +
    +
    +

    Calculations with foot

    +

    The core functions of foot are provided by calculate_footstats(). The operations include: 1) calculating geometry measures for each footprint, 2) summarising one or more geometry measures of footprints within zones. The simplest usage involves supplying a set of building footprint polygons and the desired characteristics to calculate. All operations return the same format - a data.table, optionally with a column for the index and the named column for the summarised footprint metric(s).

    +
    # the area and perimeter of each building footprint
    +built_area <- calculate_footstats(buildings, what = list("area", "perimeter"))
    +#> Selecting metrics 
    +#> Setting control values. 
    +#> Pre-calculating areas 
    +#> Pre-calculating perimeters 
    +#> No summary functions found, returning metrics.
    +
    +head(built_area)
    +#>               area    perimeter
    +#> 1:  22.00824 [m^2] 19.26045 [m]
    +#> 2: 220.39011 [m^2] 67.95023 [m]
    +#> 3:  38.95750 [m^2] 25.37269 [m]
    +#> 4: 386.74429 [m^2] 98.55654 [m]
    +#> 5: 349.57765 [m^2] 82.22555 [m]
    +#> 6: 164.00931 [m^2] 55.87172 [m]
    +

    To summarise the footprint characteristics over all building polygons, the name of a summary function, or a list of multiple functions can be supplied.

    +
    # the average and standard deviation of all building areas
    +calculate_footstats(buildings, what = "area", how = list("mean", "sd"))
    +#> Selecting metrics 
    +#> Setting control values. 
    +#> Pre-calculating areas 
    +#> 
    +#> Calculating  2  metrics ... 
    +#>    area mean  
    +#>    area sd  
    +#> Finished calculating metrics.
    +#>    zoneID      area_mean area_sd
    +#> 1:      1 263.1865 [m^2]  715.28
    +
    +

    Available metrics and summary measures

    +

    Currently implemented are measures for:

    +
      +
    • building presence
    • +
    • area
    • +
    • perimeter
    • +
    • nearest neighbour distance
    • +
    • angle of rotation
    • +
    • compactness and shape
    • +
    +

    A table of metrics and other packaged summary function names available for each is available using list_fs(). The results of this function provide “cols” and “funs” which can be passed as what and how arguments, respectively, to calculate_footstats.

    +
    # get all available built-in functions for perimeter calculations
    +argsList <- list_fs(what = "perimeter")
    +
    +calculate_footstats(buildings, what = argsList$cols, how = argsList$funs)
    +#> Selecting metrics 
    +#> Setting control values. 
    +#> Pre-calculating perimeters 
    +#> 
    +#> Calculating  7  metrics ... 
    +#>    perimeter cv  
    +#>    perimeter max  
    +#>    perimeter mean  
    +#>    perimeter median  
    +#>    perimeter min  
    +#>    perimeter sd  
    +#>    perimeter sum  
    +#> Finished calculating metrics.
    +#>    zoneID perimeter_cv perimeter_max perimeter_mean perimeter_median
    +#> 1:      1    0.9396027  1089.097 [m]   62.22027 [m]     46.68314 [m]
    +#>    perimeter_min perimeter_sd perimeter_sum
    +#> 1:  14.88332 [m]     58.46233  301146.1 [m]
    +

    With no other argument supplied, all the footprints are treated as coming from the same spatial zone for any summary function. A later section describes the process for identifying zones.

    +
    +
    +
    +

    Additional characteristics and geometry measures

    +

    Whenever possible, foot makes use of generic functions within R. Most low-level geometry calculations rely on sf and lwgeom and users need to have recent versions of these packages installed. There are other stand-alone functions within foot to support more complex or less-common measurements.

    +
    +

    Nearest neighbour distances

    +

    Distances can be calculated between footprints, or between footprints and other spatial objects. The distances can be measured edge-to-edge (method="poly") or the centroids of the building footprints can be used (method="centroid").

    +
    # nearest distance for the first 10 buildings to any other building measured
    +# between polygon edges
    +fs_nndist(buildings[1:10, ], buildings, maxSearch = 200, unit = "m")
    +#> Units: [m]
    +#>  [1]  3.961359  7.894813  1.019971 22.306277  2.484471 16.257603  0.951225
    +#>  [8]  1.864305  2.607113  2.291474
    +
    +# omitting argument 'Y' measures distance among the footprints supplied setting
    +# maxSearch=NULL removes the search restriction
    +fs_nndist(buildings[1:10, ], method = "centroid", maxSearch = NULL)
    +#> Units: [m]
    +#>  [1]  99.95859  99.95859  90.11091  90.11091 116.64498 116.64498 217.86037
    +#>  [8] 228.63068 217.86037 438.91352
    +

    Note that distance calculations are slower for polygons and for unprojected coordinate systems. The centroid-based calculations are fast. It is recommended that a maximum search radius is always used. Internally the calculations are done with a data.table to benefit from multi-threading capabilities.

    +
    +
    +

    Rotation angles

    +

    A less conventional geometric measure is derived from the rotated bounding rectangle. This is the rectangle enclosing a footprint polygon which has been rotated to minimise the area. In contrast, a “bounding box” for a polygon is always oriented along the x and y axes.

    +
    # To obtain the rotated rectangle as a spatial object
    +mbr <- fs_mbr(buildings[4502, ], returnShape = T)
    +
    +plot(sf::st_geometry(buildings[4502, ]), col = "grey")
    +plot(mbr, border = "red", lwd = 2, add = T)
    +

    +
    
    +# Or obtain just the angle measure
    +fs_mbr(buildings[4502, ], returnShape = F)
    +#>        X 
    +#> 63.25363
    +

    The angles can be summarised as an entropy measure and normalised to describe how much the angles of a set of structures depart from a regular grid pattern (available in calculate_footstats where how="entropy").

    +
    +
    +
    +

    Creating and supplying zone indices

    +

    Rather than treating all footprints features as belonging to a single summary zone, it’s more common to want to summarise metrics within smaller areas. There are several ways to supply information on the zones.

    +
    +

    Index by vector

    +

    A vector of indices for the zones can be supplied to foot functions as a 1) column name within the footprints, 2) a standalone numeric vector of indices, or 3) a spatial polygons object to join to the building footprints. The length of a vector of indices must match the number of building polygons.

    +
    # create a vector of ten random zones
    +idx <- sample(1:10, nrow(buildings), replace = T)
    +buildings$id <- idx  # add it to the buildings object
    +table(buildings$id)  # splitting observations into 10 groups
    +#> 
    +#>   1   2   3   4   5   6   7   8   9  10 
    +#> 488 470 475 470 503 480 491 491 498 474
    +
    +# 1. pass the index by name
    +colnames(buildings)
    +#> [1] "FID_1"    "geometry" "id"
    +calculate_footstats(buildings, "id", what = "area", how = "mean", verbose = FALSE)
    +#>     id      area_mean
    +#>  1:  1 311.8235 [m^2]
    +#>  2:  2 258.5639 [m^2]
    +#>  3:  3 284.0431 [m^2]
    +#>  4:  4 257.0889 [m^2]
    +#>  5:  5 250.8175 [m^2]
    +#>  6:  6 225.4767 [m^2]
    +#>  7:  7 243.3985 [m^2]
    +#>  8:  8 294.7468 [m^2]
    +#>  9:  9 285.8606 [m^2]
    +#> 10: 10 218.1389 [m^2]
    +
    +# 2. pass the index as a standalone vector
    +calculate_footstats(buildings, idx, what = "settled", how = "count", verbose = FALSE)
    +#>     zoneID settled_count
    +#>  1:      1           488
    +#>  2:      2           470
    +#>  3:      3           475
    +#>  4:      4           470
    +#>  5:      5           503
    +#>  6:      6           480
    +#>  7:      7           491
    +#>  8:      8           491
    +#>  9:      9           498
    +#> 10:     10           474
    +
    +# 3. pass a separate spatial object of zones
    +calculate_footstats(buildings, zone = adminzones, what = "angle", how = "entropy", 
    +    verbose = FALSE)
    +#>     zoneID angle_entropy
    +#>  1:      1     0.9351095
    +#>  2:      2     0.3108829
    +#>  3:      3     0.5849276
    +#>  4:      4     0.2003194
    +#>  5:      5     0.6678707
    +#>  6:      6     0.7139565
    +#>  7:      7     0.9691374
    +#>  8:      8     0.6102641
    +#>  9:      9     0.5110688
    +#> 10:     10     0.7309444
    +#> 11:     11     0.3933987
    +#> 12:     12     0.4602628
    +#> 13:     13     1.0000000
    +#> 14:     14     0.4656334
    +#> 15:     15     0.3129308
    +#> 16:     16     0.4551733
    +#> 17:     17     0.3864071
    +#> 18:     18     0.6949125
    +#> 19:     19     0.2928137
    +#> 20:     20     0.4598566
    +#> 21:     21     0.6047045
    +#> 22:     22     0.6520860
    +#> 23:     23     0.2425743
    +#> 24:     24     0.4518750
    +#> 25:     25     0.8426361
    +#> 26:     26     0.7302834
    +#> 27:     27     0.4325040
    +#> 28:     29     0.6144511
    +#> 29:     30     0.4754793
    +#> 30:     31     0.6023774
    +#> 31:     32     0.4508777
    +#> 32:     33     0.8639418
    +#> 33:     34     0.1613791
    +#>     zoneID angle_entropy
    +
    +
    +

    Index by zone shapes

    +

    Rather than supplying a pre-calculated column or vector of zonal indices, buildings can be assigned a zone based on a spatial join. When the index is created in the building footprints, it will be named “zoneID” or a user-specified name.

    +
    # examine the other objects loaded from supplied data
    +head(adminzones)
    +#> Simple feature collection with 6 features and 1 field
    +#> geometry type:  POLYGON
    +#> dimension:      XY
    +#> bbox:           xmin: 32.60589 ymin: 0.3277355 xmax: 32.62531 ymax: 0.3388665
    +#> geographic CRS: WGS 84
    +#>   Id                       geometry
    +#> 1  1 POLYGON ((32.61231 0.327771...
    +#> 2  2 POLYGON ((32.61034 0.335317...
    +#> 3  3 POLYGON ((32.61592 0.327757...
    +#> 4  4 POLYGON ((32.6214 0.3277355...
    +#> 5  5 POLYGON ((32.62342 0.333760...
    +#> 6  6 POLYGON ((32.62498 0.335199...
    +head(clusters)
    +#> Simple feature collection with 6 features and 1 field
    +#> geometry type:  POLYGON
    +#> dimension:      XY
    +#> bbox:           xmin: 32.60624 ymin: 0.3303863 xmax: 32.63375 ymax: 0.3472696
    +#> geographic CRS: WGS 84
    +#>   Id                       geometry
    +#> 1  1 POLYGON ((32.6066 0.3398617...
    +#> 2  2 POLYGON ((32.61151 0.337602...
    +#> 3  3 POLYGON ((32.61028 0.331954...
    +#> 4  4 POLYGON ((32.61649 0.344649...
    +#> 5  5 POLYGON ((32.62552 0.345516...
    +#> 6  6 POLYGON ((32.63225 0.340927...
    +
    +# Return a table of index values based on administrative zone polygons using the
    +# standalone function within `foot`
    +zID <- zonalIndex(buildings, adminzones, returnObject = F)
    +head(zID, 10)  # the xID column are row numbers to object X
    +#>      xID zoneID
    +#>  1:  145      1
    +#>  2:  338      1
    +#>  3:  655      1
    +#>  4:  995      1
    +#>  5: 2869      1
    +#>  6: 3164      1
    +#>  7: 3263      1
    +#>  8: 3314      1
    +#>  9: 3407      1
    +#> 10: 3454      1
    +
    +# Alternatively (and preferably), join zones to create a new footprint object A
    +# custom zone name can be used but must be specificed to the summary functions
    +zObj <- zonalIndex(buildings, clusters, zoneField = "Id", returnObject = T)
    +zObj
    +#> Simple feature collection with 348 features and 3 fields
    +#> geometry type:  POLYGON
    +#> dimension:      XY
    +#> bbox:           xmin: 32.60631 ymin: 0.328119 xmax: 32.63543 ymax: 0.349785
    +#> geographic CRS: WGS 84
    +#> First 10 features:
    +#>    FID_1 id Id                       geometry
    +#> 1   6293 10  1 POLYGON ((32.60744 0.338575...
    +#> 2  17706  7  1 POLYGON ((32.60664 0.339347...
    +#> 3  29106  1  1 POLYGON ((32.60719 0.339276...
    +#> 4  53393  2  1 POLYGON ((32.60779 0.339691...
    +#> 5  57187  2  1 POLYGON ((32.60826 0.338663...
    +#> 6  59496  1  1 POLYGON ((32.60692 0.339387...
    +#> 7  68588  5  1 POLYGON ((32.60791 0.338896...
    +#> 8  68590  7  1 POLYGON ((32.60768 0.339633...
    +#> 9  72387  1  1 POLYGON ((32.60777 0.338962...
    +#> 10 78504  8  1 POLYGON ((32.60702 0.338636...
    +

    When using a new spatial object which has been joined to its zones, remember to supply the name of the zone field to calculate_foostats.

    +
    # use the new object and zone field 'Id' in a summary calculation
    +colnames(zObj)
    +#> [1] "FID_1"    "id"       "Id"       "geometry"
    +
    +zarea <- calculate_footstats(zObj, zone = "Id", what = "area", how = "mean", verbose = F)
    +clusters <- merge(clusters, zarea, by = "Id")
    +plot(sf::st_geometry(adminzones))
    +plot(clusters["area_mean"], add = T)
    +

    +

    The zonalIndex function works by spatial intersection. This produces some (potentially useful) side effects that users need to be aware of. Specifically, note that if a building footprint intersects more than 1 zone it will be duplicated and associated to all intersecting zones.

    +

    The default behaviour (see method) is to assign a building to a zone based on its centroid.

    +
    # Note the selected structures extend beyond the cluster boundary
    +plot(sf::st_geometry(clusters)[[6]])
    +plot(sf::st_geometry(buildings), add = T)
    +plot(sf::st_geometry(zObj[zObj$Id == 6, ]), col = "red", add = T)
    +plot(sf::st_geometry(sf::st_centroid(zObj[zObj$Id == 6, ])), pch = 16, add = T)
    +

    +

    Alternatively, an intersection can be used to assign footprints to any zones which are intersected. The whole footprint is associated, even if the shape is not “contained” by the zone.

    +
    # Note the selected structures extend beyond the cluster boundary
    +zInt <- zonalIndex(buildings, clusters, zoneField = "Id", method = "intersect")
    +
    +plot(sf::st_geometry(clusters)[[6]])
    +plot(sf::st_geometry(buildings), add = T)
    +plot(sf::st_geometry(zInt[zInt$Id == 6, ]), col = "red", add = T)
    +

    +

    Finally, the intersection can return a clipped set of buildings.

    +
    zClip <- zonalIndex(buildings, clusters, zoneField = "Id", method = "clip")
    +
    +plot(sf::st_geometry(clusters)[[6]])
    +plot(sf::st_geometry(buildings), add = T)
    +plot(sf::st_geometry(zClip[zClip$Id == 6, ]), col = "red", add = T)
    +

    +

    This third option clips the footprints via intersection, potentially leaving small slivers of structures in the zone which will affect the feature statistics.

    +

    An additional side effect of the intersection operation is that overlapping zones are allowed, and this can duplicate and associate footprints into both (overlapping) zones.

    +
    # create a temporary shape by shifting one cluster
    +newClusters <- st_sfc(sf::st_geometry(clusters)[[1]], sf::st_cast(sf::st_geometry(clusters)[[1]] + 
    +    c(0.001, 1e-04), "POLYGON"), crs = sf::st_crs(clusters))
    +
    +newClusters <- st_sf(geometry = newClusters, crs = sf::st_crs(clusters))
    +
    +newObj <- zonalIndex(buildings, newClusters, method = "clip")
    +
    +# areas of overlap are in a purple hue
    +plot(sf::st_geometry(newClusters))
    +plot(sf::st_geometry(newObj[newObj$zoneID == 1, ]), col = "red", add = T)
    +plot(sf::st_geometry(newObj[newObj$zoneID == 2, ]), col = sf::sf.colors(n = 1, alpha = 0.5), 
    +    add = T)
    +plot(sf::st_geometry(buildings), add = T)
    +

    +

    These side effects are allowed because they allow for flexibility to support types of “focal” summaries of statistics and to produce a true gridded measure of footprint metrics.

    +
    +
    +
    +

    Calculating multiple metrics

    +

    The calculate_footstats() function provides a convenient wrapper to the individual footprint statistics and as well to zonalIndex. The function accepts a variety of input formats (see ?calculate_footstats). Multiple metrics can be calculated for the same sets of buildings and zones.

    +
    # Creates a zonal index and calculates multiple metrics
    +# Use the intersection method define zones
    +results <- calculate_footstats(buildings, 
    +                               zone=adminzones, 
    +                               what="area",
    +                               how=list("mean","cv"),
    +                               controlZone=list(method="intersect"),
    +                               verbose=F
    +                              )
    +  results
    +#>     zoneID        area_mean   area_cv
    +#>  1:      1  402.59838 [m^2] 1.2479809
    +#>  2:      2  211.05341 [m^2] 1.1069491
    +#>  3:      3  525.07469 [m^2] 1.9194754
    +#>  4:      4  555.09314 [m^2] 3.8206699
    +#>  5:      5  568.71544 [m^2] 1.7000587
    +#>  6:      6 1021.95294 [m^2] 2.0652377
    +#>  7:      7 2027.70402 [m^2] 1.4077033
    +#>  8:      8  908.24803 [m^2] 1.4225521
    +#>  9:      9  181.68220 [m^2] 1.6104922
    +#> 10:     10  251.99721 [m^2] 1.5272718
    +#> 11:     11  220.11253 [m^2] 1.4300230
    +#> 12:     12   87.83690 [m^2] 1.2659928
    +#> 13:     13  661.61890 [m^2] 1.7191007
    +#> 14:     14   81.85444 [m^2] 0.3447115
    +#> 15:     15   80.78532 [m^2] 1.0115881
    +#> 16:     16   68.75161 [m^2] 0.5102143
    +#> 17:     17  139.17524 [m^2] 0.8678702
    +#> 18:     18   87.23691 [m^2] 1.5248900
    +#> 19:     19  237.07627 [m^2] 0.6189696
    +#> 20:     20  199.39362 [m^2] 0.9103598
    +#> 21:     21  244.09906 [m^2] 2.4147823
    +#> 22:     22  192.39558 [m^2] 0.3464272
    +#> 23:     23  776.00000 [m^2] 2.1447695
    +#> 24:     24  219.27229 [m^2] 0.7972001
    +#> 25:     25  506.22155 [m^2] 1.3228666
    +#> 26:     26 1129.64270 [m^2] 1.7076343
    +#> 27:     27  228.00865 [m^2] 2.2623265
    +#> 28:     29  238.76025 [m^2] 2.7887959
    +#> 29:     30  203.68136 [m^2] 0.8303153
    +#> 30:     31  142.13649 [m^2]        NA
    +#> 31:     32  272.76984 [m^2] 1.0894764
    +#> 32:     33  418.66391 [m^2] 0.9787568
    +#> 33:     34  176.81732 [m^2] 1.3707628
    +#>     zoneID        area_mean   area_cv
    +

    Multiple metrics can be applied to specific groups of characteristics by providing nested lists of metrics and summary functions. The argument what will accept a string or a list of strings for specific metric names. Users may also supply "all" or "nodist" to calculate all available metrics or all bar the nearest neighbour distance-related ones, respectively. Excluding the distance-related metrics can speed up the calculations due to the long-running. Other performance improvements can be to set controlDistance=list("method"="centroid"), which uses centroid-to-centroid nearest neighbour distances rather than polygon edge-to-edge. See also, ?fs_nndist.

    +
    # Use nested lists to group characteristics and summary functions
    +results <- calculate_footstats(buildings, 
    +                               zone=adminzones, 
    +                               what=list(list("area","perimeter"), 
    +                                         list("settled")),
    +                               how=list(list("sum","cv"), 
    +                                        list("count")),
    +                               controlZone=list(method="centroid"),
    +                               verbose=F
    +                              )
    +  results
    +#>     zoneID          area_sum   perimeter_sum   area_cv perimeter_cv
    +#>  1:      1   8454.5661 [m^2]  1781.62245 [m] 1.2479809    0.6561304
    +#>  2:      2  29758.5314 [m^2]  8596.30974 [m] 1.1069491    0.7313348
    +#>  3:      3  25728.6600 [m^2]  4414.86934 [m] 1.9194754    0.9811076
    +#>  4:      4  63835.7113 [m^2]  9640.94681 [m] 3.8206699    1.0600954
    +#>  5:      5  26729.6259 [m^2]  4329.10334 [m] 1.7000587    0.9581201
    +#>  6:      6  35768.3531 [m^2]  3835.74843 [m] 2.0652377    0.9616081
    +#>  7:      7  28387.8563 [m^2]  2565.09927 [m] 1.4077033    0.9286094
    +#>  8:      8  44504.1537 [m^2]  6348.37632 [m] 1.4225521    0.8489816
    +#>  9:      9  48145.7825 [m^2] 15739.78807 [m] 1.6104922    0.9246860
    +#> 10:     10   7559.9164 [m^2]  2087.88260 [m] 1.5272718    1.0900293
    +#> 11:     11  24212.3780 [m^2]  7672.66021 [m] 1.4300230    0.8815988
    +#> 12:     12   3109.8042 [m^2]  1403.25960 [m] 1.3014215    0.7257624
    +#> 13:     13   2646.4756 [m^2]   423.87801 [m] 1.7191007    1.3165558
    +#> 14:     14   6712.0641 [m^2]  3088.40482 [m] 0.3447115    0.2059276
    +#> 15:     15  19226.9062 [m^2]  8676.96497 [m] 1.0115881    0.5073846
    +#> 16:     16   9900.2318 [m^2]  4968.39946 [m] 0.5102143    0.3095971
    +#> 17:     17  14752.5754 [m^2]  5213.18182 [m] 0.8678702    0.5541695
    +#> 18:     18  36895.4355 [m^2] 15742.13708 [m] 1.5308486    0.7093738
    +#> 19:     19  96490.0399 [m^2] 26354.10548 [m] 0.6189696    0.4151437
    +#> 20:     20  80355.6287 [m^2] 23566.14546 [m] 0.9103598    0.5257108
    +#> 21:     21  18063.3304 [m^2]  4403.44976 [m] 2.4147823    0.7810948
    +#> 22:     22  10196.9657 [m^2]  3061.47987 [m] 0.3464272    0.2238327
    +#> 23:     23 132673.0618 [m^2] 17464.78357 [m] 2.1625263    1.1205392
    +#> 24:     24  61834.7854 [m^2] 17707.47705 [m] 0.7972001    0.5317242
    +#> 25:     25  11643.0956 [m^2]  2163.70761 [m] 1.3228666    0.8940386
    +#> 26:     26 150208.7190 [m^2] 17604.40321 [m] 1.6814458    1.0767096
    +#> 27:     27  33816.8355 [m^2] 10017.95165 [m] 0.7963207    0.4462858
    +#> 28:     29  37007.8380 [m^2]  9185.83050 [m] 2.7887959    1.0713172
    +#> 29:     30  15276.1021 [m^2]  4621.61764 [m] 0.8303153    0.5426390
    +#> 30:     31    142.1365 [m^2]    48.13661 [m]        NA           NA
    +#> 31:     32  35732.8487 [m^2]  9127.30909 [m] 1.0894764    0.6469339
    +#> 32:     33   7117.2864 [m^2]  1557.24261 [m] 0.9787568    0.6104610
    +#> 33:     34 146935.1905 [m^2] 47733.83601 [m] 1.3707628    0.7474269
    +#>     zoneID          area_sum   perimeter_sum   area_cv perimeter_cv
    +#>     settled_count
    +#>  1:            21
    +#>  2:           141
    +#>  3:            49
    +#>  4:           115
    +#>  5:            47
    +#>  6:            35
    +#>  7:            14
    +#>  8:            49
    +#>  9:           265
    +#> 10:            30
    +#> 11:           110
    +#> 12:            36
    +#> 13:             4
    +#> 14:            82
    +#> 15:           238
    +#> 16:           144
    +#> 17:           106
    +#> 18:           424
    +#> 19:           407
    +#> 20:           403
    +#> 21:            74
    +#> 22:            53
    +#> 23:           172
    +#> 24:           282
    +#> 25:            23
    +#> 26:           129
    +#> 27:           177
    +#> 28:           155
    +#> 29:            75
    +#> 30:             1
    +#> 31:           131
    +#> 32:            17
    +#> 33:           831
    +#>     settled_count
    +
    +

    Filtering buildings

    +

    In some settings it may be preferable to exclude very small and/or very large building footprint polygons. The lower and upper bounds for filtering can be specified with minArea and maxArea in the filter argument. The values for these filters are in the same units specified by controlUnits or the default value for area calculations. Note that an “area” footprint statistic does not need to be requested as this characteristic is automatically calculated to enable filtering.

    +
    # Filtering: # footprints must be larger than 50 m^2 and smaller than 1000 m^2
    +calculate_footstats(buildings,
    +                    what="perimeter",
    +                    how=list("mean", "sum"),
    +                    controlUnits=list(areaUnit="m^2"),
    +                    filter=list(minArea=50, maxArea=1000),
    +                    verbose=FALSE)  
    +#>    zoneID perimeter_mean perimeter_sum
    +#> 1:      1   63.16899 [m]  228482.2 [m]
    +
    +
    +
    +

    Next steps

    +

    The calculate_footstats function provides the core functionality for calculating and summarising the characteristics of building footprints. It also wraps the functionality of assigning footprints to zones based on different spatial joining techniques. To go further with foot the concept of footprint morphology calculations can be extended to created gridded data. See vignette("bigfoot", package="foot"). Additionally, users can specify their own custom summary functions to be used with calculate_footstats. This and other advanced options are covered in vignette("cobbler", package="foot").

    +
    +
    sessionInfo()
    +#> R version 3.6.3 (2020-02-29)
    +#> Platform: x86_64-pc-linux-gnu (64-bit)
    +#> Running under: Ubuntu 20.04.1 LTS
    +#> 
    +#> Matrix products: default
    +#> BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.9.0
    +#> LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.9.0
    +#> 
    +#> locale:
    +#>  [1] LC_CTYPE=en_GB.UTF-8       LC_NUMERIC=C              
    +#>  [3] LC_TIME=en_GB.UTF-8        LC_COLLATE=en_GB.UTF-8    
    +#>  [5] LC_MONETARY=en_GB.UTF-8    LC_MESSAGES=en_GB.UTF-8   
    +#>  [7] LC_PAPER=en_GB.UTF-8       LC_NAME=C                 
    +#>  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
    +#> [11] LC_MEASUREMENT=en_GB.UTF-8 LC_IDENTIFICATION=C       
    +#> 
    +#> attached base packages:
    +#> [1] stats     graphics  grDevices utils     datasets  methods   base     
    +#> 
    +#> other attached packages:
    +#> [1] sf_0.9-6 foot_0.5
    +#> 
    +#> loaded via a namespace (and not attached):
    +#>  [1] Rcpp_1.0.5         highr_0.8          pillar_1.4.3       compiler_3.6.3    
    +#>  [5] formatR_1.7        class_7.3-16       iterators_1.0.13   tools_3.6.3       
    +#>  [9] digest_0.6.25      evaluate_0.14      tibble_3.0.1       lifecycle_0.2.0   
    +#> [13] pkgconfig_2.0.3    rlang_0.4.8        foreach_1.5.1      DBI_1.1.0         
    +#> [17] filelock_1.0.2     yaml_2.2.1         parallel_3.6.3     xfun_0.18         
    +#> [21] e1071_1.7-4        stringr_1.4.0      dplyr_0.8.5        knitr_1.30        
    +#> [25] vctrs_0.2.4        tidyselect_1.0.0   classInt_0.4-3     grid_3.6.3        
    +#> [29] glue_1.4.0         data.table_1.13.2  R6_2.4.1           rmarkdown_2.1     
    +#> [33] purrr_0.3.4        magrittr_1.5       stars_0.4-3        codetools_0.2-16  
    +#> [37] htmltools_0.4.0    ellipsis_0.3.0     units_0.6-7        abind_1.4-5       
    +#> [41] assertthat_0.2.1   KernSmooth_2.23-16 stringi_1.4.6      doParallel_1.0.16 
    +#> [45] lwgeom_0.2-5       crayon_1.3.4
    +
    + + + + +
    + + + + + + + + + + + + + + +