├── .Rbuildignore ├── .github ├── .gitignore └── workflows │ ├── R-CMD-check.yaml │ ├── pkgdown.yaml │ └── update-citation-cff.yaml ├── .gitignore ├── CITATION.cff ├── DESCRIPTION ├── LICENSE.md ├── MultiscaleDTM.Rproj ├── NAMESPACE ├── R ├── AdjSD.R ├── BPI.R ├── DMV.R ├── DirSlope.R ├── MultiscaleDTM-package.R ├── Pfit.R ├── Qfit.R ├── RIE.R ├── RcppExports.R ├── RelPos.R ├── SAPA.R ├── SlpAsp.R ├── SurfaceArea.R ├── TPI.R ├── VRM.R ├── curvatures.R ├── erupt.R └── explore_terrain.R ├── README.Rmd ├── README.md ├── _pkgdown.yml ├── cran-comments.md ├── inst ├── CITATION ├── Terrain_Attributes_Explorer_App │ └── app.R └── testdata │ ├── adj_sd.RDS │ ├── bpi.RDS │ ├── dmv.RDS │ ├── qmetrics.RDS │ ├── rie.RDS │ ├── rp.RDS │ ├── sapa.RDS │ ├── slp_asp_queen.RDS │ ├── slp_asp_rook.RDS │ ├── tpi.RDS │ └── vrm.RDS ├── man ├── AdjSD.Rd ├── BPI.Rd ├── DMV.Rd ├── DirSlp.Rd ├── Pfit.Rd ├── Qfit.Rd ├── RIE.Rd ├── RelPos.Rd ├── SAPA.Rd ├── SlpAsp.Rd ├── SurfaceArea.Rd ├── TPI.Rd ├── VRM.Rd ├── annulus_window.Rd ├── circle_window.Rd ├── classify_features_ff.Rd ├── erupt.Rd ├── explore_terrain.Rd ├── figures │ ├── Qfit_annotated.png │ ├── README-AdjSD-1.png │ ├── README-BPI-1.png │ ├── README-DMV-1.png │ ├── README-RIE-1.png │ ├── README-RP-1.png │ ├── README-SAPA-1.png │ ├── README-SlpAsp-1.png │ ├── README-TPI-1.png │ ├── README-Topo-1.png │ ├── README-VRM-1.png │ ├── RIE.png │ ├── SAPA_annotated.png │ ├── SlpAsp.png │ ├── VRM_annotated.png │ ├── VRM_focal.png │ ├── WindowShapes.png │ ├── adj_sd.png │ ├── chaintape.png │ ├── kwindow.png │ └── qmetrics.jpg ├── fragments │ └── README_Frag.Rmd ├── kmax.Rd ├── kmean.Rd ├── kmin.Rd ├── knc.Rd ├── kns.Rd ├── ku.Rd ├── outlier_filter.Rd └── tgc.Rd ├── src ├── .gitignore ├── AdjSD.cpp ├── CountVals.cpp ├── Makevars ├── Makevars.win ├── Qfit.cpp ├── RcppExports.cpp └── SurfaceArea.cpp ├── tests ├── testthat.R └── testthat │ └── test-functions.R └── vignettes ├── .gitignore └── README.Rmd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^MultiscaleDTM\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^LICENSE\.md$ 4 | inst/Terrain_Attributes_Explorer_App/rsconnect 5 | ^CITATION\.cff$ 6 | ^\.github$ 7 | ^README\.Rmd$ 8 | ^\.Rhistory$ 9 | ^CRAN-SUBMISSION$ 10 | ^cran-comments\.md$ 11 | ^_pkgdown\.yml$ 12 | ^docs$ 13 | ^pkgdown$ 14 | BuildDocs.R 15 | ^doc$ 16 | ^Meta$ 17 | man/figures/sub_qfit*.png -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master, test] 6 | pull_request: 7 | branches: [main, master, test] 8 | 9 | name: R-CMD-check 10 | 11 | jobs: 12 | R-CMD-check: 13 | runs-on: ${{ matrix.config.os }} 14 | 15 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | config: 21 | - {os: macos-latest, r: 'release'} 22 | - {os: windows-latest, r: 'release'} 23 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 24 | - {os: ubuntu-latest, r: 'release'} 25 | - {os: ubuntu-latest, r: 'oldrel-1'} 26 | 27 | env: 28 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 29 | R_KEEP_PKG_SOURCE: yes 30 | RGL_USE_NULL: true 31 | 32 | steps: 33 | - uses: actions/checkout@v4 34 | 35 | - uses: r-lib/actions/setup-pandoc@v2 36 | 37 | - uses: r-lib/actions/setup-r@v2 38 | with: 39 | r-version: ${{ matrix.config.r }} 40 | http-user-agent: ${{ matrix.config.http-user-agent }} 41 | use-public-rspm: true 42 | 43 | - uses: r-lib/actions/setup-r-dependencies@v2 44 | with: 45 | extra-packages: any::rcmdcheck 46 | needs: check 47 | 48 | - uses: r-lib/actions/check-r-package@v2 49 | with: 50 | upload-snapshots: true -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | release: 9 | types: [published] 10 | workflow_dispatch: 11 | 12 | name: pkgdown 13 | 14 | jobs: 15 | pkgdown: 16 | runs-on: ubuntu-latest 17 | # Only restrict concurrency for non-PR jobs 18 | concurrency: 19 | group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} 20 | env: 21 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 22 | steps: 23 | - uses: actions/checkout@v4 24 | 25 | - uses: r-lib/actions/setup-pandoc@v2 26 | 27 | - uses: r-lib/actions/setup-r@v2 28 | with: 29 | use-public-rspm: true 30 | 31 | - uses: r-lib/actions/setup-r-dependencies@v2 32 | with: 33 | extra-packages: any::pkgdown, local::. 34 | needs: website 35 | 36 | - name: Build site 37 | run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) 38 | shell: Rscript {0} 39 | 40 | - name: Deploy to GitHub pages 🚀 41 | if: github.event_name != 'pull_request' 42 | uses: JamesIves/github-pages-deploy-action@4.1.4 43 | with: 44 | clean: false 45 | branch: gh-pages 46 | folder: docs 47 | -------------------------------------------------------------------------------- /.github/workflows/update-citation-cff.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/master/examples 2 | # The action runs when: 3 | # - A new release is published 4 | # - The DESCRIPTION or inst/CITATION are modified 5 | # - Can be run manually 6 | # For customizing the triggers, visit https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows 7 | on: 8 | release: 9 | types: [published] 10 | push: 11 | branches: [master, main] 12 | paths: 13 | - DESCRIPTION 14 | - inst/CITATION 15 | workflow_dispatch: 16 | 17 | name: Update CITATION.cff 18 | 19 | jobs: 20 | update-citation-cff: 21 | runs-on: macos-latest 22 | env: 23 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 24 | steps: 25 | - uses: actions/checkout@v4 26 | - uses: r-lib/actions/setup-r@v2 27 | - uses: r-lib/actions/setup-r-dependencies@v2 28 | with: 29 | extra-packages: | 30 | any::cffr 31 | any::V8 32 | 33 | - name: Update CITATION.cff 34 | run: | 35 | 36 | library(cffr) 37 | 38 | # Customize with your own code 39 | # See https://docs.ropensci.org/cffr/articles/cffr.html 40 | 41 | # Write your own keys 42 | mykeys <- list() 43 | 44 | # Create your CITATION.cff file 45 | cff_write(keys = mykeys) 46 | 47 | shell: Rscript {0} 48 | 49 | - name: Commit results 50 | run: | 51 | git config --local user.name "$GITHUB_ACTOR" 52 | git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" 53 | git add CITATION.cff 54 | git commit -m 'Update CITATION.cff' || echo "No changes to commit" 55 | git push origin || echo "No changes to commit" 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # History files 2 | .Rhistory 3 | .Rapp.history 4 | 5 | # Session Data files 6 | .RData 7 | 8 | # User-specific files 9 | .Ruserdata 10 | 11 | # Example code in package build process 12 | *-Ex.R 13 | 14 | # Output files from R CMD build 15 | /*.tar.gz 16 | 17 | # Output files from R CMD check 18 | /*.Rcheck/ 19 | 20 | # RStudio files 21 | .Rproj.user/ 22 | 23 | # produced vignettes 24 | vignettes/*.html 25 | vignettes/*.pdf 26 | 27 | # OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3 28 | .httr-oauth 29 | 30 | # knitr and R markdown default cache directories 31 | *_cache/ 32 | /cache/ 33 | 34 | # Temporary files created by R markdown 35 | *.utf8.md 36 | *.knit.md 37 | 38 | # R Environment Variables 39 | .Renviron 40 | .Rproj.user 41 | 42 | #Paths 43 | rsconnect 44 | CRAN-SUBMISSION 45 | docs 46 | inst/doc 47 | man/figures/sub_qfit*.png 48 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | # -------------------------------------------- 2 | # CITATION file created with {cffr} R package 3 | # See also: https://docs.ropensci.org/cffr/ 4 | # -------------------------------------------- 5 | 6 | cff-version: 1.2.0 7 | message: 'To cite package "MultiscaleDTM" in publications use:' 8 | type: software 9 | license: GPL-3.0-or-later 10 | title: 'MultiscaleDTM: Multi-Scale Geomorphometric Terrain Attributes' 11 | version: '1.0' 12 | doi: 10.1111/tgis.13067 13 | identifiers: 14 | - type: doi 15 | value: 10.32614/CRAN.package.MultiscaleDTM 16 | abstract: Calculates multi-scale geomorphometric terrain attributes from regularly 17 | gridded digital terrain models using a variable focal windows size (Ilich et al. 18 | (2023) ). 19 | authors: 20 | - family-names: Ilich 21 | given-names: Alexander 22 | email: ailich@usf.edu 23 | orcid: https://orcid.org/0000-0003-1758-8499 24 | - family-names: Lecours 25 | given-names: Vincent 26 | - family-names: Misiuk 27 | given-names: Benjamin 28 | - family-names: Murawski 29 | given-names: Steven 30 | preferred-citation: 31 | type: article 32 | title: 'MultiscaleDTM: An open-source R package for multiscale geomorphometric analysis' 33 | authors: 34 | - family-names: Ilich 35 | given-names: Alexander R. 36 | - family-names: Misiuk 37 | given-names: Benjamin 38 | - family-names: Lecours 39 | given-names: Vincent 40 | - family-names: Murawski 41 | given-names: Steven A. 42 | doi: 10.1111/tgis.13067 43 | year: '2023' 44 | publisher: 45 | name: Wiley 46 | volume: '27' 47 | issue: '4' 48 | journal: Transactions in GIS 49 | repository: https://CRAN.R-project.org/package=MultiscaleDTM 50 | repository-code: https://github.com/ailich/MultiscaleDTM 51 | url: https://ailich.github.io/MultiscaleDTM/ 52 | contact: 53 | - family-names: Ilich 54 | given-names: Alexander 55 | email: ailich@usf.edu 56 | orcid: https://orcid.org/0000-0003-1758-8499 57 | references: 58 | - type: generic 59 | title: MultiscaleDTM 60 | authors: 61 | - family-names: Ilich 62 | given-names: Alexander R. 63 | - family-names: Misiuk 64 | given-names: Benjamin 65 | - family-names: Lecours 66 | given-names: Vincent 67 | - family-names: Murawski 68 | given-names: Steven A. 69 | year: '2021' 70 | doi: 10.5281/zenodo.5548338 71 | url: https://github.com/ailich/MultiscaleDTM 72 | - type: software 73 | title: terra 74 | abstract: 'terra: Spatial Data Analysis' 75 | notes: Depends 76 | url: https://rspatial.org/ 77 | repository: https://CRAN.R-project.org/package=terra 78 | authors: 79 | - family-names: Hijmans 80 | given-names: Robert J. 81 | email: r.hijmans@gmail.com 82 | orcid: https://orcid.org/0000-0001-5872-2872 83 | year: '2025' 84 | doi: 10.32614/CRAN.package.terra 85 | version: '>= 1.7.46' 86 | - type: software 87 | title: Rcpp 88 | abstract: 'Rcpp: Seamless R and C++ Integration' 89 | notes: LinkingTo 90 | url: https://www.rcpp.org 91 | repository: https://CRAN.R-project.org/package=Rcpp 92 | authors: 93 | - family-names: Eddelbuettel 94 | given-names: Dirk 95 | email: edd@debian.org 96 | orcid: https://orcid.org/0000-0001-6419-907X 97 | - family-names: Francois 98 | given-names: Romain 99 | orcid: https://orcid.org/0000-0002-2444-4226 100 | - family-names: Allaire 101 | given-names: JJ 102 | orcid: https://orcid.org/0000-0003-0174-9868 103 | - family-names: Ushey 104 | given-names: Kevin 105 | orcid: https://orcid.org/0000-0003-2880-7407 106 | - family-names: Kou 107 | given-names: Qiang 108 | orcid: https://orcid.org/0000-0001-6786-5453 109 | - family-names: Russell 110 | given-names: Nathan 111 | - family-names: Ucar 112 | given-names: Iñaki 113 | orcid: https://orcid.org/0000-0001-6403-5550 114 | - family-names: Bates 115 | given-names: Doug 116 | orcid: https://orcid.org/0000-0001-8316-9503 117 | - family-names: Chambers 118 | given-names: John 119 | year: '2025' 120 | doi: 10.32614/CRAN.package.Rcpp 121 | - type: software 122 | title: RcppArmadillo 123 | abstract: 'RcppArmadillo: ''Rcpp'' Integration for the ''Armadillo'' Templated Linear 124 | Algebra Library' 125 | notes: LinkingTo 126 | url: https://dirk.eddelbuettel.com/code/rcpp.armadillo.html 127 | repository: https://CRAN.R-project.org/package=RcppArmadillo 128 | authors: 129 | - family-names: Eddelbuettel 130 | given-names: Dirk 131 | email: edd@debian.org 132 | orcid: https://orcid.org/0000-0001-6419-907X 133 | - family-names: Francois 134 | given-names: Romain 135 | orcid: https://orcid.org/0000-0002-2444-4226 136 | - family-names: Bates 137 | given-names: Doug 138 | orcid: https://orcid.org/0000-0001-8316-9503 139 | - family-names: Ni 140 | given-names: Binxiang 141 | - family-names: Sanderson 142 | given-names: Conrad 143 | orcid: https://orcid.org/0000-0002-0049-4501 144 | year: '2025' 145 | doi: 10.32614/CRAN.package.RcppArmadillo 146 | - type: software 147 | title: raster 148 | abstract: 'raster: Geographic Data Analysis and Modeling' 149 | notes: Imports 150 | url: https://rspatial.org/raster 151 | repository: https://CRAN.R-project.org/package=raster 152 | authors: 153 | - family-names: Hijmans 154 | given-names: Robert J. 155 | email: r.hijmans@gmail.com 156 | orcid: https://orcid.org/0000-0001-5872-2872 157 | year: '2025' 158 | doi: 10.32614/CRAN.package.raster 159 | - type: software 160 | title: dplyr 161 | abstract: 'dplyr: A Grammar of Data Manipulation' 162 | notes: Imports 163 | url: https://dplyr.tidyverse.org 164 | repository: https://CRAN.R-project.org/package=dplyr 165 | authors: 166 | - family-names: Wickham 167 | given-names: Hadley 168 | email: hadley@posit.co 169 | orcid: https://orcid.org/0000-0003-4757-117X 170 | - family-names: François 171 | given-names: Romain 172 | orcid: https://orcid.org/0000-0002-2444-4226 173 | - family-names: Henry 174 | given-names: Lionel 175 | - family-names: Müller 176 | given-names: Kirill 177 | orcid: https://orcid.org/0000-0002-1416-3412 178 | - family-names: Vaughan 179 | given-names: Davis 180 | email: davis@posit.co 181 | orcid: https://orcid.org/0000-0003-4777-038X 182 | year: '2025' 183 | doi: 10.32614/CRAN.package.dplyr 184 | - type: software 185 | title: shiny 186 | abstract: 'shiny: Web Application Framework for R' 187 | notes: Imports 188 | url: https://shiny.posit.co/ 189 | repository: https://CRAN.R-project.org/package=shiny 190 | authors: 191 | - family-names: Chang 192 | given-names: Winston 193 | email: winston@posit.co 194 | orcid: https://orcid.org/0000-0002-1576-2126 195 | - family-names: Cheng 196 | given-names: Joe 197 | email: joe@posit.co 198 | - family-names: Allaire 199 | given-names: JJ 200 | email: jj@posit.co 201 | - family-names: Sievert 202 | given-names: Carson 203 | email: carson@posit.co 204 | orcid: https://orcid.org/0000-0002-4958-2844 205 | - family-names: Schloerke 206 | given-names: Barret 207 | email: barret@posit.co 208 | orcid: https://orcid.org/0000-0001-9986-114X 209 | - family-names: Xie 210 | given-names: Yihui 211 | email: yihui@posit.co 212 | - family-names: Allen 213 | given-names: Jeff 214 | - family-names: McPherson 215 | given-names: Jonathan 216 | email: jonathan@posit.co 217 | - family-names: Dipert 218 | given-names: Alan 219 | - family-names: Borges 220 | given-names: Barbara 221 | year: '2025' 222 | doi: 10.32614/CRAN.package.shiny 223 | - type: software 224 | title: rgl 225 | abstract: 'rgl: 3D Visualization Using OpenGL' 226 | notes: Imports 227 | url: https://dmurdoch.github.io/rgl/ 228 | repository: https://CRAN.R-project.org/package=rgl 229 | authors: 230 | - family-names: Murdoch 231 | given-names: Duncan 232 | email: murdoch.duncan@gmail.com 233 | - family-names: Adler 234 | given-names: Daniel 235 | email: dadler@dyncall.org 236 | year: '2025' 237 | doi: 10.32614/CRAN.package.rgl 238 | - type: software 239 | title: stats 240 | abstract: 'R: A Language and Environment for Statistical Computing' 241 | notes: Imports 242 | authors: 243 | - name: R Core Team 244 | institution: 245 | name: R Foundation for Statistical Computing 246 | address: Vienna, Austria 247 | year: '2025' 248 | - type: software 249 | title: utils 250 | abstract: 'R: A Language and Environment for Statistical Computing' 251 | notes: Imports 252 | authors: 253 | - name: R Core Team 254 | institution: 255 | name: R Foundation for Statistical Computing 256 | address: Vienna, Austria 257 | year: '2025' 258 | - type: software 259 | title: knitr 260 | abstract: 'knitr: A General-Purpose Package for Dynamic Report Generation in R' 261 | notes: Suggests 262 | url: https://yihui.org/knitr/ 263 | repository: https://CRAN.R-project.org/package=knitr 264 | authors: 265 | - family-names: Xie 266 | given-names: Yihui 267 | email: xie@yihui.name 268 | orcid: https://orcid.org/0000-0003-0645-5666 269 | year: '2025' 270 | doi: 10.32614/CRAN.package.knitr 271 | - type: software 272 | title: rmarkdown 273 | abstract: 'rmarkdown: Dynamic Documents for R' 274 | notes: Suggests 275 | url: https://pkgs.rstudio.com/rmarkdown/ 276 | repository: https://CRAN.R-project.org/package=rmarkdown 277 | authors: 278 | - family-names: Allaire 279 | given-names: JJ 280 | email: jj@posit.co 281 | - family-names: Xie 282 | given-names: Yihui 283 | email: xie@yihui.name 284 | orcid: https://orcid.org/0000-0003-0645-5666 285 | - family-names: Dervieux 286 | given-names: Christophe 287 | email: cderv@posit.co 288 | orcid: https://orcid.org/0000-0003-4474-2498 289 | - family-names: McPherson 290 | given-names: Jonathan 291 | email: jonathan@posit.co 292 | - family-names: Luraschi 293 | given-names: Javier 294 | - family-names: Ushey 295 | given-names: Kevin 296 | email: kevin@posit.co 297 | - family-names: Atkins 298 | given-names: Aron 299 | email: aron@posit.co 300 | - family-names: Wickham 301 | given-names: Hadley 302 | email: hadley@posit.co 303 | - family-names: Cheng 304 | given-names: Joe 305 | email: joe@posit.co 306 | - family-names: Chang 307 | given-names: Winston 308 | email: winston@posit.co 309 | - family-names: Iannone 310 | given-names: Richard 311 | email: rich@posit.co 312 | orcid: https://orcid.org/0000-0003-3925-190X 313 | year: '2025' 314 | doi: 10.32614/CRAN.package.rmarkdown 315 | - type: software 316 | title: tmap 317 | abstract: 'tmap: Thematic Maps' 318 | notes: Suggests 319 | url: https://r-tmap.github.io/tmap/ 320 | repository: https://CRAN.R-project.org/package=tmap 321 | authors: 322 | - family-names: Tennekes 323 | given-names: Martijn 324 | email: mtennekes@gmail.com 325 | year: '2025' 326 | doi: 10.32614/CRAN.package.tmap 327 | - type: software 328 | title: colorRamps 329 | abstract: 'colorRamps: Builds Color Tables' 330 | notes: Suggests 331 | repository: https://CRAN.R-project.org/package=colorRamps 332 | authors: 333 | - family-names: Keitt 334 | given-names: Tim 335 | email: tkeitt@gmail.com 336 | orcid: https://orcid.org/0000-0002-4587-1083 337 | year: '2025' 338 | doi: 10.32614/CRAN.package.colorRamps 339 | - type: software 340 | title: cowplot 341 | abstract: 'cowplot: Streamlined Plot Theme and Plot Annotations for ''ggplot2''' 342 | notes: Suggests 343 | url: https://wilkelab.org/cowplot/ 344 | repository: https://CRAN.R-project.org/package=cowplot 345 | authors: 346 | - family-names: Wilke 347 | given-names: Claus O. 348 | email: wilke@austin.utexas.edu 349 | orcid: https://orcid.org/0000-0002-7470-9261 350 | year: '2025' 351 | doi: 10.32614/CRAN.package.cowplot 352 | - type: software 353 | title: magick 354 | abstract: 'magick: Advanced Graphics and Image-Processing in R' 355 | notes: Suggests 356 | url: https://docs.ropensci.org/magick/ 357 | repository: https://CRAN.R-project.org/package=magick 358 | authors: 359 | - family-names: Ooms 360 | given-names: Jeroen 361 | email: jeroenooms@gmail.com 362 | orcid: https://orcid.org/0000-0002-4035-0289 363 | year: '2025' 364 | doi: 10.32614/CRAN.package.magick 365 | - type: software 366 | title: testthat 367 | abstract: 'testthat: Unit Testing for R' 368 | notes: Suggests 369 | url: https://testthat.r-lib.org 370 | repository: https://CRAN.R-project.org/package=testthat 371 | authors: 372 | - family-names: Wickham 373 | given-names: Hadley 374 | email: hadley@posit.co 375 | year: '2025' 376 | doi: 10.32614/CRAN.package.testthat 377 | version: '>= 3.0.0' 378 | 379 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: MultiscaleDTM 2 | Title: Multi-Scale Geomorphometric Terrain Attributes 3 | Version: 1.0 4 | Authors@R: 5 | c( 6 | person(given = "Alexander", 7 | family = "Ilich", 8 | role = c("aut", "cre"), 9 | email = "ailich@usf.edu", 10 | comment = c(ORCID = "0000-0003-1758-8499")), 11 | person(given = "Vincent", 12 | family = "Lecours", 13 | role = c("aut")), 14 | person(given = "Benjamin", 15 | family = "Misiuk", 16 | role = c("aut")), 17 | person(given = "Steven", 18 | family = "Murawski", 19 | role = c("aut"))) 20 | Description: Calculates multi-scale geomorphometric terrain attributes from regularly gridded digital terrain models using a variable focal windows size (Ilich et al. (2023) ). 21 | License: GPL (>= 3) 22 | Encoding: UTF-8 23 | Roxygen: list(markdown = TRUE) 24 | RoxygenNote: 7.3.2 25 | BugReports: https://github.com/ailich/MultiscaleDTM/issues 26 | Depends: terra (>= 1.7.46) 27 | SystemRequirements: C++17 28 | LinkingTo: 29 | Rcpp, 30 | RcppArmadillo 31 | Imports: 32 | Rcpp, 33 | raster, 34 | dplyr, 35 | shiny, 36 | rgl, 37 | stats, 38 | utils 39 | Suggests: 40 | knitr, 41 | rmarkdown, 42 | tmap, 43 | colorRamps, 44 | cowplot, 45 | magick, 46 | testthat (>= 3.0.0) 47 | URL: https://ailich.github.io/MultiscaleDTM/, https://github.com/ailich/MultiscaleDTM 48 | VignetteBuilder: knitr 49 | Config/testthat/edition: 3 50 | -------------------------------------------------------------------------------- /MultiscaleDTM.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | ProjectId: e9bb442a-3ced-4688-ba69-800f98e1dcaa 3 | 4 | RestoreWorkspace: Default 5 | SaveWorkspace: Default 6 | AlwaysSaveHistory: Default 7 | 8 | EnableCodeIndexing: Yes 9 | UseSpacesForTab: Yes 10 | NumSpacesForTab: 2 11 | Encoding: UTF-8 12 | 13 | RnwWeave: Sweave 14 | LaTeX: pdfLaTeX 15 | 16 | BuildType: Package 17 | PackageUseDevtools: Yes 18 | PackageInstallArgs: --no-multiarch --with-keep.source 19 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(AdjSD) 4 | export(BPI) 5 | export(DMV) 6 | export(DirSlp) 7 | export(Pfit) 8 | export(Qfit) 9 | export(RIE) 10 | export(RelPos) 11 | export(SAPA) 12 | export(SlpAsp) 13 | export(SurfaceArea) 14 | export(TPI) 15 | export(VRM) 16 | export(annulus_window) 17 | export(circle_window) 18 | export(erupt) 19 | export(explore_terrain) 20 | import(rgl) 21 | import(shiny) 22 | import(terra) 23 | importFrom(Rcpp,sourceCpp) 24 | importFrom(dplyr,case_when) 25 | importFrom(raster,raster) 26 | importFrom(raster,stack) 27 | importFrom(raster,writeRaster) 28 | importFrom(stats,sd) 29 | importFrom(utils,compareVersion) 30 | importFrom(utils,packageVersion) 31 | useDynLib(MultiscaleDTM, .registration = TRUE) 32 | -------------------------------------------------------------------------------- /R/AdjSD.R: -------------------------------------------------------------------------------- 1 | #' Calculates standard deviation of bathymetry (a measure of rugosity) adjusted for slope 2 | #' 3 | #' Calculates standard deviation of bathymetry (a measure of rugosity). Using a sliding rectangular window a plane is fit to the data and the standard deviation of the residuals is calculated (Ilich et al., 2023) 4 | #' @param r DTM as a SpatRaster or RasterLayer in a projected coordinate system where map units match elevation/depth units 5 | #' @param w A vector of length 2 specifying the dimensions of the rectangular window to use where the first number is the number of rows and the second number is the number of columns. Window size must be an odd number. 6 | #' @param na.rm A logical indicating whether or not to remove NA values before calculations 7 | #' @param include_scale logical indicating whether to append window size to the layer names (default = FALSE) 8 | #' @param filename character Output filename. 9 | #' @param overwrite logical. If TRUE, filename is overwritten (default is FALSE). 10 | #' @param wopt list with named options for writing files as in writeRaster 11 | #' @return a SpatRaster or RasterLayer of adjusted rugosity 12 | #' @examples 13 | #' r<- erupt() 14 | #' adjsd<- AdjSD(r, w=c(5,5), na.rm = TRUE) 15 | #' plot(adjsd) 16 | #' @import terra 17 | #' @importFrom raster raster 18 | #' @importFrom raster writeRaster 19 | #' @references 20 | #' Ilich, A. R., Misiuk, B., Lecours, V., & Murawski, S. A. (2023). MultiscaleDTM: An open-source R package for multiscale geomorphometric analysis. Transactions in GIS, 27(4). https://doi.org/10.1111/tgis.13067 21 | #' @export 22 | 23 | AdjSD<- function(r, w=c(3,3), na.rm=FALSE, include_scale=FALSE, filename=NULL, overwrite=FALSE, wopt=list()){ 24 | og_class<- class(r)[1] 25 | if(og_class=="RasterLayer"){ 26 | r<- terra::rast(r) #Convert to SpatRaster 27 | } 28 | 29 | #Input checks 30 | if(!(og_class %in% c("RasterLayer", "SpatRaster"))){ 31 | stop("Error: Input must be a 'SpatRaster' or 'RasterLayer'") 32 | } 33 | if(terra::nlyr(r)!=1){ 34 | stop("Error: Input raster must be one layer.") 35 | } 36 | if(isTRUE(terra::is.lonlat(r, perhaps=FALSE))){ 37 | stop("Error: Coordinate system is Lat/Lon. Coordinate system must be projected with elevation/depth units matching map units.") 38 | } 39 | if(terra::is.lonlat(r, perhaps=TRUE, warn=FALSE)){ 40 | warning("Coordinate system may be Lat/Lon. Please ensure that the coordinate system is projected with elevation/depth units matching map units.") 41 | } 42 | if(length(w)==1){ 43 | w<- rep(w,2)} 44 | if(length(w) > 2){ 45 | stop("Specified window exceeds 2 dimensions")} 46 | if(any(0 == (w %% 2))){ 47 | stop("Error: w must be odd")} 48 | if(all(w<3)){ 49 | stop("Error: w must be greater or equal to 3 in at least one dimension") 50 | } 51 | if(prod(w) < 4){ 52 | stop("Error: Window size must have at least 4 cells to fit surface and have residuals") 53 | } 54 | 55 | #Define local coordinate system of window 56 | x_mat<- matrix(data = seq(from = (-xres(r) * floor(w[2]/2)), to = (xres(r) * floor(w[2]/2)), length.out = w[2]), nrow = w[1], ncol=w[2], byrow=TRUE) 57 | x<- as.vector(t(x_mat)) #Transpose for focal 58 | y_mat<- matrix(data = seq(from = (yres(r) * floor(w[1]/2)), to = (-yres(r) * floor(w[1]/2)), length.out = w[1]), nrow = w[1], ncol=w[2]) 59 | y<- as.vector(t(y_mat)) #Transpose for focal 60 | 61 | #Explanatory Variable matrix X for linear fit 62 | X<- cbind(x, y, 1) #Z = dx+ey+f 63 | 64 | if(!na.rm){ 65 | Xt<- t(X) 66 | XtX_inv<- solve(Xt %*% X) 67 | } 68 | 69 | #Fit Quadratic and Extract Residuals 70 | if(na.rm){ 71 | out<- terra::focalCpp(r, w=w, fun = C_AdjSD_narmT, X_full= X, fillvalue=NA, wopt=wopt) 72 | } else{ 73 | out<- terra::focalCpp(r, w=w, fun = C_AdjSD_narmF, X= X, Xt=Xt, XtX_inv=XtX_inv, fillvalue=NA, wopt=wopt) 74 | } 75 | 76 | names(out)<- "adjSD" 77 | if(include_scale){names(out)<- paste0(names(out), "_", w[1],"x", w[2])} #Add scale to layer names 78 | 79 | # Return 80 | if(og_class =="RasterLayer"){ 81 | out<- raster::raster(out) 82 | if(!is.null(filename)){ 83 | return(raster::writeRaster(out, filename=filename, overwrite=overwrite)) 84 | } 85 | } 86 | if(!is.null(filename)){ 87 | return(terra::writeRaster(out, filename=filename, overwrite=overwrite, wopt=wopt)) 88 | } 89 | return(out) 90 | } 91 | -------------------------------------------------------------------------------- /R/BPI.R: -------------------------------------------------------------------------------- 1 | #' Calculates Bathymetric Position Index 2 | #' 3 | #' Calculates Bathymetric Position Index (BPI). BPI is a measure of relative position that calculates the difference between the value of the focal cell and the mean of cells contained within an annulus shaped neighborhood. Positive values indicate local highs (i.e. peaks) and negative values indicate local lows (i.e. depressions). BPI can be expressed in units of the input DTM raster or can standardized relative to the local topography by dividing by the standard deviation or range of included elevation values in the focal window. BPI calls the function RelPos internally which serves as a general purpose and more flexible function for calculating relative position. 4 | #' @param r r DTM as a SpatRaster or RasterLayer. 5 | #' @param w Vector of length 2 specifying c(inner, outer) radii of the annulus in "cell" or "map" units or a focal weights matrix created by MultiscaleDTM::annulus_window. Inner radius must be less than or equal to outer radius. There is no default size. 6 | #' @param stand Standardization method. Either "none" (the default), "range" or "sd" indicating whether the relative position should be standardized by dividing by the standard deviation or range of included values in the focal window. If stand is 'none' the layer name will be "bpi", otherwise it will be "sbpi" to indicate that the layer has been standardized. 7 | #' @param unit Unit for w if it is a vector (default is unit="cell"). If w is a matrix, unit is ignored and extracted directly from w. 8 | #' @param na.rm Logical indicating whether or not to remove NA values before calculations. 9 | #' @param include_scale Logical indicating whether to append window size to the layer names (default = FALSE) or a character vector specifying the name you would like to append or a number specifying the number of significant digits. If include_scale = TRUE the appended scale will be the inner and outer radius. If unit="map" then window size will have "MU" after the number indicating that the number represents the scale in map units (note units can be extracted from w created with MultiscaleDTM::circle_window and MultiscaleDTM::annulus_window). 10 | #' @param filename Character output filename. 11 | #' @param overwrite Logical. If TRUE, filename is overwritten (default is FALSE). 12 | #' @param wopt List with named options for writing files as in writeRaster. 13 | #' @return A SpatRaster or RasterLayer. 14 | #' @examples 15 | #' r<- erupt() 16 | #' bpi<- BPI(r, w = c(2,4), stand= "none", unit = "cell", na.rm = TRUE) 17 | #' plot(bpi) 18 | #' @import terra 19 | #' @importFrom raster raster 20 | #' @importFrom raster writeRaster 21 | #' @importFrom dplyr case_when 22 | #' @references 23 | #' Lundblad, E.R., Wright, D.J., Miller, J., Larkin, E.M., Rinehart, R., Naar, D.F., Donahue, B.T., Anderson, S.M., Battista, T., 2006. A benthic terrain classification scheme for American Samoa. Marine Geodesy 29, 89–111. https://doi.org/10.1080/01490410600738021 24 | #' @export 25 | 26 | BPI<- function(r, w, stand="none", 27 | unit="cell", 28 | na.rm=FALSE, include_scale =FALSE, 29 | filename=NULL, overwrite=FALSE, wopt=list()){ 30 | og_class<- class(r)[1] 31 | if(og_class=="RasterLayer"){ 32 | r<- terra::rast(r) #Convert to SpatRaster 33 | } 34 | unit<- tolower(unit) 35 | #Input checks 36 | if(!(og_class %in% c("RasterLayer", "SpatRaster"))){ 37 | stop("Error: Input must be a 'SpatRaster' or 'RasterLayer'") 38 | } 39 | 40 | bpi<- MultiscaleDTM::RelPos(r, w=w, shape= "annulus", stand=stand, 41 | exclude_center= FALSE, unit=unit, fun="mean", na.rm=na.rm, 42 | include_scale =include_scale, filename=NULL, overwrite=FALSE, wopt=list()) 43 | names(bpi)<- gsub(pattern = "rpos", replacement = "bpi", names(bpi)) 44 | 45 | #Return 46 | if(og_class =="RasterLayer"){ 47 | bpi<- raster::raster(bpi) 48 | if(!is.null(filename)){ 49 | return(raster::writeRaster(bpi, filename=filename, overwrite=overwrite)) 50 | } 51 | } 52 | if(!is.null(filename)){ 53 | return(terra::writeRaster(bpi, filename=filename, overwrite=overwrite, wopt=wopt)) 54 | } 55 | return(bpi) 56 | } -------------------------------------------------------------------------------- /R/DMV.R: -------------------------------------------------------------------------------- 1 | #' Calculates Difference from Mean Value (DMV) 2 | #' 3 | #' Calculates Difference from Mean Value (DMV). DMV is a measure of relative position that calculates the difference between the value of the focal cell and the mean of all cells in a rectangular or circular neighborhood. Positive values indicate local highs (i.e. peaks) and negative values indicate local lows (i.e. depressions). DMV can be expressed in units of the input DTM raster or can standardized relative to the local topography by dividing by the standard deviation or range of elevation values in the focal window. DMV calls the function RelPos internally which serves as a general purpose and more flexible function for calculating relative position. 4 | #' @param r DTM as a SpatRaster or RasterLayer. 5 | #' @param w For a "rectangle" focal window, a vector of length 2 containing odd numbers specifying dimensions where the first number is the number of rows and the second is the number of columns (or a single number if the number of rows and columns is equal). For a "circle" shaped focal window, a single integer representing the radius in "cell" or "map" units or a focal weights matrix created by MultiscaleDTM::circle_window. 6 | #' @param shape Character representing the shape of the focal window. Either "rectangle" (default) or "circle". 7 | #' @param stand Standardization method. Either "none" (the default), "range" or "sd" indicating whether the TPI should be standardized by dividing by the standard deviation or range of included values in the focal window. If stand is 'none' the layer name will be "dmv", otherwise it will be "sdmv" to indicate that the layer has been standardized. 8 | #' @param unit Unit for w if shape is 'circle' and it is a vector (default is unit="cell"). For circular windows specified with a matrix, unit is ignored and extracted directly from w. For rectangular and custom focal windows set unit='cell' or set unit to NA/NULL. 9 | #' @param na.rm Logical indicating whether or not to remove NA values before calculations. 10 | #' @param include_scale Logical indicating whether to append window size to the layer names (default = FALSE) or a character vector specifying the name you would like to append or a number specifying the number of significant digits. If include_scale = TRUE the number of rows and number of columns will be appended for rectangular windows. For circular windows it will be a single number representing the radius. If unit="map" then window size will have "MU" after the number indicating that the number represents the scale in map units (note units can be extracted from w created with MultiscaleDTM::circle_window). 11 | #' @param filename Character output filename. 12 | #' @param overwrite Logical. If TRUE, filename is overwritten (default is FALSE). 13 | #' @param wopt List with named options for writing files as in writeRaster. 14 | #' @return a SpatRaster or RasterLayer. 15 | #' @examples 16 | #' r<- erupt() 17 | #' dmv<- DMV(r, w=c(5,5), shape= "rectangle", stand="range", na.rm = TRUE) 18 | #' plot(dmv) 19 | #' @import terra 20 | #' @importFrom raster raster 21 | #' @importFrom raster writeRaster 22 | #' @importFrom dplyr case_when 23 | #' @references 24 | #' Lecours, V., Devillers, R., Simms, A.E., Lucieer, V.L., Brown, C.J., 2017. Towards a Framework for Terrain Attribute Selection in Environmental Studies. Environmental Modelling & Software 89, 19-30. https://doi.org/10.1016/j.envsoft.2016.11.027 25 | #' Wilson, J.P., Gallant, J.C. (Eds.), 2000. Terrain Analysis: Principles and Applications. John Wiley & Sons, Inc. 26 | #' @export 27 | 28 | DMV<- function(r, w = dplyr::case_when(tolower(shape)=="rectangle" ~ 3, 29 | tolower(shape)=="circle" & isTRUE(tolower(unit)=="cell") ~ 1, 30 | tolower(shape)=="circle" & isTRUE(tolower(unit)=="map") ~ max(terra::res(r))), 31 | shape= "rectangle", stand="none", unit="cell", na.rm=FALSE, 32 | include_scale=FALSE, filename=NULL, overwrite=FALSE, wopt=list()){ 33 | og_class<- class(r)[1] 34 | if(og_class=="RasterLayer"){ 35 | r<- terra::rast(r) #Convert to SpatRaster 36 | } 37 | #Input checks 38 | if(!(og_class %in% c("RasterLayer", "SpatRaster"))){ 39 | stop("Error: Input must be a 'SpatRaster' or 'RasterLayer'") 40 | } 41 | if (!(shape %in% c("rectangle", "circle"))){ 42 | stop("Error: shape must be 'rectangle' or 'circle'") 43 | } 44 | dmv<- MultiscaleDTM::RelPos(r, w=w, shape= shape, stand=stand, exclude_center= FALSE, 45 | unit=unit, fun="mean", na.rm=na.rm, include_scale =include_scale, filename=filename, overwrite=overwrite, wopt=wopt) 46 | names(dmv)<- gsub(pattern = "rpos", replacement = "dmv", names(dmv)) 47 | 48 | #Return 49 | if(og_class =="RasterLayer"){ 50 | dmv<- raster::raster(dmv) 51 | if(!is.null(filename)){ 52 | return(raster::writeRaster(dmv, filename=filename, overwrite=overwrite)) 53 | } 54 | } 55 | if(!is.null(filename)){ 56 | return(terra::writeRaster(dmv, filename=filename, overwrite=overwrite, wopt=wopt)) 57 | } 58 | return(dmv) 59 | } 60 | -------------------------------------------------------------------------------- /R/DirSlope.R: -------------------------------------------------------------------------------- 1 | #' Directional Slope 2 | #' 3 | #' Calculates the slope along a specified direction. Upslope values are positive and downslope values are negative. 4 | #' @param alpha Angle (in specified 'unit') at which you would like to calculate slope. 0 represents up in map direction (usually North) and it increases clockwise. This can be a single number or it can be a raster of cell values. 5 | #' @param dz.dx The change in elevation per unit distance in the x direction as a SpatRaster, RasterLayer, or a single number. Positive is to the right. See details for more. 6 | #' @param dz.dy The change in elevation per unit distance in the y direction as a SpatRaster or RasterLayer,or a single number Positive is up. See details for more. 7 | #' @param unit "degrees" or "radians" (default is "degrees") 8 | #' @param abs logical indicating whether or not to return the absolute value of slope (default is FALSE) 9 | #' @param include_dir logical indicating whether to append direction to layer name (default is FALSE) 10 | #' @param filename character Output filename. Can be a single filename, or as many filenames as there are layers to write a file for each layer 11 | #' @param overwrite logical. If TRUE, filename is overwritten (default is FALSE). 12 | #' @param wopt list with named options for writing files as in writeRaster 13 | #' @import terra 14 | #' @importFrom raster raster 15 | #' @importFrom raster stack 16 | #' @importFrom raster writeRaster 17 | #' @return a SpatRaster or RasterStack of slope and/or aspect (and components of aspect) 18 | #' @examples 19 | #' r<- erupt() 20 | #' dz1<- SlpAsp(r, metrics = c("dz.dx", "dz.dy")) 21 | #' dz2<- Qfit(r, metrics = c(), return_params = TRUE, as_derivs=TRUE) 22 | #' dz3<- Pfit(r, metrics = c("dz.dx", "dz.dy")) 23 | #' dirslp1<- DirSlp(alpha = 45, dz.dx= dz1$dz.dx, dz.dy= dz1$dz.dy) 24 | #' dirslp2<- DirSlp(alpha = 45, dz.dx= dz2$zx, dz.dy= dz2$zy) 25 | #' dirslp3<- DirSlp(alpha = 45, dz.dx= dz3$dz.dx, dz.dy= dz3$dz.dy) 26 | #' @details dz.dx and dz.dy can be calculated at a specified scale via `SlpAsp`, `Pfit`, `Qfit` (zx and zy), or from an existing layer calculated by another program. 27 | #' @references 28 | #' Neteler, M., & Mitasova, H. (2008). Open source GIS: A GRASS GIS approach (3rd ed.). Springer. 29 | #' @import terra 30 | #' @importFrom raster raster 31 | #' @importFrom raster stack 32 | #' @export 33 | 34 | DirSlp<- function(alpha, dz.dx, dz.dy, unit = "degrees", abs=FALSE, include_dir=FALSE, filename=NULL, overwrite=FALSE, wopt=list()){ 35 | og_class<- c(class(alpha)[1], class(dz.dx)[1], class(dz.dy)[1]) 36 | if(og_class[1]=="RasterLayer"){ 37 | alpha<- terra::rast(alpha) 38 | } 39 | if(og_class[2]=="RasterLayer"){ 40 | dz.dx<- terra::rast(dz.dx) 41 | } 42 | if(og_class[3]=="RasterLayer"){ 43 | dz.dy<- terra::rast(dz.dy) 44 | } 45 | 46 | #Input checks 47 | if(any(!(og_class %in% c("RasterLayer", "SpatRaster", "integer", "numeric")))){ 48 | stop("Error: Inputs 'alpha', 'dz.dx', and 'dz.dy' must be a 'SpatRaster', 'RasterLayer', 'integer', or 'numeric'") 49 | } 50 | 51 | unit<- tolower(unit) #make lowercase 52 | if(!(unit %in% c("degrees", "radians"))){ 53 | stop("unit must be 'degrees' or 'radians'") 54 | } 55 | 56 | if(class(alpha)[1]=="SpatRaster"){ 57 | direction<- "variable" 58 | } else{ 59 | direction<- alpha 60 | } 61 | 62 | if(unit=="degrees"){ 63 | alpha<- alpha*(pi/180) # convert to radians 64 | } 65 | 66 | if(class(alpha)[1]=="SpatRaster"){ 67 | dir_slp<- terra::math((dz.dx * terra::math(alpha, fun="sin", wopt=wopt)) + (dz.dy * terra::math(alpha, fun="cos", wopt=wopt)), fun="atan", wopt=wopt) 68 | } else{ 69 | dir_slp<- atan(dz.dx * sin(alpha) + (dz.dy) * cos(alpha)) 70 | } 71 | if(unit=="degrees"){ 72 | dir_slp<- dir_slp*(180/pi) # convert to degrees 73 | names(dir_slp)<- "dirslope" 74 | } 75 | 76 | if(abs){ 77 | if(class(dir_slp)[1]=="SpatRaster"){ 78 | dir_slp<- terra::math(dir_slp, fun="abs", wopt=wopt) 79 | } else{ 80 | dir_slp<- abs(dir_slp) 81 | } 82 | } 83 | 84 | if(include_dir){names(dir_slp)<- paste0(names(dir_slp), "_", direction)} 85 | 86 | #Return 87 | if(og_class[2]=="RasterLayer"){ 88 | dir_slp<- raster::raster(dir_slp) 89 | if(!is.null(filename)){ 90 | return(raster::writeRaster(dir_slp, filename=filename, overwrite=overwrite)) 91 | } 92 | } 93 | 94 | if(!is.null(filename)){ 95 | return(terra::writeRaster(dir_slp, filename=filename, overwrite=overwrite, wopt=wopt)) 96 | } 97 | return(dir_slp) 98 | } 99 | -------------------------------------------------------------------------------- /R/MultiscaleDTM-package.R: -------------------------------------------------------------------------------- 1 | ## usethis namespace: start 2 | #' @useDynLib MultiscaleDTM, .registration = TRUE 3 | ## usethis namespace: end 4 | NULL 5 | ## usethis namespace: start 6 | #' @importFrom Rcpp sourceCpp 7 | ## usethis namespace: end 8 | NULL -------------------------------------------------------------------------------- /R/Pfit.R: -------------------------------------------------------------------------------- 1 | #' Calculates multiscale slope and aspect using a local planar fit. 2 | #' 3 | #' Calculates multiscale slope and aspect of a DTM over a sliding rectangular window using a using a local planar fit to the surface (Sharpnack and Akin 1969). 4 | #' @param r DTM as a SpatRaster (terra) or RasterLayer (raster) in a projected coordinate system where map units match elevation/depth units (up is assumed to be north for calculations of aspect, northness, and eastness). 5 | #' @param w Vector of length 2 specifying the dimensions of the rectangular window to use where the first number is the number of rows and the second number is the number of columns. Window size must be an odd number. Default is 3x3. 6 | #' @param unit "degrees" or "radians". 7 | #' @param metrics Character vector specifying which terrain attributes to return. The default is to return c("pslope", "paspect", "peastness", and "pnorthness"). These are preceded with a 'p' to differentiate them from the measures calculated by SlpAsp() and 'Qfit' where the 'p' indicates that a planar surface was used for the calculation. Additional measures available include "dz.dx" and "dz.dy" which are the x and y components of slope respectively. 8 | #' @param na.rm Logical indicating whether or not to remove NA values before calculations. 9 | #' @param include_scale logical indicating whether to append window size to the layer names (default = FALSE) 10 | #' @param mask_aspect Logical. If TRUE (default), aspect will be set to NA and northness and eastness will be set to 0 when slope = 0. If FALSE, aspect is set to 270 degrees or 3*pi/2 radians ((-pi/2)- atan2(0,0)+2*pi) and northness and eastness will be calculated from this. 11 | #' @param filename character Output filename. Can be a single filename, or as many filenames as there are layers to write a file for each layer 12 | #' @param overwrite logical. If TRUE, filename is overwritten (default is FALSE). 13 | #' @param wopt list with named options for writing files as in writeRaster 14 | #' @return a SpatRaster (terra) or RasterStack/RasterLayer (raster) 15 | #' @examples 16 | #' r<- erupt() 17 | #' pmetrics<- Pfit(r, w = c(5,5), unit = "degrees", na.rm = TRUE) 18 | #' plot(pmetrics) 19 | #' @import terra 20 | #' @importFrom raster raster 21 | #' @importFrom raster stack 22 | #' @importFrom raster writeRaster 23 | #' @details 24 | #' If only first order derivatives are needed, Pfit is faster than Qfit and should provide equivalent results to Qfit for first order derivatives (Jones, 1998) when na.rm=FALSE and approximately the same results otherwise. 25 | #' 26 | #' @references 27 | #' Evans, I.S., 1980. An integrated system of terrain analysis and slope mapping. Zeitschrift f¨ur Geomorphologic Suppl-Bd 36, 274–295. 28 | #' Jones, K. H. (1998). A comparison of algorithms used to compute hill slope as a property of the DEM. Computers & Geosciences, 24(4), 315–323. https://doi.org/10.1016/S0098-3004(98)00032-6 29 | #' Wood, J., 1996. The geomorphological characterisation of digital elevation models (Ph.D.). University of Leicester. 30 | #' @export 31 | 32 | Pfit<- function(r, w=c(3,3), unit= "degrees", metrics = c("pslope", "paspect", "peastness", "pnorthness"), na.rm=FALSE, include_scale=FALSE, mask_aspect=TRUE, filename=NULL, overwrite=FALSE, wopt=list()){ 33 | all_metrics<- c("pslope", "paspect", "peastness", "pnorthness", "dz.dx", "dz.dy") 34 | og_class<- class(r)[1] 35 | if(og_class=="RasterLayer"){ 36 | r<- terra::rast(r) #Convert to SpatRaster 37 | } 38 | 39 | # Input checks 40 | if(!(og_class %in% c("RasterLayer", "SpatRaster"))){ 41 | stop("Error: Input must be a 'SpatRaster' or 'RasterLayer'") 42 | } 43 | if(terra::nlyr(r)!=1){ 44 | stop("Error: Input raster must be one layer.") 45 | } 46 | if(isTRUE(terra::is.lonlat(r, perhaps=FALSE))){ 47 | stop("Error: Coordinate system is Lat/Lon. Coordinate system must be projected with elevation/depth units matching map units.") 48 | } 49 | if(terra::is.lonlat(r, perhaps=TRUE, warn=FALSE)){ 50 | warning("Coordinate system may be Lat/Lon. Please ensure that the coordinate system is projected with elevation/depth units matching map units.") 51 | } 52 | if(length(w)==1){ 53 | w<- rep(w,2)} 54 | if(any(0 == (w %% 2))){ 55 | stop("Error: w must be odd")} 56 | if(length(w) > 2){ 57 | stop("Specified window exceeds 2 dimensions")} 58 | if(all(w<3)){ 59 | stop("Error: w must be greater or equal to 3 in at least one dimension") 60 | } 61 | 62 | unit<- tolower(unit) #make lowercase 63 | if (!any(unit==c("degrees", "radians"))){ 64 | stop("unit must be 'degrees' or 'radians'") 65 | } 66 | 67 | # Increase tolerance of inputs to metrics 68 | metrics<- tolower(metrics) #Make all lowercase 69 | metrics[metrics=="slope"]<- "pslope" #replace slope with pslope 70 | metrics[metrics=="aspect"]<- "paspect" #replace aspect with paspect 71 | metrics[metrics=="eastness"]<- "peastness" #replace eastness with peastness 72 | metrics[metrics=="northness"]<- "pnorthness" #replace northness with pnorthness 73 | 74 | if (any(!(metrics %in% all_metrics))){ 75 | stop("Error: Invlaid metric. Valid metrics include 'pslope', 'paspect', 'peastness', 'pnorthness', 'dz.dx', and 'dz.dy'.") 76 | } 77 | 78 | needed_metrics<- metrics 79 | 80 | if(any(c("peastness", "pnorthness") %in% needed_metrics) & (!("paspect" %in% needed_metrics))){ 81 | needed_metrics<- c(needed_metrics, "paspect") 82 | } 83 | 84 | if(mask_aspect & ("paspect" %in% needed_metrics) & (!("pslope" %in% needed_metrics))){ 85 | needed_metrics<- c(needed_metrics, "pslope") 86 | } 87 | 88 | if(any(c("pslope", "paspect", "peastness", "pnorthness") %in% needed_metrics) & !("dz.dx" %in% needed_metrics)){ 89 | needed_metrics<- c(needed_metrics, "dz.dx") 90 | } 91 | 92 | if(any(c("pslope", "paspect", "peastness", "pnorthness") %in% needed_metrics) & !("dz.dy" %in% needed_metrics)){ 93 | needed_metrics<- c(needed_metrics, "dz.dy") 94 | } 95 | 96 | #Define local coordinate system of window 97 | 98 | x_mat<- matrix(data = seq(from = (-xres(r) * floor(w[2]/2)), to = (xres(r) * floor(w[2]/2)), length.out = w[2]), nrow = w[1], ncol=w[2], byrow=TRUE) 99 | x<- as.vector(t(x_mat)) #Transpose for focal 100 | 101 | y_mat<- matrix(data = seq(from = (yres(r) * floor(w[1]/2)), to = (-yres(r) * floor(w[1]/2)), length.out = w[1]), nrow = w[1], ncol=w[2]) 102 | y<- as.vector(t(y_mat)) #Transpose for focal 103 | 104 | # forcing through center doesn't affect slope (Jones, 1998) so don't bother estimating the extra coefficient unless na.rm=TRUE 105 | # If na.rm = TRUE, you want to be able to get the slope even if the central cell is NA 106 | X<- cbind(x, y) # Z = dx+ey (no intercept) 107 | 108 | if(na.rm){ 109 | X<- cbind(X,1) 110 | } # Z = dx+ey+f (with intercept) 111 | 112 | if(!na.rm){ 113 | Xt<- t(X) 114 | XtX_inv<- solve(Xt %*% X) 115 | } 116 | 117 | # Calculate Regression Parameters 118 | if(na.rm){ 119 | params<- terra::focalCpp(r, w=w, fun = C_Qfit1_narmT, X_full= X, return_intercept = FALSE, fillvalue=NA, wopt=wopt) 120 | } else{ 121 | params<- terra::focalCpp(r, w=w, fun = C_Qfit2_narmF, X= X, Xt= Xt, XtX_inv= XtX_inv, fillvalue=NA, wopt=wopt) 122 | } 123 | names(params)<- c("dz.dx", "dz.dy") 124 | 125 | out<- terra::rast() #Initialize output 126 | 127 | if("dz.dx" %in% needed_metrics){ 128 | out<- c(out, params$dz.dx, warn=FALSE) 129 | } 130 | 131 | if("dz.dy" %in% needed_metrics){ 132 | out<- c(out, params$dz.dy, warn=FALSE) 133 | } 134 | 135 | #Use regression parameters to calculate slope and aspect 136 | if("pslope" %in% needed_metrics){ 137 | slp<- terra::math(terra::math(params$dz.dx^2 + params$dz.dy^2, fun="sqrt", wopt=wopt), fun="atan", wopt=wopt) 138 | if(unit=="degrees"){ 139 | slp<- slp*(180/pi) 140 | } 141 | names(slp)<- "pslope" 142 | out<- c(out, slp, warn=FALSE) 143 | } 144 | 145 | if("paspect" %in% needed_metrics){ 146 | asp<- (-pi/2) - terra::atan_2(params$dz.dy,params$dz.dx, wopt=wopt) # aspect relative to North 147 | asp<- ifel(asp < 0, yes = asp+(2*pi), no= asp, wopt=wopt) # Constrain range so between 0 and 2pi 148 | 149 | if (mask_aspect & ("paspect" %in% metrics)){ 150 | asp<- terra::mask(asp, mask= slp, maskvalues = 0, updatevalue = NA, wopt=wopt) #Set aspect to undefined where slope is zero (doesn't need to be needed_metrics since also mask northness and eastness as 0). 151 | } 152 | 153 | if("peastness" %in% needed_metrics){ 154 | eastness<- sin(asp) 155 | if (mask_aspect){ 156 | eastness<- terra::mask(eastness, mask= slp, maskvalues = 0, updatevalue = 0, wopt=wopt) #Set eastness to 0 where slope is zero 157 | } 158 | names(eastness)<- "peastness" 159 | out<- c(out, eastness, warn=FALSE) 160 | } 161 | 162 | if("pnorthness" %in% needed_metrics){ 163 | northness<- cos(asp) 164 | if (mask_aspect){ 165 | northness<- terra::mask(northness, mask= slp, maskvalues = 0, updatevalue = 0, wopt=wopt) #Set northness to 0 where slope is zero 166 | } 167 | names(northness)<- "pnorthness" 168 | out<- c(out, northness, warn=FALSE) 169 | } 170 | if(unit=="degrees"){ 171 | asp<- asp*180/pi 172 | } 173 | names(asp)<- "paspect" 174 | out<- c(out, asp, warn=FALSE) 175 | } 176 | 177 | out<- terra::subset(out, metrics, wopt=wopt) #Subset needed metrics to requested metrics in proper order 178 | 179 | if(include_scale){names(out)<- paste0(names(out), "_", w[1],"x", w[2])} #Add scale to layer names 180 | 181 | #Return 182 | if(og_class=="RasterLayer"){ 183 | if(terra::nlyr(out) > 1){ 184 | out<- raster::stack(out) #Convert to RasterStack 185 | if(!is.null(filename)){ 186 | if(length(filename)==1){ 187 | return(raster::writeRaster(out, filename=filename, overwrite=overwrite, bylayer=FALSE)) 188 | } else{ 189 | return(raster::writeRaster(out, filename=filename, overwrite=overwrite, bylayer=TRUE)) 190 | } 191 | } 192 | } else{ 193 | out<- raster::raster(out) 194 | if(!is.null(filename)){ 195 | return(raster::writeRaster(out, filename=filename, overwrite=overwrite)) 196 | } 197 | } 198 | } 199 | if(!is.null(filename)){ 200 | return(terra::writeRaster(out, filename=filename, overwrite=overwrite, wopt=wopt)) 201 | } 202 | return(out) 203 | } 204 | -------------------------------------------------------------------------------- /R/RIE.R: -------------------------------------------------------------------------------- 1 | #' Calculates Roughness Index-Elevation 2 | #' 3 | #' Calculates Roughness Index-Elevation. This is the standard deviation of residual topography in a focal window where residual topography is calculated as the focal pixel minus the focal mean. 4 | #' @param r DTM as a SpatRaster or RasterLayer 5 | #' @param w A vector of length 2 specifying the dimensions of the rectangular window to use where the first number is the number of rows and the second number is the number of columns. Window size must be an odd number. Default is 3x3. 6 | #' @param na.rm A logical indicating whether or not to remove NA values before calculation of SD 7 | #' @param include_scale logical indicating whether to append window size to the layer names (default = FALSE) 8 | #' @param filename character Output filename. 9 | #' @param overwrite logical. If TRUE, filename is overwritten (default is FALSE). 10 | #' @param wopt list with named options for writing files as in writeRaster 11 | #' @return a SpatRaster or RasterLayer 12 | #' @examples 13 | #' r<- erupt() 14 | #' rie<- RIE(r, w=c(5,5), na.rm = TRUE) 15 | #' plot(rie) 16 | #' @import terra 17 | #' @importFrom raster raster 18 | #' @importFrom raster writeRaster 19 | #' @importFrom stats sd 20 | #' @details Note the original paper by Cavalli et al (2008) uses a fixed 5x5 window and uses 25 as the denominator indicating use of the population standard deviation. This implementation provides a flexible window size and istead calculates the sample standard deviation which uses a denominator of n-1. 21 | #' @references 22 | #' Cavalli, M., Tarolli, P., Marchi, L., Dalla Fontana, G., 2008. The effectiveness of airborne LiDAR data in the recognition of channel-bed morphology. CATENA 73, 249–260. https://doi.org/10.1016/j.catena.2007.11.001 23 | #' @export 24 | #' 25 | 26 | RIE<- function(r, w=c(3,3), na.rm=FALSE, include_scale=FALSE, filename=NULL, overwrite=FALSE, wopt=list()){ 27 | og_class<- class(r)[1] 28 | if(og_class=="RasterLayer"){ 29 | r<- terra::rast(r) #Convert to SpatRaster 30 | } 31 | #Input checks 32 | if(!(og_class %in% c("RasterLayer", "SpatRaster"))){ 33 | stop("Error: Input must be a 'SpatRaster' or 'RasterLayer'") 34 | } 35 | if(terra::nlyr(r)!=1){ 36 | stop("Error: Input raster must be one layer.") 37 | } 38 | if(length(w)==1){w<- rep(w, 2)} 39 | if(length(w) > 2){ 40 | stop("Specified window exceeds 2 dimensions")} 41 | if(any(0 == (w %% 2))){ 42 | stop("Error: w must be odd")} 43 | if(all(w<3)){ 44 | stop("Error: w must be greater or equal to 3 in at least one dimension") 45 | } 46 | 47 | resid_topo<- r - terra::focal(x = r, w = w, fun = mean, na.rm = FALSE, wopt=wopt) #Mean is only equal to fitted value if all values present 48 | rie<- terra::focal(x = resid_topo, w = w, fun = sd, na.rm = na.rm, wopt=wopt) 49 | names(rie)<- "rie" 50 | if(include_scale){names(rie)<- paste0(names(rie), "_", w[1],"x", w[2])} #Add scale to layer names 51 | 52 | #Return 53 | if(og_class =="RasterLayer"){ 54 | rie<- raster::raster(rie) 55 | if(!is.null(filename)){ 56 | return(raster::writeRaster(rie, filename=filename, overwrite=overwrite)) 57 | } 58 | } 59 | if(!is.null(filename)){ 60 | return(terra::writeRaster(rie, filename=filename, overwrite=overwrite, wopt=wopt)) 61 | } 62 | return(rie) 63 | } -------------------------------------------------------------------------------- /R/RcppExports.R: -------------------------------------------------------------------------------- 1 | # Generated by using Rcpp::compileAttributes() -> do not edit by hand 2 | # Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 3 | 4 | C_AdjSD_narmT <- function(z, X_full, ni, nw) { 5 | .Call(`_MultiscaleDTM_C_AdjSD_narmT`, z, X_full, ni, nw) 6 | } 7 | 8 | C_AdjSD_narmF <- function(z, X, Xt, XtX_inv, ni, nw) { 9 | .Call(`_MultiscaleDTM_C_AdjSD_narmF`, z, X, Xt, XtX_inv, ni, nw) 10 | } 11 | 12 | C_CountVals <- function(z, ni, nw) { 13 | .Call(`_MultiscaleDTM_C_CountVals`, z, ni, nw) 14 | } 15 | 16 | C_Qfit1_narmT <- function(z, X_full, return_intercept, ni, nw) { 17 | .Call(`_MultiscaleDTM_C_Qfit1_narmT`, z, X_full, return_intercept, ni, nw) 18 | } 19 | 20 | C_Qfit1_narmF <- function(z, X, Xt, XtX_inv, return_intercept, ni, nw) { 21 | .Call(`_MultiscaleDTM_C_Qfit1_narmF`, z, X, Xt, XtX_inv, return_intercept, ni, nw) 22 | } 23 | 24 | C_Qfit2_narmT <- function(z, X_full, ni, nw) { 25 | .Call(`_MultiscaleDTM_C_Qfit2_narmT`, z, X_full, ni, nw) 26 | } 27 | 28 | C_Qfit2_narmF <- function(z, X, Xt, XtX_inv, ni, nw) { 29 | .Call(`_MultiscaleDTM_C_Qfit2_narmF`, z, X, Xt, XtX_inv, ni, nw) 30 | } 31 | 32 | C_TriArea <- function(a, b, c) { 33 | .Call(`_MultiscaleDTM_C_TriArea`, a, b, c) 34 | } 35 | 36 | C_SurfaceArea <- function(z, x_res, y_res, na_rm, ni, nw) { 37 | .Call(`_MultiscaleDTM_C_SurfaceArea`, z, x_res, y_res, na_rm, ni, nw) 38 | } 39 | 40 | -------------------------------------------------------------------------------- /R/RelPos.R: -------------------------------------------------------------------------------- 1 | #' Creates circular focal window 2 | #' 3 | #' Creates circular focal window around central pixel. 4 | #' @param radius radius of circular window 5 | #' @param resolution resolution of intended raster layer (one number or a vector of length 2). Only necessary if unit= "map" 6 | #' @param unit unit for radius. Either "cell" (number of cells) or "map" for map units (e.g. meters). 7 | #' @param return_dismat logical, if TRUE return a matrix of distances from focal cell instead of a matrix to pass to terra::focal. 8 | #' @return a matrix of 1's and NA's showing which cells to include and exclude respectively in focal calculations, or if return_dismat=TRUE, a matrix indicating the distance from the focal cell. It also contains attributes attributes 'unit', 'scale', and 'shape' if return_dismat=FALSE, and if return_dismat=TRUE the attribute 'unit'. 9 | #' @export 10 | circle_window<- function(radius, unit, resolution, return_dismat = FALSE){ 11 | unit<- tolower(unit) 12 | if (unit != "map" & unit != "cell"){ 13 | stop("Error: unit must equal 'map' or 'cell'") 14 | } 15 | if(length(radius)!=1){ 16 | stop("Error: radius must be a single integer") 17 | } 18 | if(unit=="cell"){ 19 | resolution<- c(1,1) 20 | } 21 | if (length(resolution)==1){ 22 | resolution<- rep(resolution,2) 23 | } 24 | nrows<- floor((radius/resolution[2])*2+1) 25 | ncols<- floor((radius/resolution[1])*2+1) 26 | if(nrows %% 2 ==0){ 27 | nrows<- nrows+1 28 | } 29 | if(ncols %% 2 ==0){ 30 | ncols<- ncols+1 31 | } #nrow and ncol must be odd to have central pixel 32 | x<- matrix(seq(1:ncols), nrow = nrows, ncol =ncols, byrow = TRUE) - ((ncols+1)/2) 33 | y<- matrix(seq(1:nrows), nrow=nrows, ncol=ncols, byrow = FALSE) - ((nrows+1)/2) 34 | x<- x * resolution[1] 35 | y<- y * resolution[2] 36 | dis_mat<- sqrt((y^2)+(x^2)) #Distance from center of window 37 | attributes(dis_mat)<- append(attributes(dis_mat), list(unit=unit)) 38 | if(return_dismat){ 39 | return(dis_mat) 40 | } 41 | w<- matrix(NA, nrow = nrow(dis_mat), ncol= ncol(dis_mat)) 42 | w[dis_mat <= radius]<- 1 43 | attributes(w)<- append(attributes(w), list(unit=unit)) 44 | if(unit=="map"){ 45 | attributes(w)<- append(attributes(w), list(scale=paste0(radius,"MU"))) 46 | } else{ 47 | attributes(w)<- append(attributes(w), list(scale=as.character(radius))) 48 | } 49 | attributes(w)<- append(attributes(w), list(shape="circle")) 50 | return(w) 51 | } 52 | 53 | #' Creates annulus focal window 54 | #' 55 | #' Creates annulus focal window around central pixel. 56 | #' @param radius radius of inner annulus c(inner,outer). Inner radius must be less than or equal to outer radius. 57 | #' @param unit unit for radius. Either "cell" (number of cells, the default) or "map" for map units (e.g. meters). 58 | #' @param resolution resolution of intended raster layer (one number or a vector of length 2). Only necessary if unit= "map" 59 | #' @return a matrix of 1's and NA's showing which cells to include and exclude respectively in focal calculations. It also contains attributes attributes 'unit', 'scale', and 'shape'. 60 | #' @export 61 | annulus_window<- function(radius, unit, resolution){ 62 | unit<- tolower(unit) 63 | if(length(radius) != 2){ 64 | stop("radius must be a vector of length 2") 65 | } 66 | if(radius[1] > radius[2]){ 67 | stop("Error: inner radius must be less than or equal to outer radius") 68 | } 69 | if((radius[1] < 1) & (unit == "cell")){ 70 | stop("Error: inner radius must be at least 1") 71 | } 72 | if(unit=="cell"){ 73 | resolution<- c(1,1) 74 | } 75 | if((radius[1] < max(resolution)) & (unit == "map")){ 76 | stop("Error: inner radius must be >= resolution") 77 | } 78 | if(length(resolution) > 2){ 79 | stop("Specified inner radius exceeds 2 dimensions") 80 | } 81 | 82 | dis_mat<- circle_window(radius = radius[2], unit = unit, resolution = resolution, return_dismat = TRUE) 83 | 84 | w<- matrix(NA, nrow=nrow(dis_mat), ncol=ncol(dis_mat)) 85 | w[(dis_mat >= radius[1]) & (dis_mat <= radius[2])]<- 1 86 | attributes(w)<- append(attributes(w), list(unit=unit)) 87 | if(unit=="map"){ 88 | attributes(w)<- append(attributes(w), list(scale=paste0(radius[1],"MUx", radius[2], "MU"))) 89 | } else{ 90 | attributes(w)<- append(attributes(w), list(scale=paste0(radius[1],"x", radius[2]))) 91 | } 92 | attributes(w)<- append(attributes(w), list(shape="annulus")) 93 | return(w) 94 | } 95 | 96 | #' Calculates Relative Position of a focal cell 97 | #' 98 | #' Calculates the relative position of a focal cell, which represents whether an area is a local high or low. Relative position is the value of the focal cell minus the value of a reference elevation (often the mean of included values in the focal window but see "fun" argument). Positive values indicate local highs (i.e. peaks) and negative values indicate local lows (i.e. depressions). Relative Position can be expressed in units of the input DTM raster or can standardized relative to the local topography by dividing by the standard deviation or range of included elevation values in the focal window. 99 | #' @param r DTM as a SpatRaster or RasterLayer. 100 | #' @param w For a "rectangle" focal window, a vector of length 2 containing odd numbers specifying dimensions where the first number is the number of rows and the second is the number of columns (or a single number if the number of rows and columns is equal). For a "circle" shaped focal window, a single integer representing the radius in "cell" or "map" units or a focal weights matrix created by MultiscaleDTM::circle_window. The default radius is 1 cell if unit= "cell" or the maximum of the x and y cell resolution if unit="map". For an "annulus" shaped focal window, a vector of length 2 specifying c(inner, outer) radii of the annulus in "cell" or "map" units or a focal weights matrix created by MultiscaleDTM::annulus_window. Inner radius must be less than or equal to outer radius. There is no default size for an annulus window. If a "custom" focal window shape is used, w must be a focal weights matrix with 1's for included values and NAs for excluded values. 101 | #' @param shape Character representing the shape of the focal window. Either "rectangle" (default), "circle", or "annulus", or "custom". If a "custom" shape is used, w must be a focal weights matrix. 102 | #' @param stand Standardization method. Either "none" (the default), "range" or "sd" indicating whether the relative position should be standardized by dividing by the standard deviation or range of included values in the focal window. If stand is 'none' the layer name will be "rpos", otherwise it will be "srpos" to indicate that the layer has been standardized. 103 | #' @param exclude_center Logical indicating whether to exclude the central value from focal calculations (Default=FALSE). Use FALSE for DMV and TRUE for TPI. Note, if a focal weights matrix is supplied to w, setting exclude_center=TRUE will overwrite the center value of w to NA, but setting exclude_center=FALSE will not overwrite the central value to be 1. 104 | #' @param unit Unit for w if shape is 'circle' or 'annulus' and it is a vector (default is unit="cell"). For circular and annulus shaped windows specified with a matrix, unit is ignored and extracted directly from w. For rectangular and custom focal windows set unit='cell' or set unit to NA/NULL. 105 | #' @param fun Function to apply to included values to determine the reference elevation. Accepted values are "mean","median", "min", and "max". The default is "mean" 106 | #' @param na.rm Logical indicating whether or not to remove NA values before calculations. 107 | #' @param include_scale Logical indicating whether to append window size to the layer names (default = FALSE) or a character vector specifying the name you would like to append or a number specifying the number of significant digits. If include_scale = TRUE the number of rows and number of columns will be appended for rectangular or custom windows. For circular windows it will be a single number representing the radius. For annulus windows it will be the inner and outer radius. If unit="map" then window size will have "MU" after the number indicating that the number represents the scale in map units (note units can be extracted from w created with MultiscaleDTM::circle_window and MultiscaleDTM::annulus_window). 108 | #' @param filename Character output filename. 109 | #' @param overwrite Logical. If TRUE, filename is overwritten (default is FALSE). 110 | #' @param wopt List with named options for writing files as in writeRaster. 111 | #' @return A SpatRaster or RasterLayer. 112 | #' @examples 113 | #' r<- erupt() 114 | #' rpos<- RelPos(r, w = c(5,5), shape= "rectangle", exclude_center = TRUE, na.rm = TRUE) 115 | #' plot(rpos) 116 | #' @import terra 117 | #' @importFrom raster raster 118 | #' @importFrom raster writeRaster 119 | #' @importFrom dplyr case_when 120 | #' @references 121 | #' Lecours, V., Devillers, R., Simms, A.E., Lucieer, V.L., Brown, C.J., 2017. Towards a Framework for Terrain Attribute Selection in Environmental Studies. Environmental Modelling & Software 89, 19-30. https://doi.org/10.1016/j.envsoft.2016.11.027 122 | #' 123 | #' Lundblad, E.R., Wright, D.J., Miller, J., Larkin, E.M., Rinehart, R., Naar, D.F., Donahue, B.T., Anderson, S.M., Battista, T., 2006. A benthic terrain classification scheme for American Samoa. Marine Geodesy 29, 89–111. https://doi.org/10.1080/01490410600738021 124 | #' 125 | #' Weiss, A., 2001. Topographic Position and Landforms Analysis. Presented at the ESRI user conference, San Diego, CA. 126 | #' 127 | #' Wilson, J.P., Gallant, J.C. (Eds.), 2000. Terrain Analysis: Principles and Applications. John Wiley & Sons, Inc. 128 | #' @export 129 | #' 130 | RelPos<- function(r, w=dplyr::case_when(tolower(shape)=="rectangle" ~ 3, 131 | tolower(shape)=="circle" & isTRUE(tolower(unit)=="cell") ~ 1, 132 | tolower(shape)=="circle" & isTRUE(tolower(unit)=="map") ~ max(terra::res(r))), 133 | shape="rectangle", stand="none", exclude_center= FALSE, 134 | unit="cell", fun = "mean", na.rm=FALSE, include_scale =FALSE, filename=NULL, overwrite=FALSE, wopt=list()){ 135 | #Input checks 136 | og_class<- class(r)[1] 137 | if(og_class=="RasterLayer"){ 138 | r<- terra::rast(r) #Convert to SpatRaster 139 | } 140 | if(!(og_class %in% c("RasterLayer", "SpatRaster"))){ 141 | stop("Error: Input must be a 'SpatRaster' or 'RasterLayer'") 142 | } 143 | if(terra::nlyr(r)!=1){ 144 | stop("Error: Input raster must be one layer.") 145 | } 146 | stand<- tolower(stand) 147 | shape<- tolower(shape) 148 | resolution<- terra::res(r) 149 | fun<- tolower(fun) 150 | 151 | ## Focal Window Checks 152 | if (!(shape %in% c("rectangle", "circle", "annulus", "custom"))){ 153 | stop("Error: shape must be 'rectangle', 'circle', 'annulus', or 'custom'") 154 | } 155 | 156 | if(!(is.vector(w) | is.matrix(w))){stop("Error: w must be a vector or matrix")} 157 | # if rectangle check if w is vector of length 1 or 2 containing odd numbers 158 | # If circle check if w is vector of length 1 or circle focal mat 159 | # If annulus check if w is vector of length 2 or annulus focal mat 160 | # If custom, check is matrix 161 | if (shape=="rectangle") { 162 | if(!(is.vector(w) & length(w)<=2)){ 163 | stop("Error: For rectangle focal windows w must be a vector of length 1 or 2")} 164 | if(length(w)==1){w<- rep(w, 2)} # repeat w if only length 1 165 | if(any(0 == (w %% 2))){stop("Error: w must be odd")} 166 | if(all(w<3)){stop("Error: w must be greater or equal to 3 in at least one dimension")} 167 | } else if (shape=="circle"){ 168 | if(is.vector(w)){ 169 | if(length(w)!=1){stop("Error: For circle focal windows w must be a vector of length 1 or a focal weights matrix")} 170 | } else { 171 | if(!isTRUE(shape == attributes(w)$shape)){stop("Error: shape = 'circle' but w was not created using 'MultiscaleDTM::circle_window'")} 172 | }} else if (shape == "annulus"){ 173 | if(is.vector(w)){ 174 | if(length(w)!=2){stop("Error: For annulus focal windows w must be a vector of length 2 or a focal weights matrix")} 175 | } else { 176 | if(!isTRUE(shape == attributes(w)$shape)){stop("Error: shape = 'annulus' but w was not created using 'MultiscaleDTM::annulus_window'")} 177 | }} else{ 178 | if(!is.matrix(w)){stop("Error: w must be a matrix for custom shaped focal windows")} 179 | } 180 | 181 | if(is.matrix(w)){ 182 | if(any(0 == (dim(w) %% 2))){ 183 | stop("Error: dimensions of w must be odd")} 184 | if(!all((is.na(w) | w==1))){ 185 | stop("Error: Focal weights matrix, w, must only include NA's and 1's") 186 | }} 187 | 188 | ## unit check 189 | if(!((unit %in% c("cell", "map")) | is.na(unit) | is.null(unit))){ 190 | stop("Error: unit must be 'cell', 'map', or NA/NULL") 191 | } 192 | if(unit=="map" & (!shape %in% c("circle", "annulus"))){ 193 | warning("Warning: unit is ignored for rectangular and custom shaped focal windows so setting 'map' as unit has no effect.") 194 | } 195 | 196 | if(is.matrix(w) & shape %in% c("circle", "annulus")){ 197 | unit<- attributes(w)$unit #overwrite unit based on w 198 | } 199 | 200 | ## stand checks 201 | if(length(stand)!= 1){ 202 | stop("Error: stand must be of a character vector of length 1") 203 | } 204 | if(!(stand %in% c("none", "range", "sd"))){ 205 | stop("Error: stand must be 'none', 'range', or 'sd'") 206 | } 207 | 208 | ## fun checks 209 | if(!(fun %in% c("mean", "median", "min", "max"))){ 210 | stop("Error: fun must be 'mean', 'median', 'min', or 'max'") 211 | } 212 | 213 | # Set up focal window 214 | ## if focal weights matrix if w is a vector 215 | if(is.vector(w)){ 216 | if(shape=="rectangle"){ 217 | w_mat <- matrix(1, nrow=w[1], ncol=w[2]) 218 | } else if(shape=="circle"){ 219 | w_mat<- MultiscaleDTM::circle_window(radius = w, unit = unit, resolution = resolution, return_dismat = FALSE) 220 | } else{ 221 | w_mat<- MultiscaleDTM::annulus_window(radius = w, unit = unit, resolution = resolution) 222 | } 223 | } else{ 224 | w_mat<- w 225 | } 226 | 227 | ## Exclude center cell 228 | if(exclude_center){ 229 | center_idx<- ceiling(0.5 * length(w_mat)) 230 | w_mat[center_idx] <- NA_real_ #Exclude central cell 231 | } 232 | 233 | # Calculate Relative Position 234 | rpos<- r - terra::focal(x = r, w = w_mat, fun = fun, na.rm = na.rm, wopt=wopt) 235 | names(rpos)<- "rpos" 236 | 237 | # standardize 238 | if(stand!="none"){ 239 | if(stand=="range"){ 240 | localmax<- terra::focal(x = r, w= w_mat, fun=max, na.rm = na.rm, wopt=wopt) 241 | localmin<- terra::focal(x = r, w= w_mat, fun=min, na.rm = na.rm, wopt=wopt) 242 | rpos<- (rpos)/(localmax-localmin) 243 | names(rpos)<- "srpos" 244 | } else if(stand=="sd"){ 245 | localsd<- terra::focal(x = r, w= w_mat, fun="sd", na.rm = na.rm, wopt=wopt) 246 | rpos<- rpos/(localsd) 247 | names(rpos)<- "srpos" 248 | } 249 | } 250 | 251 | # Extract scale 252 | if(is.character(include_scale)){ 253 | spatial_scale<- include_scale 254 | } else if(shape=="rectangle"){ 255 | spatial_scale<- paste0(w[1], "x", w[2]) 256 | } else{ 257 | spatial_scale<- attributes(w_mat)$scale 258 | } 259 | if(is.null(spatial_scale) & shape=="custom"){ 260 | spatial_scale<- paste0(nrow(w_mat), "x", ncol(w_mat)) 261 | } 262 | if(is.numeric(include_scale)){ 263 | matches<- unlist(regmatches(spatial_scale, gregexpr(pattern = "\\d+\\.?\\d*e?-?\\d*", text = spatial_scale))) 264 | replacement<- as.character(signif(as.numeric(matches), include_scale)) 265 | if(unit=="map"){ 266 | replacement<- paste0(replacement,"MU") 267 | } 268 | if(length(replacement)==1){ 269 | spatial_scale<- replacement 270 | } else if(length(replacement)==2){ 271 | spatial_scale<- paste0(replacement[1], "x", replacement[2]) 272 | } else{ 273 | stop("Error: Something went wrong in extracting spatial scale") 274 | } 275 | } 276 | if(isTRUE(include_scale) | is.character(include_scale) | is.numeric(include_scale)){names(rpos)<- paste0(names(rpos), "_", spatial_scale)} #Add scale to layer names 277 | 278 | #Return 279 | if(og_class =="RasterLayer"){ 280 | rpos<- raster::raster(rpos) 281 | if(!is.null(filename)){ 282 | return(raster::writeRaster(rpos, filename=filename, overwrite=overwrite)) 283 | } 284 | } 285 | if(!is.null(filename)){ 286 | return(terra::writeRaster(rpos, filename=filename, overwrite=overwrite, wopt=wopt)) 287 | } 288 | return(rpos) 289 | } 290 | -------------------------------------------------------------------------------- /R/SAPA.R: -------------------------------------------------------------------------------- 1 | #' Calculates surface area to planar area rugosity 2 | #' 3 | #' Calculates surface area (Jenness, 2004) to planar area rugosity and by default corrects planar area for slope using the arc-chord ratio (Du Preez, 2015). Additionally, the method has been modified to allow for calculations at multiple different window sizes (see details and Ilich et al. (2023)). 4 | #' @param r DTM as a SpatRaster or RasterLayer in a projected coordinate system where map units match elevation/depth units (Not used if both slope_layer and sa_layer are specified). 5 | #' @param w A single number or a vector of length 2 (row, column) specifying the dimensions of the rectangular window over which surface area will be summed. Window size must be an odd number. 1 refers to "native" scale and surface area and planar area will be calculated per cell (the traditional implementation). 6 | #' @param slope_correction Whether to use the arc-chord ratio to correct planar area for slope (default is TRUE) 7 | #' @param na.rm logical indicating whether to remove/account for NAs in calculations. If FALSE any calculations involving NA will be NA. If TRUE, NA values will be removed and accounted for. 8 | #' @param include_scale logical indicating whether to append window size to the layer names (default = FALSE) 9 | #' @param slope_layer Optionally specify an appropriate slope layer IN RADIANS to use. If not supplied, it will be calculated using the SlpAsp function using the "boundary" method. The slope layer should have a window size that is 2 larger than the w specified for SAPA. 10 | #' @param sa_layer Optionally specify a surface area raster that contains the surface area on a per cell level. This can be calculated with the SurfaceArea function. If calculating SAPA at multiple scales it will be more efficient to supply this so that it does not need to be calculated every time. 11 | #' @param check logical indicating whether to check if any values go below the theoretical value of 1 (default is TRUE). If any are found a warning will be displayed and values less than 1 will be replaced with 1. This is ignored if slope_correction is FALSE. 12 | #' @param tol Tolerance related to 'check' when comparing to see if values are less than 1. Values will still be replaced if check is TRUE, but a warning will not be displayed if the amount below 1 is less than or equal to the tolerance (default = 0.0001). 13 | #' @param filename character Output filename. 14 | #' @param overwrite logical. If TRUE, filename is overwritten (default is FALSE). 15 | #' @param wopt list with named options for writing files as in writeRaster 16 | #' @examples 17 | #' r<- erupt() 18 | #' sapa<- SAPA(r, w=c(5,5), slope_correction = TRUE) 19 | #' plot(sapa) 20 | #' @details Planar area is calculated as the x_dis * y_dis if uncorrected for slope and (x_dis * y_dis)/cos(slope) if corrected for slope. When w=1, this is called "native" scale and is equivalent to what is presented in Du Preez (2015) and available in the ArcGIS Benthic Terrain Modeller add-on. In this case operations are performed on a per cell basis where x_dis is the resolution of the raster in the x direction (left/right) and y_dis is the resolution of the raster in the y direction (up/down) and slope is calculated using the Horn (1981) method. To expand this to multiple scales of analysis, at w > 1 slope is calculated based on Misiuk et al (2021) which provides a modification of the Horn method to extend the matric to multiple spatial scales. Planar area is calculated the same way as for w=1 except that now x_dis is the x resolution of the raster * the number of columns in the focal window, and y_dis is y resolution of the raster * the number of rows. For w > 1, surface area is calculated as the sum of surface areas within the focal window. Although the (modified) Horn slope is used by default to be consistent with Du Preez (2015), slope calculated using a different algorithm (e.g. Wood 1996) could be supplied using the slope_layer argument. Additionally, a slope raster can be supplied if you have already calculated it and do not wish to recalculate it. However, be careful to supply a slope layer measured in radians and calculated at the relevant scale (2 larger than the w of SAPA). 21 | #' @return a SpatRaster or RasterLayer 22 | #' @import terra 23 | #' @importFrom raster raster 24 | #' @importFrom raster writeRaster 25 | #' @references 26 | #' Du Preez, C., 2015. A new arc–chord ratio (ACR) rugosity index for quantifying three-dimensional landscape structural complexity. Landscape Ecol 30, 181–192. https://doi.org/10.1007/s10980-014-0118-8 27 | #' 28 | #' Horn, B.K., 1981. Hill Shading and the Reflectance Map. Proceedings of the IEEE 69, 14-47. 29 | #' 30 | #' Ilich, A. R., Misiuk, B., Lecours, V., & Murawski, S. A. (2023). MultiscaleDTM: An open-source R package for multiscale geomorphometric analysis. Transactions in GIS, 27(4). https://doi.org/10.1111/tgis.13067 31 | #' 32 | #' Jenness, J.S., 2004. Calculating landscape surface area from digital elevation models. Wildlife Society Bulletin 32, 829-839. 33 | #' 34 | #' Misiuk, B., Lecours, V., Dolan, M.F.J., Robert, K., 2021. Evaluating the Suitability of Multi-Scale Terrain Attribute Calculation Approaches for Seabed Mapping Applications. Marine Geodesy 44, 327-385. https://doi.org/10.1080/01490419.2021.1925789 35 | #' @export 36 | 37 | SAPA<- function(r=NULL, w = 1, slope_correction=TRUE, na.rm=FALSE, include_scale=FALSE, slope_layer= NULL, sa_layer = NULL, check = TRUE, tol = 0.0001, filename=NULL, overwrite=FALSE, wopt=list()){ 38 | if(is.null(r)){ 39 | if(is.null(sa_layer)){ 40 | stop("Error: If r is NULL then 'sa_layer' must be specified") 41 | } 42 | if(slope_correction & is.null(slope_layer)){ 43 | stop("Error: If r is and slope_correction is TRUE then 'slope_layer' must be specified") 44 | } 45 | } 46 | 47 | og_class<- c(class(r)[1],class(slope_layer)[1], class(sa_layer)[1]) 48 | 49 | #Convert to SpatRaster 50 | if(og_class[1]=="RasterLayer" & og_class[1]!="NULL"){ 51 | r<- terra::rast(r) 52 | } 53 | if(og_class[2]=="RasterLayer" & og_class[2]!="NULL"){ 54 | slope_layer<- terra::rast(slope_layer) 55 | } 56 | if(og_class[3]=="RasterLayer" & og_class[3]!="NULL"){ 57 | sa_layer<- terra::rast(sa_layer) 58 | } 59 | 60 | #Input checks 61 | if(any(!(og_class %in% c("RasterLayer", "SpatRaster", "NULL")))){ 62 | stop("Error: Input must be a 'SpatRaster' or 'RasterLayer'") 63 | } 64 | 65 | if(!is.null(r)){ 66 | if(terra::nlyr(r)!=1){ 67 | stop("Error: Input raster 'r' must be one layer.") 68 | } 69 | if(isTRUE(terra::is.lonlat(r, perhaps=FALSE))){ 70 | stop("Error: Coordinate system of 'r' is Lat/Lon. Coordinate system must be projected with elevation/depth units matching map units.") 71 | } 72 | if(terra::is.lonlat(r, perhaps=TRUE, warn=FALSE)){ 73 | warning("Coordinate system of 'r' may be Lat/Lon. Please ensure that the coordinate system is projected with elevation/depth units matching map units.") 74 | } 75 | } 76 | 77 | if(!is.null(slope_layer)){ 78 | if(terra::nlyr(slope_layer)!=1){ 79 | stop("Error: Input raster 'slope_layer' must be one layer.") 80 | } 81 | if(isTRUE(terra::is.lonlat(slope_layer, perhaps=FALSE))){ 82 | stop("Error: Coordinate system of 'slope_layer' is Lat/Lon. Coordinate system must be projected.") 83 | } 84 | if(terra::is.lonlat(slope_layer, perhaps=TRUE, warn=FALSE)){ 85 | warning("Coordinate system of 'slope_layer' may be Lat/Lon. Please ensure that the coordinate system is projected.") 86 | } 87 | if(terra::global(slope_layer, max, na.rm=TRUE) > 1.6){ 88 | stop("Error: Slope must be in radians") 89 | } 90 | } 91 | 92 | if(!is.null(sa_layer)){ 93 | if(terra::nlyr(sa_layer)!=1){ 94 | stop("Error: Input raster 'sa_layer' must be one layer.") 95 | } 96 | if(isTRUE(terra::is.lonlat(sa_layer, perhaps=FALSE))){ 97 | stop("Error: Coordinate system of 'sa_layer' is Lat/Lon. Coordinate system must be projected with units corresponding to map units^2.") 98 | } 99 | if(terra::is.lonlat(sa_layer, perhaps=TRUE, warn=FALSE)){ 100 | warning("Coordinate system of 'sa_layer' may be Lat/Lon. Please ensure that the coordinate system is projected with units corresponding to map units^2.") 101 | } 102 | } 103 | 104 | if(length(w)==1){w<- rep(w,2)} 105 | if(length(w) > 2){ 106 | stop("Specified window exceeds 2 dimensions")} 107 | if(any(0 == (w %% 2))){ 108 | stop("w must be odd") 109 | } 110 | if(all(w==c(1,1))){ 111 | is_native<- TRUE} else{ 112 | is_native<- FALSE 113 | } #Indicate whether SAPA is calculated at native scale 114 | 115 | if(is.null(sa_layer)){ 116 | sa_layer<- SurfaceArea(r, na.rm=na.rm, wopt=wopt) 117 | } 118 | 119 | if(!is_native) { 120 | if(na.rm){ 121 | sa_layer<- prod(w)*terra::focal(sa_layer, w= w, fun=mean, na.rm=TRUE, wopt=wopt) 122 | } else{ 123 | sa_layer<- terra::focal(sa_layer, w= w, fun=sum, na.rm=FALSE, wopt=wopt) 124 | } 125 | } # Sum up surface area in focal window (or scale mean to number of cells) 126 | 127 | if(is.null(slope_layer) & slope_correction){ 128 | if (all(w==1) & (!na.rm)){ 129 | slope_layer<- terra::terrain(r, v="slope", unit="radians", neighbors=8, wopt=wopt) 130 | } else{ 131 | slope_layer<- SlpAsp(r, w=w+2, unit="radians", method="boundary", metrics= "slope", na.rm=na.rm, include_scale=FALSE, wopt=wopt) 132 | } 133 | } 134 | 135 | x_res<- terra::xres(sa_layer) 136 | y_res<- terra::yres(sa_layer) 137 | 138 | pa_layer<- (x_res*w[2]) * (y_res*w[1]) 139 | if(slope_correction){pa_layer<- pa_layer/terra::math(slope_layer, fun="cos", wopt=wopt)}#Planar area corrected for slope 140 | 141 | sapa<- sa_layer/pa_layer 142 | names(sapa)<- "sapa" 143 | 144 | if(slope_correction & check){ 145 | minval<- global(sapa, min, na.rm=TRUE)$min 146 | if(minval < 1){ 147 | if((1-minval) > tol){ 148 | warning(paste("SAPA < 1 detected. Minimum value of", minval)) 149 | } 150 | LessThanOne<- sapa < 1 151 | sapa<- terra::mask(sapa, LessThanOne, maskvalues = 1, updatevalue=1) 152 | } 153 | } 154 | 155 | if(include_scale){ 156 | if(is_native){ 157 | names(sapa)<- paste0(names(sapa), "_native") 158 | } else{ 159 | names(sapa)<- paste0(names(sapa), "_", w[1], "x", w[2]) 160 | }} 161 | 162 | #Return 163 | if(!("SpatRaster" %in% og_class)){ 164 | sapa<- raster::raster(sapa) 165 | if(!is.null(filename)){ 166 | return(raster::writeRaster(sapa, filename=filename, overwrite=overwrite)) 167 | } 168 | } 169 | if(!is.null(filename)){ 170 | return(terra::writeRaster(sapa, filename=filename, overwrite=overwrite, wopt=wopt)) 171 | } 172 | return(sapa) 173 | } 174 | -------------------------------------------------------------------------------- /R/SlpAsp.R: -------------------------------------------------------------------------------- 1 | #' Multiscale Slope and Aspect 2 | #' 3 | #' Calculates multiscale slope and aspect based on a modified version of the algorithm from Misiuk et al (2021) which extends classical formulations of slope restricted to a 3x3 window to multiple scales by using only cells on the edges of the focal window (see details for more information). 4 | #' @param r DTM as a SpatRaster or RasterLayer in a projected coordinate system where map units match elevation/depth units 5 | #' @param w A vector of length 2 specifying the dimensions of the rectangular window to use where the first number is the number of rows and the second number is the number of columns. Window size must be an odd number. 6 | #' @param unit "degrees" or "radians" 7 | #' @param method "rook", "queen" (default), or "boundary". The method indicates which cells to use to in computations. "rook" uses only the 4 edge cells directly up, down, left, and right; "queen" adds an additional four corner cells; "boundary" uses all edge cells (see details for more information). 8 | #' @param metrics a character string or vector of character strings of which terrain attributes to return. Default is to return c("slope", "aspect", "eastness", "northness"). Additional measures available include "dz.dx" and "dz.dy" which are the x and y components of slope respectively. 9 | #' @param na.rm Logical indicating whether or not to remove NA values before calculations. Not applicable for "rook" method. 10 | #' @param include_scale logical indicating whether to append window size to the layer names (default = FALSE) 11 | #' @param mask_aspect A logical. When mask_aspect is TRUE (the default), if slope evaluates to 0, aspect will be set to NA and both eastness and northness will be set to 0. When mask_aspect is FALSE, when slope is 0 aspect will be pi/2 radians or 90 degrees which is the behavior of raster::terrain, and northness and eastness will be calculated from that. 12 | #' @param filename character Output filename. Can be a single filename, or as many filenames as there are layers to write a file for each layer 13 | #' @param overwrite logical. If TRUE, filename is overwritten (default is FALSE). 14 | #' @param wopt list with named options for writing files as in writeRaster 15 | #' @import terra 16 | #' @importFrom raster raster 17 | #' @importFrom raster stack 18 | #' @importFrom raster writeRaster 19 | #' @return a SpatRaster or RasterStack of slope and/or aspect (and components of aspect) 20 | #' @examples 21 | #' r<- erupt() 22 | #' slp_asp<- SlpAsp(r = r, w = c(5,5), unit = "degrees", 23 | #' method = "queen", metrics = c("slope", "aspect", 24 | #' "eastness", "northness")) 25 | #' plot(slp_asp) 26 | #' @details Slope is calculated atan(sqrt(dz.dx^2 + dz.dy^2)) and aspect is calculated as (-pi/2)-atan_2(dz.dy, dz.dx) and then constrained from 0 to 2 pi/0 to 360 degrees. 27 | #' dz.dx is the difference in between the weighted mean of the right side of the focal window and weighted mean of the left side of the focal window divided by the x distance of the focal window in map units. 28 | #' dz.dy is the difference in between the weighted mean of the top side of the focal window and weighted mean of the bottom side of the focal window divided by the y distance of the focal window in map units. 29 | #' The cells used in these computations is dependent on the "method" chosen. For methods "queen" and "boundary", corner cells have half the weight of all other cells used in the computations. 30 | #' @references 31 | #' Fleming, M.D., Hoffer, R.M., 1979. Machine processing of landsat MSS data and DMA topographic data for forest cover type mapping (No. LARS Technical Report 062879). Laboratory for Applications of Remote Sensing, Purdue University, West Lafayette, Indiana. 32 | #' 33 | #' Horn, B.K., 1981. Hill Shading and the Reflectance Map. Proceedings of the IEEE 69, 14-47. 34 | #' 35 | #' Misiuk, B., Lecours, V., Dolan, M.F.J., Robert, K., 2021. Evaluating the Suitability of Multi-Scale Terrain Attribute Calculation Approaches for Seabed Mapping Applications. Marine Geodesy 44, 327-385. https://doi.org/10.1080/01490419.2021.1925789 36 | #' 37 | #' Ritter, P., 1987. A vector-based slope and aspect generation algorithm. Photogrammetric Engineering and Remote Sensing 53, 1109-1111. 38 | #' @import terra 39 | #' @importFrom raster raster 40 | #' @importFrom raster stack 41 | #' @export 42 | 43 | SlpAsp <- function(r, w=c(3,3), unit="degrees", method="queen", metrics= c("slope", "aspect", "eastness", "northness"), na.rm=FALSE, include_scale=FALSE, mask_aspect=TRUE, filename=NULL, overwrite=FALSE, wopt=list()){ 44 | og_class<- class(r)[1] 45 | 46 | if(og_class=="RasterLayer"){ 47 | r<- terra::rast(r) #Convert to SpatRaster 48 | } 49 | 50 | #Input checks 51 | if(!(og_class %in% c("RasterLayer", "SpatRaster"))){ 52 | stop("Error: Input must be a 'SpatRaster' or 'RasterLayer'") 53 | } 54 | if(terra::nlyr(r)!=1){ 55 | stop("Error: Input raster must be one layer.") 56 | } 57 | if(isTRUE(terra::is.lonlat(r, perhaps=FALSE))){ 58 | stop("Error: Coordinate system is Lat/Lon. Coordinate system must be projected with elevation/depth units matching map units.") 59 | } 60 | if(terra::is.lonlat(r, perhaps=TRUE, warn=FALSE)){ 61 | warning("Coordinate system may be Lat/Lon. Please ensure that the coordinate system is projected with elevation/depth units matching map units.") 62 | } 63 | if(length(w)==1){w<- rep(w,2)} 64 | if(length(w) > 2){ 65 | stop("Specified window exceeds 2 dimensions")} 66 | if(any(0 == (w %% 2))){ 67 | stop("w must be odd") 68 | } 69 | if(any(w<3)){ 70 | stop("Error: w must be greater or equal to 3") 71 | } 72 | unit<- tolower(unit) #make lowercase 73 | if(!(unit %in% c("degrees", "radians"))){ 74 | stop("unit must be 'degrees' or 'radians'") 75 | } 76 | 77 | if(method==4){method<- "rook"} 78 | if(method==8){method<- "queen"} 79 | 80 | if(!(method %in% c("queen", "rook", "boundary"))){ 81 | stop("method must be 'queen', 'rook', 'boundary', '8', or '4'") 82 | } 83 | metrics<- tolower(metrics) #Make all lowercase 84 | if(any(!(metrics %in% c("slope", "aspect","northness", "eastness", "dz.dx", "dz.dy")))){ 85 | stop("metrics must be 'slope', 'aspect', 'northness', 'eastness', 'dz.dx', and/or 'dz.dy'") 86 | } 87 | 88 | needed_metrics<- metrics 89 | 90 | if(any(c("slope", "aspect", "eastness", "northness") %in% needed_metrics) & !("dz.dx" %in% needed_metrics)){ 91 | needed_metrics<- c(needed_metrics, "dz.dx") 92 | } 93 | 94 | if(any(c("slope", "aspect", "eastness", "northness") %in% needed_metrics) & !("dz.dy" %in% needed_metrics)){ 95 | needed_metrics<- c(needed_metrics, "dz.dy") 96 | } 97 | 98 | if(any(c("eastness", "northness") %in% needed_metrics) & !("aspect" %in% needed_metrics)){ 99 | needed_metrics<- c(needed_metrics, "aspect") 100 | } 101 | if(mask_aspect & ("aspect" %in% needed_metrics)){ 102 | needed_metrics<- c(needed_metrics, "slope") 103 | } 104 | if(method=="rook" & na.rm){ 105 | warning("na.rm=TRUE is not relevant if `method` is 'rook'") 106 | } 107 | 108 | #j is the number of cells on either side of the focal cell; l is used to generate the focal matrix 109 | jx <- floor((w[2]/2)) 110 | jy <- floor(w[1]/2) 111 | 112 | if(method=="queen"){ 113 | mat.l<- matrix(data = NA, nrow=w[1], ncol=w[2]) # left 114 | mat.l[1,1]<- 1 115 | mat.l[jy+1,1]<- 2 116 | mat.l[w[1],1]<- 1 117 | mat.l<- mat.l/sum(mat.l, na.rm=TRUE) 118 | 119 | mat.t<- matrix(data = NA, nrow=w[1], ncol=w[2]) # top 120 | mat.t[1,1]<- 1 121 | mat.t[1, jx+1]<- 2 122 | mat.t[1, w[2]]<- 1 123 | mat.t<- mat.t/sum(mat.t, na.rm=TRUE) 124 | mat.t 125 | } else if(method=="rook"){ 126 | mat.l<- matrix(data = NA, nrow=w[1], ncol=w[2]) # left 127 | mat.l[jy+1,1]<- 1 128 | 129 | mat.t<- matrix(data = NA, nrow=w[1], ncol=w[2]) # top 130 | mat.t[1, jx+1]<- 1 131 | } else { 132 | mat.l<- matrix(data = NA, nrow=w[1], ncol=w[2]) # left 133 | mat.l[,1]<- 2 134 | mat.l[1,1]<- 1 135 | mat.l[w[1],1]<- 1 136 | mat.l<- mat.l/sum(mat.l, na.rm=TRUE) 137 | mat.l 138 | 139 | mat.t<- matrix(data = NA, nrow=w[1], ncol=w[2]) # top 140 | mat.t[1,]<- 2 141 | mat.t[1, 1]<- 1 142 | mat.t[1, w[2]]<- 1 143 | mat.t<- mat.t/sum(mat.t, na.rm=TRUE) 144 | mat.t 145 | } 146 | 147 | mat.r<- mat.l[, ncol(mat.l):1] # right, flip matrix 148 | mat.b<- mat.t[nrow(mat.t):1, ] # bottom, flip matrix 149 | 150 | if(!na.rm){ 151 | if("dz.dx" %in% needed_metrics){ 152 | mean.r<- focal(r, w=mat.r, fun=sum, na.rm=FALSE) 153 | mean.l<- focal(r, w=mat.l, fun=sum, na.rm=FALSE) 154 | } 155 | if("dz.dy" %in% needed_metrics){ 156 | mean.t<- focal(r, w=mat.t, fun=sum, na.rm=FALSE) 157 | mean.b<- focal(r, w=mat.b, fun=sum, na.rm=FALSE) 158 | } 159 | } else{ 160 | if("dz.dx" %in% needed_metrics){ 161 | mean.r<- focal(r, w=mat.r, fun=mean, na.rm=TRUE) 162 | mean.l<- focal(r, w=mat.l, fun=mean, na.rm=TRUE) 163 | } 164 | if("dz.dy" %in% needed_metrics){ 165 | mean.t<- focal(r, w=mat.t, fun=mean, na.rm=TRUE) 166 | mean.b<- focal(r, w=mat.b, fun=mean, na.rm=TRUE) 167 | } 168 | } 169 | 170 | out<- terra::rast() #initialize output 171 | 172 | if("dz.dx" %in% needed_metrics){ 173 | dx<- xres(r) * jx * 2 174 | dz.dx<- (mean.r-mean.l)/dx 175 | names(dz.dx)<- "dz.dx" 176 | out<- c(out, dz.dx, warn=FALSE) 177 | } 178 | 179 | if("dz.dy" %in% needed_metrics){ 180 | dy<- yres(r) * jy * 2 181 | dz.dy<- (mean.t-mean.b)/dy 182 | names(dz.dy)<- "dz.dy" 183 | out<- c(out, dz.dy, warn=FALSE) 184 | } 185 | 186 | if("slope" %in% needed_metrics){ 187 | slope.k<- terra::math(terra::math(dz.dx^2 + dz.dy^2, fun= "sqrt", wopt=wopt), fun= "atan", wopt=wopt) 188 | if(unit=="degrees" & ("slope" %in% metrics)){ 189 | slope.k<- slope.k * (180/pi) 190 | } 191 | names(slope.k)<- "slope" 192 | out<- c(out, slope.k, warn=FALSE) 193 | } 194 | 195 | if("aspect" %in% needed_metrics){ 196 | aspect.k<- (-pi/2) - terra::atan_2(dz.dy, dz.dx, wopt=wopt) # aspect relative to North 197 | aspect.k<- ifel(aspect.k < 0, yes = aspect.k+(2*pi), no= aspect.k, wopt=wopt) # Constrain range so between 0 and 2pi 198 | 199 | if("eastness" %in% needed_metrics){ 200 | eastness.k<- terra::math(aspect.k, fun="sin", wopt=wopt) 201 | if(mask_aspect){ 202 | eastness.k<- terra::mask(eastness.k, mask= slope.k, maskvalues = 0, updatevalue = 0, wopt=wopt) #Set eastness to 0 where slope is zero 203 | } 204 | names(eastness.k)<- "eastness" 205 | out<- c(out, eastness.k, warn=FALSE) 206 | } 207 | 208 | if("northness" %in% needed_metrics){ 209 | northness.k<- terra::math(aspect.k, fun="cos", wopt=wopt) 210 | if(mask_aspect){ 211 | northness.k<- terra::mask(northness.k, mask= slope.k, maskvalues = 0, updatevalue = 0, wopt=wopt) #Set northenss to 0 where slope is zero 212 | } 213 | names(northness.k)<- "northness" 214 | out<- c(out, northness.k, warn=FALSE) 215 | } 216 | 217 | if(mask_aspect){ 218 | aspect.k<- terra::mask(aspect.k, mask= slope.k, maskvalues = 0, updatevalue = NA, wopt=wopt) #Set aspect to undefined where slope is zero 219 | } 220 | if(unit=="degrees"){ 221 | aspect.k<- aspect.k * (180/pi) 222 | } 223 | names(aspect.k)<- "aspect" 224 | out<- c(out, aspect.k, warn=FALSE) 225 | } 226 | 227 | if(!is.null(metrics)){out<- terra::subset(out, metrics, wopt=wopt)} #Subset needed metrics to requested metrics in proper order 228 | if(include_scale){names(out)<- paste0(names(out), "_", w[1], "x", w[2])} 229 | 230 | #Return 231 | if(og_class=="RasterLayer"){ 232 | if(terra::nlyr(out) > 1){ 233 | out<- raster::stack(out) #Convert to RasterStack 234 | if(!is.null(filename)){ 235 | if(length(filename)==1){ 236 | return(raster::writeRaster(out, filename=filename, overwrite=overwrite, bylayer=FALSE)) 237 | } else{ 238 | return(raster::writeRaster(out, filename=filename, overwrite=overwrite, bylayer=TRUE)) 239 | } 240 | } 241 | } else{ 242 | out<- raster::raster(out) 243 | if(!is.null(filename)){ 244 | return(raster::writeRaster(out, filename=filename, overwrite=overwrite)) 245 | } 246 | } 247 | } 248 | if(!is.null(filename)){ 249 | return(terra::writeRaster(out, filename=filename, overwrite=overwrite, wopt=wopt)) 250 | } 251 | return(out) 252 | } 253 | 254 | 255 | 256 | -------------------------------------------------------------------------------- /R/SurfaceArea.R: -------------------------------------------------------------------------------- 1 | #' Calculates surface area of a DTM 2 | #' 3 | #' Calculates surface area on a per cell basis of a DTM based on Jenness, 2004. 4 | #' @param r DTM as a SpatRaster or RasterLayer in a projected coordinate system where map units match elevation/depth units 5 | #' @param na.rm Logical indicating whether to remove NAs from calculations. When FALSE, the sum of the eight triangles is calculated. When TRUE, the mean of the created triangles is calculated and multiplied by 8 to scale it to the proper area. 6 | #' @param filename character Output filename. 7 | #' @param overwrite logical. If TRUE, filename is overwritten (default is FALSE). 8 | #' @param wopt list with named options for writing files as in writeRaster 9 | #' @return a SpatRaster or RasterLayer 10 | #' @examples 11 | #' r<- erupt() 12 | #' sa<- SurfaceArea(r) 13 | #' plot(sa) 14 | #' @import terra 15 | #' @importFrom raster raster 16 | #' @importFrom raster writeRaster 17 | #' @references 18 | #' Jenness, J.S., 2004. Calculating landscape surface area from digital elevation models. Wildlife Society Bulletin 32, 829-839. 19 | #' @export 20 | 21 | SurfaceArea<- function(r, na.rm=FALSE, filename=NULL, overwrite=FALSE, wopt=list()){ 22 | og_class<- class(r)[1] 23 | if(og_class=="RasterLayer"){ 24 | r<- terra::rast(r) #Convert to SpatRaster 25 | } 26 | # Input checks 27 | if(!(og_class %in% c("RasterLayer", "SpatRaster"))){ 28 | stop("Error: Input must be a 'SpatRaster' or 'RasterLayer'") 29 | } 30 | if(terra::nlyr(r)!=1){ 31 | stop("Error: Input raster must be one layer.") 32 | } 33 | if(isTRUE(terra::is.lonlat(r, perhaps=FALSE))){ 34 | stop("Error: Coordinate system is Lat/Lon. Coordinate system must be projected with elevation/depth units matching map units.") 35 | } 36 | if(terra::is.lonlat(r, perhaps=TRUE, warn=FALSE)){ 37 | warning("Coordinate system may be Lat/Lon. Please ensure that the coordinate system is projected with elevation/depth units matching map units.") 38 | } 39 | SA<- terra::focalCpp(r, w=c(3,3), fun = C_SurfaceArea, x_res = terra::res(r)[1], y_res = terra::res(r)[2], na_rm=na.rm, fillvalue=NA, wopt=wopt) 40 | names(SA)<- "SA" 41 | #Return 42 | if(og_class =="RasterLayer"){ 43 | SA<- raster::raster(SA) 44 | if(!is.null(filename)){ 45 | return(raster::writeRaster(SA, filename=filename, overwrite=overwrite)) 46 | } 47 | } 48 | if(!is.null(filename)){ 49 | return(terra::writeRaster(SA, filename=filename, overwrite=overwrite, wopt=wopt)) 50 | } 51 | return(SA) 52 | } -------------------------------------------------------------------------------- /R/TPI.R: -------------------------------------------------------------------------------- 1 | #' Calculates Topographic Position Index 2 | #' 3 | #' Calculates Topographic Position Index (TPI). TPI is a measure of relative position that calculates the the difference between the value of the focal cell and the mean of mean of the surrounding cells (i.e. local mean but excluding the value of the focal cell).Positive values indicate local highs (i.e. peaks) and negative values indicate local lows (i.e. depressions). TPI can be expressed in units of the input DTM raster or can standardized relative to the local topography by dividing by the standard deviation or range of included elevation values in the focal window. 4 | #' @param r DTM as a SpatRaster or RasterLayer. TPI calls the function RelPos internally which serves as a general purpose and more flexible function for calculating relative position. 5 | #' @param w For a "rectangle" focal window, a vector of length 2 containing odd numbers specifying dimensions where the first number is the number of rows and the second is the number of columns (or a single number if the number of rows and columns is equal). For a "circle" shaped focal window, a single integer representing the radius in "cell" or "map" units or a focal weights matrix created by MultiscaleDTM::circle_window. 6 | #' @param shape Character representing the shape of the focal window. Either "rectangle" (default) or "circle". 7 | #' @param stand Standardization method. Either "none" (the default), "range" or "sd" indicating whether the TPI should be standardized by dividing by the standard deviation or range of included values in the focal window. If stand is 'none' the layer name will be "tpi", otherwise it will be "stpi" to indicate that the layer has been standardized. 8 | #' @param unit Unit for w if shape is 'circle' and it is a vector (default is unit="cell"). For circular windows specified with a matrix, unit is ignored and extracted directly from w. For rectangular and custom focal windows set unit='cell' or set unit to NA/NULL. 9 | #' @param na.rm Logical indicating whether or not to remove NA values before calculations. 10 | #' @param include_scale Logical indicating whether to append window size to the layer names (default = FALSE) or a character vector specifying the name you would like to append or a number specifying the number of significant digits. If include_scale = TRUE the number of rows and number of columns will be appended for rectangular windows. For circular windows it will be a single number representing the radius. If unit="map" then window size will have "MU" after the number indicating that the number represents the scale in map units (note units can be extracted from w created with MultiscaleDTM::circle_window). 11 | #' @param filename Character output filename. 12 | #' @param overwrite Logical. If TRUE, filename is overwritten (default is FALSE). 13 | #' @param wopt List with named options for writing files as in writeRaster. 14 | #' @return SpatRaster or RasterLayer. 15 | #' @examples 16 | #' r<- erupt() 17 | #' tpi<- TPI(r, w=c(5,5), shape="rectangle", stand="none", na.rm = TRUE) 18 | #' plot(tpi) 19 | #' @import terra 20 | #' @importFrom raster raster 21 | #' @importFrom raster writeRaster 22 | #' @importFrom dplyr case_when 23 | #' @references 24 | #' Weiss, A., 2001. Topographic Position and Landforms Analysis. Presented at the ESRI user conference, San Diego, CA. 25 | #' @export 26 | #' 27 | TPI<- function(r, w=dplyr::case_when(tolower(shape)=="rectangle" ~ 3, 28 | tolower(shape)=="circle" & isTRUE(tolower(unit)=="cell") ~ 1, 29 | tolower(shape)=="circle" & isTRUE(tolower(unit)=="map") ~ max(terra::res(r))), 30 | shape= "rectangle", stand="none", unit="cell", na.rm=FALSE, 31 | include_scale =FALSE, filename=NULL, overwrite=FALSE, wopt=list()){ 32 | 33 | og_class<- class(r)[1] 34 | if(og_class=="RasterLayer"){ 35 | r<- terra::rast(r) #Convert to SpatRaster 36 | } 37 | #Input checks 38 | if(!(og_class %in% c("RasterLayer", "SpatRaster"))){ 39 | stop("Error: Input must be a 'SpatRaster' or 'RasterLayer'") 40 | } 41 | if (!(shape %in% c("rectangle", "circle"))){ 42 | stop("Error: shape must be 'rectangle' or 'circle'") 43 | } 44 | tpi<- MultiscaleDTM::RelPos(r, w=w, shape= shape, stand=stand, exclude_center= TRUE, 45 | unit=unit, fun="mean", na.rm=na.rm, include_scale =include_scale, filename=NULL, 46 | overwrite=FALSE, wopt=list()) 47 | names(tpi)<- gsub(pattern = "rpos", replacement = "tpi", names(tpi)) 48 | 49 | #Return 50 | if(og_class =="RasterLayer"){ 51 | tpi<- raster::raster(tpi) 52 | if(!is.null(filename)){ 53 | return(raster::writeRaster(tpi, filename=filename, overwrite=overwrite)) 54 | } 55 | } 56 | if(!is.null(filename)){ 57 | return(terra::writeRaster(tpi, filename=filename, overwrite=overwrite, wopt=wopt)) 58 | } 59 | return(tpi) 60 | } -------------------------------------------------------------------------------- /R/VRM.R: -------------------------------------------------------------------------------- 1 | #' Implementation of the Sappington et al., (2007) vector ruggedness measure 2 | #' 3 | #' Implementation of the Sappington et al., (2007) vector ruggedness measure, modified from Evans (2021). 4 | #' @param r DTM as a SpatRaster or RasterLayer 5 | #' @param w A vector of length 2 specifying the dimensions of the rectangular window to use where the first number is the number of rows and the second number is the number of columns. Window size must be an odd number. Default is 3x3. 6 | #' @param na.rm A logical indicating whether or not to remove NA values before calculations. See details for more information. 7 | #' @param include_scale logical indicating whether to append window size to the layer names (default = FALSE) 8 | #' @param filename character Output filename. 9 | #' @param overwrite logical. If TRUE, filename is overwritten (default is FALSE). 10 | #' @param wopt list with named options for writing files as in writeRaster 11 | #' @return a RasterLayer 12 | #' @examples 13 | #' r<- erupt() 14 | #' vrm<- VRM(r, w=c(5,5), na.rm = TRUE) 15 | #' plot(vrm) 16 | #' @import terra 17 | #' @importFrom raster raster 18 | #' @importFrom raster writeRaster 19 | #' @importFrom utils packageVersion 20 | #' @importFrom utils compareVersion 21 | #' @details 22 | #' If the crs is cartesian, when na.rm=TRUE, NA's will be removed from the slope/aspect calculations. When the crs is lat/lon, na.rm=TRUE will not affect the calculation of slope/aspect as terra::terrain will be used since it can calculate slope and aspect for spherical geometry but it does not support na.rm. In both cases when na.rm=TRUE, the x, y, and z components will be summed with na.rm=TRUE, and the N used in the denominator of the VRM equation will be the number of non-NA cells in the window rather than the total number of cells. 23 | #' 24 | #' @references 25 | #' Evans JS (2021). spatialEco. R package version 1.3-6, https://github.com/jeffreyevans/spatialEco. 26 | #' 27 | #' Sappington, J.M., Longshore, K.M., Thompson, D.B., 2007. Quantifying Landscape Ruggedness for Animal Habitat Analysis: A Case Study Using Bighorn Sheep in the Mojave Desert. The Journal of Wildlife Management 71, 1419-1426. https://doi.org/10.2193/2005-723 28 | #' @export 29 | 30 | VRM<- function(r, w=c(3,3), na.rm = FALSE, include_scale=FALSE, filename=NULL, overwrite=FALSE, wopt=list()){ 31 | og_class<- class(r)[1] 32 | if(og_class=="RasterLayer"){ 33 | r<- terra::rast(r) #Convert to SpatRaster 34 | } 35 | #Input checks 36 | if(!(og_class %in% c("RasterLayer", "SpatRaster"))){ 37 | stop("Error: Input must be a 'SpatRaster' or 'RasterLayer'") 38 | } 39 | if(terra::nlyr(r)!=1){ 40 | stop("Error: Input raster must be one layer.") 41 | } 42 | if(length(w)==1){ 43 | w<- rep(w,2)} 44 | if(length(w) > 2){ 45 | stop("Specified window exceeds 2 dimensions")} 46 | if(any(0 == (w %% 2))){ 47 | stop("Error: w must be odd")} 48 | if(all(w<3)){ 49 | stop("Error: w must be greater or equal to 3 in at least one dimension") 50 | } 51 | 52 | if((compareVersion(as.character(packageVersion("terra")), "1.5.49")==-1) & isTRUE(terra::is.lonlat(r, perhaps=FALSE))){ 53 | warning("Distance calculations conducted using Haversine (spheroid) rather geodesic formulas since terra version is < 1.5.49") 54 | } 55 | 56 | if(isTRUE(terra::is.lonlat(r, perhaps=FALSE)) | (!na.rm)){ 57 | sa <- terra::terrain(r, v=c("slope", "aspect"), unit="radians", neighbors=8, wopt=wopt) 58 | } else{ 59 | sa <- SlpAsp(r, w=c(3,3),unit="radians", method="queen", metrics= c("slope", "aspect"), na.rm=na.rm, include_scale=FALSE, mask_aspect=FALSE, wopt=wopt) 60 | } #SlpAsp can use na.rm in slope calculations, terra::terrain cannot but can handle spherical geometry. 61 | 62 | #Decompose vectors into components 63 | slope.sin.cos <- terra::lapp(sa$slope, fun = function(s) c(sin(s), cos(s)), wopt = wopt) 64 | aspect.sin.cos <- terra::lapp(sa$aspect, fun = function(a) c(sin(a), cos(a)), wopt = wopt) 65 | 66 | # Decompose into X, Y, Z components 67 | components <- terra::lapp(c(aspect.sin.cos, slope.sin.cos), fun = function(sin_a, cos_a, sin_s, cos_s) { 68 | x <- sin_a * sin_s 69 | y <- cos_a * sin_s 70 | z <- cos_s 71 | c(x, y, z) 72 | }, wopt = wopt) 73 | Xrast <- components[[1]] 74 | Yrast <- components[[2]] 75 | Zrast <- components[[3]] 76 | 77 | # Focal sum for each component 78 | x.sum <- terra::focal(Xrast, w = w, fun = sum, na.rm = na.rm, wopt = wopt) 79 | y.sum <- terra::focal(Yrast, w = w, fun = sum, na.rm = na.rm, wopt = wopt) 80 | z.sum <- terra::focal(Zrast, w = w, fun = sum, na.rm = na.rm, wopt = wopt) 81 | 82 | # Resultant vector 83 | res_vect <- terra::lapp(c(x.sum, y.sum, z.sum), fun = function(x, y, z) sqrt(x^2 + y^2 + z^2), wopt = wopt) 84 | 85 | # Adjust scale factor depending on na.rm 86 | scale.factor <- if (!na.rm) { 87 | round(w[1] * w[2], 0) 88 | } else { 89 | terra::focalCpp(Zrast, w = w, fun = C_CountVals, wopt = wopt) 90 | } 91 | 92 | # Final VRM output 93 | out <- 1 - (res_vect / scale.factor) 94 | names(out)<- "vrm" 95 | if(include_scale){names(out)<- paste0(names(out), "_", w[1],"x", w[2])} #Add scale to layer names 96 | 97 | #Return 98 | if(og_class =="RasterLayer"){ 99 | out<- raster::raster(out) 100 | if(!is.null(filename)){ 101 | return(raster::writeRaster(out, filename=filename, overwrite=overwrite)) 102 | } 103 | } 104 | if(!is.null(filename)){ 105 | return(terra::writeRaster(out, filename=filename, overwrite=overwrite, wopt=wopt)) 106 | } 107 | return(out) 108 | } -------------------------------------------------------------------------------- /R/curvatures.R: -------------------------------------------------------------------------------- 1 | #' Calculate normal contour curvature 2 | #' 3 | #' Calculate normal contour curvature (kn)c, which is the principal representative of the plan curvature group based on regression coefficients from the equation Z =ax^2+by^2+cxy+dx+ey(+f). 4 | #' @param a regression coefficient 5 | #' @param b regression coefficient 6 | #' @param c regression coefficient 7 | #' @param d regression coefficient 8 | #' @param e regression coefficient 9 | #' @references 10 | #' Evans, I.S., 1980. An integrated system of terrain analysis and slope mapping. Zeitschrift f¨ur Geomorphologic Suppl-Bd 36, 274–295. 11 | #' 12 | #' Wood, J., 1996. The geomorphological characterisation of digital elevation models (Ph.D.). University of Leicester. 13 | #' 14 | #' Minár, J., Evans, I.S., Jenčo, M., 2020. A comprehensive system of definitions of land surface (topographic) curvatures, with implications for their application in geoscience modelling and prediction. Earth-Science Reviews 211, 103414. https://doi.org/10.1016/j.earscirev.2020.103414 15 | knc<- function(a,b,c,d,e){ 16 | #Planc: Normal Contour Curvature 17 | out<- -2*(a*(e^2) - c*d*e + b*d^2)/((d^2+e^2) * sqrt(1+d^2+e^2)) 18 | return(out) 19 | } 20 | 21 | #' Calculate normal slope line curvature 22 | #' 23 | #' Calculate normal slope line curvature (kn)s, which is the principal representative of the profile curvature group based on regression coefficients from the equation Z =ax^2+by^2+cxy+dx+ey(+f). 24 | #' @param a regression coefficient 25 | #' @param b regression coefficient 26 | #' @param c regression coefficient 27 | #' @param d regression coefficient 28 | #' @param e regression coefficient 29 | #' @references 30 | #' Evans, I.S., 1980. An integrated system of terrain analysis and slope mapping. Zeitschrift f¨ur Geomorphologic Suppl-Bd 36, 274–295. 31 | #' 32 | #' Wood, J., 1996. The geomorphological characterisation of digital elevation models (Ph.D.). University of Leicester. 33 | #' 34 | #' Minár, J., Evans, I.S., Jenčo, M., 2020. A comprehensive system of definitions of land surface (topographic) curvatures, with implications for their application in geoscience modelling and prediction. Earth-Science Reviews 211, 103414. https://doi.org/10.1016/j.earscirev.2020.103414 35 | 36 | kns<- function(a,b,c,d,e){ 37 | #Profc: Normal Slope Line Curvature 38 | out<- (-2 * (a*d^2 + c*d*e + b*e^2)) / ((d^2 + e^2)*(1 + d^2 + e^2)^1.5) 39 | return(out) 40 | } 41 | 42 | #' Calculate contour geodesic torsion 43 | #' 44 | #' Calculate contour geodesic torsion (tg)c, which is the principal representative of the twisting curvature group based on regression coefficients from the equation Z =ax^2+by^2+cxy+dx+ey(+f). 45 | #' @param a regression coefficient 46 | #' @param b regression coefficient 47 | #' @param c regression coefficient 48 | #' @param d regression coefficient 49 | #' @param e regression coefficient 50 | #' @references 51 | #' Evans, I.S., 1980. An integrated system of terrain analysis and slope mapping. Zeitschrift f¨ur Geomorphologic Suppl-Bd 36, 274–295. 52 | #' 53 | #' Wood, J., 1996. The geomorphological characterisation of digital elevation models (Ph.D.). University of Leicester. 54 | #' 55 | #' Minár, J., Evans, I.S., Jenčo, M., 2020. A comprehensive system of definitions of land surface (topographic) curvatures, with implications for their application in geoscience modelling and prediction. Earth-Science Reviews 211, 103414. https://doi.org/10.1016/j.earscirev.2020.103414 56 | 57 | tgc<- function(a,b,c,d,e){ 58 | #TwistC: Contour geodesic torsion 59 | out<- (2*d*e*(a-b) - c*(d^2-e^2))/((d^2+e^2)*(1+d^2+e^2)) 60 | return(out) 61 | } 62 | 63 | #' Calculate mean curvature 64 | #' 65 | #' Calculate mean curvature, kmean, from the equation Z =ax^2+by^2+cxy+dx+ey(+f). 66 | #' @param a regression coefficient 67 | #' @param b regression coefficient 68 | #' @param c regression coefficient 69 | #' @param d regression coefficient 70 | #' @param e regression coefficient 71 | #' @references 72 | #' Evans, I.S., 1980. An integrated system of terrain analysis and slope mapping. Zeitschrift f¨ur Geomorphologic Suppl-Bd 36, 274–295. 73 | #' 74 | #' Wood, J., 1996. The geomorphological characterisation of digital elevation models (Ph.D.). University of Leicester. 75 | #' 76 | #' Minár, J., Evans, I.S., Jenčo, M., 2020. A comprehensive system of definitions of land surface (topographic) curvatures, with implications for their application in geoscience modelling and prediction. Earth-Science Reviews 211, 103414. https://doi.org/10.1016/j.earscirev.2020.103414 77 | 78 | kmean<- function(a,b,c,d,e){ 79 | #Mean Curvature 80 | out<- -(a*(1+e^2) - c*d*e + b*(1+d^2)) / (sqrt((1+d^2+e^2)^3)) 81 | return(out) 82 | } 83 | 84 | #' Calculate unsphericity curvature 85 | #' 86 | #' Calculate unsphericity curvature, ku, from the equation Z =ax^2+by^2+cxy+dx+ey(+f). 87 | #' @param a regression coefficient 88 | #' @param b regression coefficient 89 | #' @param c regression coefficient 90 | #' @param d regression coefficient 91 | #' @param e regression coefficient 92 | #' @references 93 | #' Evans, I.S., 1980. An integrated system of terrain analysis and slope mapping. Zeitschrift f¨ur Geomorphologic Suppl-Bd 36, 274–295. 94 | #' 95 | #' Wood, J., 1996. The geomorphological characterisation of digital elevation models (Ph.D.). University of Leicester. 96 | #' 97 | #' Minár, J., Evans, I.S., Jenčo, M., 2020. A comprehensive system of definitions of land surface (topographic) curvatures, with implications for their application in geoscience modelling and prediction. Earth-Science Reviews 211, 103414. https://doi.org/10.1016/j.earscirev.2020.103414 98 | 99 | ku<- function(a,b,c,d,e){ 100 | #unsphericity curvature 101 | out<- sqrt(((a*(1+e^2) - c*d*e +b*(1+d^2)) / (sqrt((1+d^2+e^2)^3)))^2 - ((4*a*b-c^2)/(1+d^2+e^2)^2)) 102 | return(out) 103 | } 104 | 105 | #' Calculate min curvature 106 | #' 107 | #' Calculate min curvature, kmin, from the equation Z =ax^2+by^2+cxy+dx+ey(+f). 108 | #' @param a regression coefficient 109 | #' @param b regression coefficient 110 | #' @param c regression coefficient 111 | #' @param d regression coefficient 112 | #' @param e regression coefficient 113 | #' @references 114 | #' Evans, I.S., 1980. An integrated system of terrain analysis and slope mapping. Zeitschrift f¨ur Geomorphologic Suppl-Bd 36, 274–295. 115 | #' 116 | #' Wood, J., 1996. The geomorphological characterisation of digital elevation models (Ph.D.). University of Leicester. 117 | #' 118 | #' Minár, J., Evans, I.S., Jenčo, M., 2020. A comprehensive system of definitions of land surface (topographic) curvatures, with implications for their application in geoscience modelling and prediction. Earth-Science Reviews 211, 103414. https://doi.org/10.1016/j.earscirev.2020.103414 119 | 120 | kmin<- function(a,b,c,d,e){ 121 | #Min Curvature 122 | out<- kmean(a,b,c,d,e)-ku(a,b,c,d,e) 123 | return(out) 124 | } 125 | 126 | #' Calculate max curvature 127 | #' 128 | #' Calculate max curvature, kmax, from the equation Z =ax^2+by^2+cxy+dx+ey(+f). 129 | #' @param a regression coefficient 130 | #' @param b regression coefficient 131 | #' @param c regression coefficient 132 | #' @param d regression coefficient 133 | #' @param e regression coefficient 134 | #' @references 135 | #' Evans, I.S., 1980. An integrated system of terrain analysis and slope mapping. Zeitschrift f¨ur Geomorphologic Suppl-Bd 36, 274–295. 136 | #' 137 | #' Wood, J., 1996. The geomorphological characterisation of digital elevation models (Ph.D.). University of Leicester. 138 | #' 139 | #' Minár, J., Evans, I.S., Jenčo, M., 2020. A comprehensive system of definitions of land surface (topographic) curvatures, with implications for their application in geoscience modelling and prediction. Earth-Science Reviews 211, 103414. https://doi.org/10.1016/j.earscirev.2020.103414 140 | 141 | kmax<- function(a,b,c,d,e){ 142 | #Max Curvature 143 | out<- kmean(a,b,c,d,e)+ku(a,b,c,d,e) 144 | return(out) 145 | } -------------------------------------------------------------------------------- /R/erupt.R: -------------------------------------------------------------------------------- 1 | #' Create georeferenced version of R's built in volcano dataset 2 | #' 3 | #' Create georeferenced version of R's built in volcano dataset. Useful dataset for generating quick examples. 4 | #' @return SpatRaster 5 | #' @examples 6 | #' r<- erupt() 7 | #' @import terra 8 | #' @export 9 | erupt<- function(){ 10 | volcano<- volcano #add visible binding for variable volcano 11 | return(rast(volcano, 12 | extent= ext(2667400, 2667400 + ncol(volcano)*10, 13 | 6478700, 6478700 + nrow(volcano)*10), 14 | crs = "EPSG:27200")) 15 | } -------------------------------------------------------------------------------- /R/explore_terrain.R: -------------------------------------------------------------------------------- 1 | #' Interactive Shiny app to look at terrain attributes 2 | #' 3 | #' Interactive Shiny app to look at terrain attributes based on a surface fit using a Wood/Evans Quadratic Equation: Z =ax^2+by^2+cxy+dx+ey(+f) 4 | #' @import shiny 5 | #' @import rgl 6 | #' @importFrom dplyr case_when 7 | #' @return No return value, launches Shiny app. 8 | #' @references 9 | #' Evans, I.S., 1980. An integrated system of terrain analysis and slope mapping. Zeitschrift f¨ur Geomorphologic Suppl-Bd 36, 274–295. 10 | #' 11 | #' Wood, J., 1996. The geomorphological characterisation of digital elevation models (Ph.D.). University of Leicester. 12 | #' 13 | #' Minár, J., Evans, I.S., Jenčo, M., 2020. A comprehensive system of definitions of land surface (topographic) curvatures, with implications for their application in geoscience modelling and prediction. Earth-Science Reviews 211, 103414. https://doi.org/10.1016/j.earscirev.2020.103414 14 | #' @export 15 | explore_terrain<- function(){ 16 | appDir<- system.file("Terrain_Attributes_Explorer_App", package = "MultiscaleDTM") 17 | shiny::runApp(appDir, display.mode = "normal") 18 | } -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "README" 3 | author: "Alexander Ilich" 4 | date: "`r format(Sys.time(), '%B %d, %Y')`" 5 | output: 6 | github_document: 7 | pandoc_args: --mathml 8 | --- 9 | ```{r setup, include=FALSE} 10 | knitr::opts_chunk$set(echo = TRUE, warning = FALSE, message = FALSE, fig.path = "man/figures/README-") #Default chunk options 11 | md_fig_dir<- "man/figures/" #Path relative to this Rmd 12 | R_fig_dir<- "../figures/" #Path relative to child Rmd 13 | ``` 14 | 15 | ```{r child='man/fragments/README_Frag.Rmd'} 16 | ``` -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: https://ailich.github.io/MultiscaleDTM/ 2 | template: 3 | bootstrap: 5 4 | 5 | -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | ## R CMD check results 2 | 3 | 0 errors | 0 warnings | 1 note 4 | 5 | * This is a new release. 6 | -------------------------------------------------------------------------------- /inst/CITATION: -------------------------------------------------------------------------------- 1 | citHeader("To cite MultiscaleDTM in publications use:") 2 | 3 | bibentry( 4 | bibtype = "Article", 5 | author = c(person("Alexander R.", "Ilich"), 6 | person("Benjamin", "Misiuk"), 7 | person("Vincent", "Lecours"), 8 | person("Steven A.", "Murawski")), 9 | title = "MultiscaleDTM: An open-source R package for multiscale geomorphometric analysis", 10 | doi = "10.1111/tgis.13067", 11 | year = 2023, 12 | publisher = "Wiley", 13 | volume = 27, 14 | number = 4, 15 | journal = "Transactions in GIS", 16 | textVersion = "Ilich, A. R., Misiuk, B., Lecours, V., & Murawski, S. A. (2023). MultiscaleDTM: An open-source R package for multiscale geomorphometric analysis. Transactions in GIS, 27(4). https://doi.org/10.1111/tgis.13067") 17 | 18 | 19 | bibentry(bibtype = "misc", 20 | author = c(person("Alexander R.", "Ilich"), 21 | person("Benjamin", "Misiuk"), 22 | person("Vincent", "Lecours"), 23 | person("Steven A.", "Murawski")), 24 | title = "MultiscaleDTM", 25 | year= "2021", 26 | doi = "10.5281/zenodo.5548338", 27 | url = "https://github.com/ailich/MultiscaleDTM", 28 | textVersion = "Ilich, Alexander R.; Misiuk, Benjamin; Lecours, Vincent; Murawski, Steven A.; 2021. “MultiscaleDTM”, doi:10.5281/zenodo.5548338. https://github.com/ailich/MultiscaleDTM." 29 | ) 30 | -------------------------------------------------------------------------------- /inst/Terrain_Attributes_Explorer_App/app.R: -------------------------------------------------------------------------------- 1 | kns2<- function(a,b,c,d,e){ 2 | #Profc: Normal Slope Line Curvature 3 | out<- (-2 * (a*d^2 + c*d*e + b*e^2)) / ((d^2 + e^2)*(1 + d^2 + e^2)^1.5) 4 | return(out) 5 | } 6 | 7 | knc2<- function(a,b,c,d,e){ 8 | #Planc: Normal Contour Curvature 9 | out<- -2*(a*(e^2) - c*d*e + b*d^2)/((d^2+e^2) * sqrt(1+d^2+e^2)) 10 | return(out) 11 | } 12 | 13 | tgc2<- function(a,b,c,d,e){ 14 | #TwistC: Contour geodesic torsion 15 | out<- (2*d*e*(a-b) - c*(d^2-e^2))/((d^2+e^2)*(1+d^2+e^2)) 16 | return(out) 17 | } 18 | 19 | kmean2<- function(a,b,c,d,e){ 20 | #Mean Curvature 21 | out<- -(a*(1+e^2) - c*d*e + b*(1+d^2)) / (sqrt((1+d^2+e^2)^3)) 22 | return(out) 23 | } 24 | 25 | ku2<- function(a,b,c,d,e){ 26 | #unsphericity curvature 27 | out<- sqrt(((a*(1+e^2) - c*d*e +b*(1+d^2)) / (sqrt((1+d^2+e^2)^3)))^2 - ((4*a*b-c^2)/(1+d^2+e^2)^2)) 28 | return(out) 29 | } 30 | 31 | kmin2<- function(a,b,c,d,e){ 32 | #Min Curvature 33 | out<- kmean2(a,b,c,d,e)-ku2(a,b,c,d,e) 34 | return(out) 35 | } 36 | 37 | kmax2<- function(a,b,c,d,e){ 38 | #Max Curvature 39 | out<- kmean2(a,b,c,d,e)+ku2(a,b,c,d,e) 40 | return(out) 41 | } 42 | 43 | classify_features2<- function(slope, planc, maxc, minc) { 44 | slope_tolerance<- 1 45 | curvature_tolerance<- 0.0001 46 | dplyr::case_when(is.na(slope) ~ NA_character_, 47 | (slope > slope_tolerance) & (planc > curvature_tolerance) ~ "Ridge", 48 | (slope > slope_tolerance) & (planc < -curvature_tolerance) ~ "Channel", 49 | slope > slope_tolerance ~ "Planar Slope", 50 | (maxc > curvature_tolerance) & (minc > curvature_tolerance) ~ "Peak", 51 | (maxc > curvature_tolerance) & (minc < -curvature_tolerance) ~ "Pass", 52 | maxc > curvature_tolerance ~ "Ridge", 53 | (minc < -curvature_tolerance) & (maxc < -curvature_tolerance) ~ "Pit", 54 | minc < -curvature_tolerance ~ "Channel", 55 | TRUE ~ "Planar Flat") 56 | } 57 | 58 | nr<-3 59 | nc<- 3 60 | x<- matrix(1:nc, nrow=nr, ncol=nc, byrow=TRUE) 61 | x<- x-mean(x) 62 | 63 | y<- matrix(nr:1, nrow=nr, ncol=nc, byrow=FALSE) 64 | y<- y-mean(y) 65 | 66 | plot_surface<- function(a,b,c,d,e){ 67 | z<- a*x^2+ b*y^2+ c*x*y + d*x + e*y 68 | df<- data.frame(x=as.vector(x),y=as.vector(y),z=as.vector(z)) 69 | m<- lm(z ~ I(x^2)+I(y^2)+I(x*y)+x+y, data = df) 70 | if(any(round(m$coefficients, 1)[c(2,3,4,5,6,1)]!=c(a,b,c,d,e,0))){ 71 | stop("Error: fit regression parameters don't match specified parameters") 72 | } 73 | 74 | asp<- (-pi/2) - atan2(e,d) #Shift aspect so north is zero 75 | asp[asp < 0]<- asp[asp < 0] + 2*pi 76 | asp[asp >= 2*pi]<- asp[asp >= 2*pi] - 2*pi # Constrain aspect from 0 to 2pi 77 | eastness<- sin(asp) 78 | northness<- cos(asp) 79 | 80 | rgl::plot3d(m) 81 | if(any(e!=0, d!=0)){ 82 | rgl::arrow3d(p0=c(0,0,0), p1 = c(eastness, northness,0)) 83 | } 84 | } 85 | 86 | make_table<- function(a,b,c,d,e){ 87 | slp<- atan(sqrt(d^2 + e^2)) 88 | asp<- (-pi/2) - atan2(e,d) #Shift aspect so north is zero 89 | asp[asp < 0]<- asp[asp < 0] + 2*pi 90 | asp[asp >= 2*pi]<- asp[asp >= 2*pi] - 2*pi # Constrain aspect from 0 to 2pi 91 | if(slp==0){ 92 | asp<- NA_real_ 93 | } 94 | eastness<- sin(asp) 95 | northness<- cos(asp) 96 | asp_deg<- asp * (180/pi) 97 | slp_deg<- slp * (180/pi) 98 | 99 | profc<- kns2(a,b,c,d,e) 100 | planc<- knc2(a,b,c,d,e) 101 | twistc<- tgc2(a,b,c,d,e) 102 | maxc<- kmax2(a,b,c,d,e) 103 | minc<- kmin2(a,b,c,d,e) 104 | meanc<- kmean2(a,b,c,d,e) 105 | 106 | #Translation to Minar 2020 formulas 107 | # zx<- (2*a*x) + (c*y) + d 108 | # zxx<- 2*a 109 | # zy<- (2*b*y) + (c*x) + e 110 | # zyy<- 2*b 111 | # zxy<- c 112 | 113 | #At central cell x and y are 0. Therefore, 114 | # zx<- d 115 | # zxx<- 2*a 116 | # zy<- e 117 | # zyy<- 2*b 118 | # zxy<- c 119 | 120 | out<- round(data.frame(profc = profc, 121 | planc= planc, 122 | twistc=twistc, 123 | meanc=meanc, 124 | maxc=maxc, 125 | minc=minc, 126 | slope_degrees=slp_deg, 127 | aspect_degrees=asp_deg, 128 | eastness=eastness, 129 | northness=northness, 130 | feature = NA_real_),3) 131 | 132 | out$feature[1] = classify_features2(slp_deg, planc, maxc, minc) 133 | return(out) 134 | } 135 | 136 | ui <- fluidPage( 137 | 138 | # Application title 139 | titlePanel("Terrain Attributes Explorer"), 140 | 141 | # Sidebar with a slider input for number of bins 142 | fluidRow(column(2, 143 | wellPanel("Regression Coefficients", 144 | sliderInput(inputId = "a", label = "a", min = -3, max = 3, value = 0, step = 0.1), 145 | sliderInput(inputId = "b", label = "b", min = -3, max = 3, value = 0, step = 0.1), 146 | sliderInput(inputId = "c", label = "c", min = -3, max = 3, value = 0, step = 0.1), 147 | sliderInput(inputId = "d", label = "d", min = -3, max = 3, value = 0, step = 0.1), 148 | sliderInput(inputId = "e", label = "e", min = -3, max = 3, value = 0, step = 0.1))), 149 | column(5,rgl::rglwidgetOutput("plot", width = 800, height = 600)), 150 | column(2, wellPanel(p("The shape of a small area of a digital elevation model can be approximated with a quadratic formulated as:"), 151 | p(withMathJax("$$\\small{Z=aX^2+bY^2+cXY+dX+eY+f}$$")), 152 | p("where Z is the elevation or depth, X is east/west direction, Y is north/south direction, and a-e are regression coefficients describing the shape of the quadratic surface and f is the intercept. From this surface we can calculate various terrain attributes including slope, aspect, and measures of curvature. Aspect is the compass direction of the slope measured as degrees clockwise from North, and can be decomposed into its north/south (northness) and east/west (eastness) components. There are three basic types of curvature: profile curvature which measures curvature along the direction of maximum slope, plan curvature which measures curvature perpendicular to the direction of maximum slope, and twisting curvature."))), 153 | column(3, wellPanel(p(withMathJax("$$\\small{\\text{Profile Curvature} = \\frac{-2(ad^2 + cde + be^2)}{(d^2+e^2)(1 + d^2 + e^2)^\\frac{3}{2}}}$$"), 154 | p(withMathJax("$$\\small{\\text{Plan Curvature} = \\frac{-2(ae^2 - cde + bd^2)}{(d^2+e^2) \\sqrt{1+d^2+e^2}}}$$")), 155 | p(withMathJax("$$\\small{\\text{Twisting Curvature} = \\frac{2de(a-b) - c(d^2-e^2)}{(d^2+e^2)(1+d^2+e^2)}}$$")), 156 | p(withMathJax("$$\\small{\\text{Mean Curvature} = -\\frac{a(1+e^2) - cde + b(1+d^2)}{(1+d^2+e^2)^\\frac{3}{2}}}$$")), 157 | p(withMathJax("$$\\small{\\text{ku} =\\sqrt{\\left(\\frac{a(1+e^2) - cde + b(1+d^2)}{(1+d^2+e^2)^\\frac{3}{2}}\\right)^2 - \\frac{4ab-c^2}{(1+d^2+e^2)^2}}}$$")), 158 | p(withMathJax("$$\\small{\\text{Max Curvature} = \\text{Mean Curvature} + \\text{ku}}$$")), 159 | p(withMathJax("$$\\small{\\text{Min Curvature} = \\text{Mean Curvature} - \\text{ku}}$$")), 160 | p(withMathJax("$$\\small{\\text{Slope} = arctan(\\sqrt{d^2 + e^2})}$$")), 161 | p(withMathJax("$$\\small{\\text{Aspect} = -\\frac{\\pi}{2} - arctan2(e,d)}$$")), 162 | p(withMathJax("$$\\small{\\text{Eastness} = sin(\\text{Aspect})}$$")), 163 | p(withMathJax("$$\\small{\\text{Northness} = cos(\\text{Aspect})}$$")), 164 | ))), 165 | fluidRow(column(12, wellPanel(tableOutput("table")))))) 166 | 167 | # Define server logic required to draw a histogram 168 | 169 | server <- function(input, output, output2) { 170 | output$plot <- rgl::renderRglwidget({ 171 | plot_surface(input$a, input$b, input$c, input$d, input$e) 172 | rgl::rglwidget() 173 | }) 174 | 175 | output$table<- renderTable({make_table(input$a, input$b, input$c, input$d, input$e)}, bordered=TRUE) 176 | } 177 | 178 | # Run the application 179 | shinyApp(ui = ui, server = server) 180 | -------------------------------------------------------------------------------- /inst/testdata/adj_sd.RDS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailich/MultiscaleDTM/7edec48c2d1b1e5c8975cfdcb9e1db694271a0f8/inst/testdata/adj_sd.RDS -------------------------------------------------------------------------------- /inst/testdata/bpi.RDS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailich/MultiscaleDTM/7edec48c2d1b1e5c8975cfdcb9e1db694271a0f8/inst/testdata/bpi.RDS -------------------------------------------------------------------------------- /inst/testdata/dmv.RDS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailich/MultiscaleDTM/7edec48c2d1b1e5c8975cfdcb9e1db694271a0f8/inst/testdata/dmv.RDS -------------------------------------------------------------------------------- /inst/testdata/qmetrics.RDS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailich/MultiscaleDTM/7edec48c2d1b1e5c8975cfdcb9e1db694271a0f8/inst/testdata/qmetrics.RDS -------------------------------------------------------------------------------- /inst/testdata/rie.RDS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailich/MultiscaleDTM/7edec48c2d1b1e5c8975cfdcb9e1db694271a0f8/inst/testdata/rie.RDS -------------------------------------------------------------------------------- /inst/testdata/rp.RDS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailich/MultiscaleDTM/7edec48c2d1b1e5c8975cfdcb9e1db694271a0f8/inst/testdata/rp.RDS -------------------------------------------------------------------------------- /inst/testdata/sapa.RDS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailich/MultiscaleDTM/7edec48c2d1b1e5c8975cfdcb9e1db694271a0f8/inst/testdata/sapa.RDS -------------------------------------------------------------------------------- /inst/testdata/slp_asp_queen.RDS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailich/MultiscaleDTM/7edec48c2d1b1e5c8975cfdcb9e1db694271a0f8/inst/testdata/slp_asp_queen.RDS -------------------------------------------------------------------------------- /inst/testdata/slp_asp_rook.RDS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailich/MultiscaleDTM/7edec48c2d1b1e5c8975cfdcb9e1db694271a0f8/inst/testdata/slp_asp_rook.RDS -------------------------------------------------------------------------------- /inst/testdata/tpi.RDS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailich/MultiscaleDTM/7edec48c2d1b1e5c8975cfdcb9e1db694271a0f8/inst/testdata/tpi.RDS -------------------------------------------------------------------------------- /inst/testdata/vrm.RDS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailich/MultiscaleDTM/7edec48c2d1b1e5c8975cfdcb9e1db694271a0f8/inst/testdata/vrm.RDS -------------------------------------------------------------------------------- /man/AdjSD.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/AdjSD.R 3 | \name{AdjSD} 4 | \alias{AdjSD} 5 | \title{Calculates standard deviation of bathymetry (a measure of rugosity) adjusted for slope} 6 | \usage{ 7 | AdjSD( 8 | r, 9 | w = c(3, 3), 10 | na.rm = FALSE, 11 | include_scale = FALSE, 12 | filename = NULL, 13 | overwrite = FALSE, 14 | wopt = list() 15 | ) 16 | } 17 | \arguments{ 18 | \item{r}{DTM as a SpatRaster or RasterLayer in a projected coordinate system where map units match elevation/depth units} 19 | 20 | \item{w}{A vector of length 2 specifying the dimensions of the rectangular window to use where the first number is the number of rows and the second number is the number of columns. Window size must be an odd number.} 21 | 22 | \item{na.rm}{A logical indicating whether or not to remove NA values before calculations} 23 | 24 | \item{include_scale}{logical indicating whether to append window size to the layer names (default = FALSE)} 25 | 26 | \item{filename}{character Output filename.} 27 | 28 | \item{overwrite}{logical. If TRUE, filename is overwritten (default is FALSE).} 29 | 30 | \item{wopt}{list with named options for writing files as in writeRaster} 31 | } 32 | \value{ 33 | a SpatRaster or RasterLayer of adjusted rugosity 34 | } 35 | \description{ 36 | Calculates standard deviation of bathymetry (a measure of rugosity). Using a sliding rectangular window a plane is fit to the data and the standard deviation of the residuals is calculated (Ilich et al., 2023) 37 | } 38 | \examples{ 39 | r<- erupt() 40 | adjsd<- AdjSD(r, w=c(5,5), na.rm = TRUE) 41 | plot(adjsd) 42 | } 43 | \references{ 44 | Ilich, A. R., Misiuk, B., Lecours, V., & Murawski, S. A. (2023). MultiscaleDTM: An open-source R package for multiscale geomorphometric analysis. Transactions in GIS, 27(4). https://doi.org/10.1111/tgis.13067 45 | } 46 | -------------------------------------------------------------------------------- /man/BPI.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/BPI.R 3 | \name{BPI} 4 | \alias{BPI} 5 | \title{Calculates Bathymetric Position Index} 6 | \usage{ 7 | BPI( 8 | r, 9 | w, 10 | stand = "none", 11 | unit = "cell", 12 | na.rm = FALSE, 13 | include_scale = FALSE, 14 | filename = NULL, 15 | overwrite = FALSE, 16 | wopt = list() 17 | ) 18 | } 19 | \arguments{ 20 | \item{r}{r DTM as a SpatRaster or RasterLayer.} 21 | 22 | \item{w}{Vector of length 2 specifying c(inner, outer) radii of the annulus in "cell" or "map" units or a focal weights matrix created by MultiscaleDTM::annulus_window. Inner radius must be less than or equal to outer radius. There is no default size.} 23 | 24 | \item{stand}{Standardization method. Either "none" (the default), "range" or "sd" indicating whether the relative position should be standardized by dividing by the standard deviation or range of included values in the focal window. If stand is 'none' the layer name will be "bpi", otherwise it will be "sbpi" to indicate that the layer has been standardized.} 25 | 26 | \item{unit}{Unit for w if it is a vector (default is unit="cell"). If w is a matrix, unit is ignored and extracted directly from w.} 27 | 28 | \item{na.rm}{Logical indicating whether or not to remove NA values before calculations.} 29 | 30 | \item{include_scale}{Logical indicating whether to append window size to the layer names (default = FALSE) or a character vector specifying the name you would like to append or a number specifying the number of significant digits. If include_scale = TRUE the appended scale will be the inner and outer radius. If unit="map" then window size will have "MU" after the number indicating that the number represents the scale in map units (note units can be extracted from w created with MultiscaleDTM::circle_window and MultiscaleDTM::annulus_window).} 31 | 32 | \item{filename}{Character output filename.} 33 | 34 | \item{overwrite}{Logical. If TRUE, filename is overwritten (default is FALSE).} 35 | 36 | \item{wopt}{List with named options for writing files as in writeRaster.} 37 | } 38 | \value{ 39 | A SpatRaster or RasterLayer. 40 | } 41 | \description{ 42 | Calculates Bathymetric Position Index (BPI). BPI is a measure of relative position that calculates the difference between the value of the focal cell and the mean of cells contained within an annulus shaped neighborhood. Positive values indicate local highs (i.e. peaks) and negative values indicate local lows (i.e. depressions). BPI can be expressed in units of the input DTM raster or can standardized relative to the local topography by dividing by the standard deviation or range of included elevation values in the focal window. BPI calls the function RelPos internally which serves as a general purpose and more flexible function for calculating relative position. 43 | } 44 | \examples{ 45 | r<- erupt() 46 | bpi<- BPI(r, w = c(2,4), stand= "none", unit = "cell", na.rm = TRUE) 47 | plot(bpi) 48 | } 49 | \references{ 50 | Lundblad, E.R., Wright, D.J., Miller, J., Larkin, E.M., Rinehart, R., Naar, D.F., Donahue, B.T., Anderson, S.M., Battista, T., 2006. A benthic terrain classification scheme for American Samoa. Marine Geodesy 29, 89–111. https://doi.org/10.1080/01490410600738021 51 | } 52 | -------------------------------------------------------------------------------- /man/DMV.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/DMV.R 3 | \name{DMV} 4 | \alias{DMV} 5 | \title{Calculates Difference from Mean Value (DMV)} 6 | \usage{ 7 | DMV( 8 | r, 9 | w = dplyr::case_when(tolower(shape) == "rectangle" ~ 3, tolower(shape) == "circle" & 10 | isTRUE(tolower(unit) == "cell") ~ 1, tolower(shape) == "circle" & 11 | isTRUE(tolower(unit) == "map") ~ max(terra::res(r))), 12 | shape = "rectangle", 13 | stand = "none", 14 | unit = "cell", 15 | na.rm = FALSE, 16 | include_scale = FALSE, 17 | filename = NULL, 18 | overwrite = FALSE, 19 | wopt = list() 20 | ) 21 | } 22 | \arguments{ 23 | \item{r}{DTM as a SpatRaster or RasterLayer.} 24 | 25 | \item{w}{For a "rectangle" focal window, a vector of length 2 containing odd numbers specifying dimensions where the first number is the number of rows and the second is the number of columns (or a single number if the number of rows and columns is equal). For a "circle" shaped focal window, a single integer representing the radius in "cell" or "map" units or a focal weights matrix created by MultiscaleDTM::circle_window.} 26 | 27 | \item{shape}{Character representing the shape of the focal window. Either "rectangle" (default) or "circle".} 28 | 29 | \item{stand}{Standardization method. Either "none" (the default), "range" or "sd" indicating whether the TPI should be standardized by dividing by the standard deviation or range of included values in the focal window. If stand is 'none' the layer name will be "dmv", otherwise it will be "sdmv" to indicate that the layer has been standardized.} 30 | 31 | \item{unit}{Unit for w if shape is 'circle' and it is a vector (default is unit="cell"). For circular windows specified with a matrix, unit is ignored and extracted directly from w. For rectangular and custom focal windows set unit='cell' or set unit to NA/NULL.} 32 | 33 | \item{na.rm}{Logical indicating whether or not to remove NA values before calculations.} 34 | 35 | \item{include_scale}{Logical indicating whether to append window size to the layer names (default = FALSE) or a character vector specifying the name you would like to append or a number specifying the number of significant digits. If include_scale = TRUE the number of rows and number of columns will be appended for rectangular windows. For circular windows it will be a single number representing the radius. If unit="map" then window size will have "MU" after the number indicating that the number represents the scale in map units (note units can be extracted from w created with MultiscaleDTM::circle_window).} 36 | 37 | \item{filename}{Character output filename.} 38 | 39 | \item{overwrite}{Logical. If TRUE, filename is overwritten (default is FALSE).} 40 | 41 | \item{wopt}{List with named options for writing files as in writeRaster.} 42 | } 43 | \value{ 44 | a SpatRaster or RasterLayer. 45 | } 46 | \description{ 47 | Calculates Difference from Mean Value (DMV). DMV is a measure of relative position that calculates the difference between the value of the focal cell and the mean of all cells in a rectangular or circular neighborhood. Positive values indicate local highs (i.e. peaks) and negative values indicate local lows (i.e. depressions). DMV can be expressed in units of the input DTM raster or can standardized relative to the local topography by dividing by the standard deviation or range of elevation values in the focal window. DMV calls the function RelPos internally which serves as a general purpose and more flexible function for calculating relative position. 48 | } 49 | \examples{ 50 | r<- erupt() 51 | dmv<- DMV(r, w=c(5,5), shape= "rectangle", stand="range", na.rm = TRUE) 52 | plot(dmv) 53 | } 54 | \references{ 55 | Lecours, V., Devillers, R., Simms, A.E., Lucieer, V.L., Brown, C.J., 2017. Towards a Framework for Terrain Attribute Selection in Environmental Studies. Environmental Modelling & Software 89, 19-30. https://doi.org/10.1016/j.envsoft.2016.11.027 56 | Wilson, J.P., Gallant, J.C. (Eds.), 2000. Terrain Analysis: Principles and Applications. John Wiley & Sons, Inc. 57 | } 58 | -------------------------------------------------------------------------------- /man/DirSlp.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/DirSlope.R 3 | \name{DirSlp} 4 | \alias{DirSlp} 5 | \title{Directional Slope} 6 | \usage{ 7 | DirSlp( 8 | alpha, 9 | dz.dx, 10 | dz.dy, 11 | unit = "degrees", 12 | abs = FALSE, 13 | include_dir = FALSE, 14 | filename = NULL, 15 | overwrite = FALSE, 16 | wopt = list() 17 | ) 18 | } 19 | \arguments{ 20 | \item{alpha}{Angle (in specified 'unit') at which you would like to calculate slope. 0 represents up in map direction (usually North) and it increases clockwise. This can be a single number or it can be a raster of cell values.} 21 | 22 | \item{dz.dx}{The change in elevation per unit distance in the x direction as a SpatRaster, RasterLayer, or a single number. Positive is to the right. See details for more.} 23 | 24 | \item{dz.dy}{The change in elevation per unit distance in the y direction as a SpatRaster or RasterLayer,or a single number Positive is up. See details for more.} 25 | 26 | \item{unit}{"degrees" or "radians" (default is "degrees")} 27 | 28 | \item{abs}{logical indicating whether or not to return the absolute value of slope (default is FALSE)} 29 | 30 | \item{include_dir}{logical indicating whether to append direction to layer name (default is FALSE)} 31 | 32 | \item{filename}{character Output filename. Can be a single filename, or as many filenames as there are layers to write a file for each layer} 33 | 34 | \item{overwrite}{logical. If TRUE, filename is overwritten (default is FALSE).} 35 | 36 | \item{wopt}{list with named options for writing files as in writeRaster} 37 | } 38 | \value{ 39 | a SpatRaster or RasterStack of slope and/or aspect (and components of aspect) 40 | } 41 | \description{ 42 | Calculates the slope along a specified direction. Upslope values are positive and downslope values are negative. 43 | } 44 | \details{ 45 | dz.dx and dz.dy can be calculated at a specified scale via \code{SlpAsp}, \code{Pfit}, \code{Qfit} (zx and zy), or from an existing layer calculated by another program. 46 | } 47 | \examples{ 48 | r<- erupt() 49 | dz1<- SlpAsp(r, metrics = c("dz.dx", "dz.dy")) 50 | dz2<- Qfit(r, metrics = c(), return_params = TRUE, as_derivs=TRUE) 51 | dz3<- Pfit(r, metrics = c("dz.dx", "dz.dy")) 52 | dirslp1<- DirSlp(alpha = 45, dz.dx= dz1$dz.dx, dz.dy= dz1$dz.dy) 53 | dirslp2<- DirSlp(alpha = 45, dz.dx= dz2$zx, dz.dy= dz2$zy) 54 | dirslp3<- DirSlp(alpha = 45, dz.dx= dz3$dz.dx, dz.dy= dz3$dz.dy) 55 | } 56 | \references{ 57 | Neteler, M., & Mitasova, H. (2008). Open source GIS: A GRASS GIS approach (3rd ed.). Springer. 58 | } 59 | -------------------------------------------------------------------------------- /man/Pfit.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/Pfit.R 3 | \name{Pfit} 4 | \alias{Pfit} 5 | \title{Calculates multiscale slope and aspect using a local planar fit.} 6 | \usage{ 7 | Pfit( 8 | r, 9 | w = c(3, 3), 10 | unit = "degrees", 11 | metrics = c("pslope", "paspect", "peastness", "pnorthness"), 12 | na.rm = FALSE, 13 | include_scale = FALSE, 14 | mask_aspect = TRUE, 15 | filename = NULL, 16 | overwrite = FALSE, 17 | wopt = list() 18 | ) 19 | } 20 | \arguments{ 21 | \item{r}{DTM as a SpatRaster (terra) or RasterLayer (raster) in a projected coordinate system where map units match elevation/depth units (up is assumed to be north for calculations of aspect, northness, and eastness).} 22 | 23 | \item{w}{Vector of length 2 specifying the dimensions of the rectangular window to use where the first number is the number of rows and the second number is the number of columns. Window size must be an odd number. Default is 3x3.} 24 | 25 | \item{unit}{"degrees" or "radians".} 26 | 27 | \item{metrics}{Character vector specifying which terrain attributes to return. The default is to return c("pslope", "paspect", "peastness", and "pnorthness"). These are preceded with a 'p' to differentiate them from the measures calculated by SlpAsp() and 'Qfit' where the 'p' indicates that a planar surface was used for the calculation. Additional measures available include "dz.dx" and "dz.dy" which are the x and y components of slope respectively.} 28 | 29 | \item{na.rm}{Logical indicating whether or not to remove NA values before calculations.} 30 | 31 | \item{include_scale}{logical indicating whether to append window size to the layer names (default = FALSE)} 32 | 33 | \item{mask_aspect}{Logical. If TRUE (default), aspect will be set to NA and northness and eastness will be set to 0 when slope = 0. If FALSE, aspect is set to 270 degrees or 3\emph{pi/2 radians ((-pi/2)- atan2(0,0)+2}pi) and northness and eastness will be calculated from this.} 34 | 35 | \item{filename}{character Output filename. Can be a single filename, or as many filenames as there are layers to write a file for each layer} 36 | 37 | \item{overwrite}{logical. If TRUE, filename is overwritten (default is FALSE).} 38 | 39 | \item{wopt}{list with named options for writing files as in writeRaster} 40 | } 41 | \value{ 42 | a SpatRaster (terra) or RasterStack/RasterLayer (raster) 43 | } 44 | \description{ 45 | Calculates multiscale slope and aspect of a DTM over a sliding rectangular window using a using a local planar fit to the surface (Sharpnack and Akin 1969). 46 | } 47 | \details{ 48 | If only first order derivatives are needed, Pfit is faster than Qfit and should provide equivalent results to Qfit for first order derivatives (Jones, 1998) when na.rm=FALSE and approximately the same results otherwise. 49 | } 50 | \examples{ 51 | r<- erupt() 52 | pmetrics<- Pfit(r, w = c(5,5), unit = "degrees", na.rm = TRUE) 53 | plot(pmetrics) 54 | } 55 | \references{ 56 | Evans, I.S., 1980. An integrated system of terrain analysis and slope mapping. Zeitschrift f¨ur Geomorphologic Suppl-Bd 36, 274–295. 57 | Jones, K. H. (1998). A comparison of algorithms used to compute hill slope as a property of the DEM. Computers & Geosciences, 24(4), 315–323. https://doi.org/10.1016/S0098-3004(98)00032-6 58 | Wood, J., 1996. The geomorphological characterisation of digital elevation models (Ph.D.). University of Leicester. 59 | } 60 | -------------------------------------------------------------------------------- /man/Qfit.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/Qfit.R 3 | \name{Qfit} 4 | \alias{Qfit} 5 | \title{Calculates multiscale slope, aspect, curvature, and morphometric features using a local quadratic fit} 6 | \usage{ 7 | Qfit( 8 | r, 9 | w = c(3, 3), 10 | unit = "degrees", 11 | metrics = c("elev", "qslope", "qaspect", "qeastness", "qnorthness", "profc", "planc", 12 | "twistc", "meanc", "maxc", "minc", "features"), 13 | slope_tolerance = 1, 14 | curvature_tolerance = 1e-04, 15 | outlier_quantile = c(0.01, 0.99), 16 | na.rm = FALSE, 17 | force_center = FALSE, 18 | include_scale = FALSE, 19 | mask_aspect = TRUE, 20 | return_params = FALSE, 21 | as_derivs = FALSE, 22 | filename = NULL, 23 | overwrite = FALSE, 24 | wopt = list() 25 | ) 26 | } 27 | \arguments{ 28 | \item{r}{DTM as a SpatRaster (terra) or RasterLayer (raster) in a projected coordinate system where map units match elevation/depth units (up is assumed to be north for calculations of aspect, northness, and eastness).} 29 | 30 | \item{w}{Vector of length 2 specifying the dimensions of the rectangular window to use where the first number is the number of rows and the second number is the number of columns. Window size must be an odd number. Default is 3x3.} 31 | 32 | \item{unit}{"degrees" or "radians".} 33 | 34 | \item{metrics}{Character vector specifying which terrain attributes to return. The default is to return all available metrics, c("elev", "qslope", "qaspect", "qeastness", "qnorthness", "profc", "planc", "twistc", "meanc", "maxc", "minc", "features"). Slope, aspect, eastness, and northness are preceded with a 'q' to differentiate them from the measures calculated by SlpAsp() where the 'q' indicates that a quadratic surface was used for the calculation. 'elev' is the predicted elevation at the central cell (i.e. the intercept term of the regression) and is only relevant when force_center=FALSE. 'profc' is the profile curvature, 'planc' is the plan curvature, 'meanc' is the mean curvature, 'minc' is minimum curvature, and 'features' are morphometric features. See details.} 35 | 36 | \item{slope_tolerance}{Slope tolerance that defines a 'flat' surface (degrees; default = 1.0). Relevant for the features layer.} 37 | 38 | \item{curvature_tolerance}{Curvature tolerance that defines 'planar' surface (default = 0.0001). Relevant for the features layer.} 39 | 40 | \item{outlier_quantile}{A numeric vector of length two or three. If two numbers are used it specifies the lower (Q1) and upper (Q2) quantiles used for determining the interquantile range (IQR). These values should be between 0 and 1 with Q2 > Q1. An optional third number can be used to specify a the size of a regular sample to be taken which can be useful if the full dataset is too large to fit in memory. Values are considered outliers and replaced with NA if they are less than Q1-(100\emph{IQR) or greater than Q2+(100}IQR), where IQR=Q2-Q1. The outlier filter is performed on the results of the regression parameters ('a'-'e' and 'elev') prior to calculation of subsequent terrain attributes. Note that c(0,1) will skip the outlier filtering step and can speed up computations. The default is c(0.01,0.99).} 41 | 42 | \item{na.rm}{Logical indicating whether or not to remove NA values before calculations.} 43 | 44 | \item{force_center}{Logical specifying whether the constrain the model through the central cell of the focal window} 45 | 46 | \item{include_scale}{Logical indicating whether to append window size to the layer names (default = FALSE).} 47 | 48 | \item{mask_aspect}{Logical. If TRUE (default), aspect will be set to NA and northness and eastness will be set to 0 when slope = 0. If FALSE, aspect is set to 270 degrees or 3\emph{pi/2 radians ((-pi/2)- atan2(0,0)+2}pi) and northness and eastness will be calculated from this.} 49 | 50 | \item{return_params}{Logical indicating whether to return Wood/Evans regression parameters (default = FALSE).} 51 | 52 | \item{as_derivs}{Logical indicating whether parameters should be formatted as partial derivatives instead of regression coefficients (default = FALSE) (Minár et al., 2020).} 53 | 54 | \item{filename}{character Output filename. Can be a single filename, or as many filenames as there are layers to write a file for each layer} 55 | 56 | \item{overwrite}{logical. If TRUE, filename is overwritten (default is FALSE).} 57 | 58 | \item{wopt}{list with named options for writing files as in writeRaster} 59 | } 60 | \value{ 61 | a SpatRaster (terra) or RasterStack/RasterLayer (raster) 62 | } 63 | \description{ 64 | Calculates multiscale slope, aspect, curvature, and morphometric features of a DTM over a sliding rectangular window using a local quadratic fit to the surface (Evans, 1980; Wood, 1996). 65 | } 66 | \details{ 67 | This function calculates slope, aspect, eastness, northness, profile curvature, plan curvature, mean curvature, twisting curvature, maximum curvature, minimum curvature, morphometric features, and a smoothed version of the elevation surface using a quadratic surface fit from Z = aX^2+bY^2+cXY+dX+eY+f, where Z is the elevation or depth values, X and Y are the xy coordinates relative to the central cell in the focal window, and a-f are parameters to be estimated (Evans, 1980; Minár et al. 2020; Wood, 1996). For aspect, 0 degrees represents north (or if rotated, the direction that increases as you go up rows in your data) and increases clockwise. For calculations of northness (cos(asp)) and eastness (sin(asp)), up in the y direction is assumed to be north, and if this is not true for your data (e.g. you are using a rotated coordinate system), you must adjust accordingly. All curvature formulas are adapted from Minár et al 2020. Therefore all curvatures are measured in units of 1/length (e.g. m^-1) except twisting curvature which is measured in radians/length (i.e. change in angle per unit distance), and we adopt a geographic sign convention where convex is positive and concave is negative (i.e., hills are considered convex with positive. Naming convention for curvatures is not consistent across the literature, however Minár et al (2020) has suggested a framework in which the reported measures of curvature translate to profile curvature = (kn)s, plan curvature = (kn)c, twisting curvature (Tg)c, mean curvature = kmean, maximum curvature = kmax, minimum curvature = kmin. For morphometric features cross-sectional curvature (zcc) was replaced by planc (kn)c, z''min was replaced by kmax, and z''max was replaced by kmin as these are more robust ways to measures the same types of curvature (Minár et al., 2020). Additionally, the planar feature from Wood (1996) was split into planar flat and slope depending on whether the slope threshold is exceeded or not. 68 | } 69 | \examples{ 70 | r<- erupt() 71 | qmetrics<- Qfit(r, w = c(5,5), unit = "degrees", na.rm = TRUE) 72 | plot(qmetrics) 73 | 74 | # To get only the regression coefficients, set "metrics=c()" and "return_params=TRUE" 75 | reg_coefs<- Qfit(r, w = c(5,5), metrics=c(), unit = "degrees", na.rm = TRUE, return_params=TRUE) 76 | plot(reg_coefs) 77 | } 78 | \references{ 79 | Evans, I.S., 1980. An integrated system of terrain analysis and slope mapping. Zeitschrift f¨ur Geomorphologic Suppl-Bd 36, 274–295. 80 | 81 | Minár, J., Evans, I.S., Jenčo, M., 2020. A comprehensive system of definitions of land surface (topographic) curvatures, with implications for their application in geoscience modelling and prediction. Earth-Science Reviews 211, 103414. https://doi.org/10.1016/j.earscirev.2020.103414 82 | 83 | Wilson, M.F., O’Connell, B., Brown, C., Guinan, J.C., Grehan, A.J., 2007. Multiscale Terrain Analysis of Multibeam Bathymetry Data for Habitat Mapping on the Continental Slope. Marine Geodesy 30, 3-35. https://doi.org/10.1080/01490410701295962 84 | 85 | Wood, J., 1996. The geomorphological characterisation of digital elevation models (Ph.D.). University of Leicester. 86 | } 87 | -------------------------------------------------------------------------------- /man/RIE.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/RIE.R 3 | \name{RIE} 4 | \alias{RIE} 5 | \title{Calculates Roughness Index-Elevation} 6 | \usage{ 7 | RIE( 8 | r, 9 | w = c(3, 3), 10 | na.rm = FALSE, 11 | include_scale = FALSE, 12 | filename = NULL, 13 | overwrite = FALSE, 14 | wopt = list() 15 | ) 16 | } 17 | \arguments{ 18 | \item{r}{DTM as a SpatRaster or RasterLayer} 19 | 20 | \item{w}{A vector of length 2 specifying the dimensions of the rectangular window to use where the first number is the number of rows and the second number is the number of columns. Window size must be an odd number. Default is 3x3.} 21 | 22 | \item{na.rm}{A logical indicating whether or not to remove NA values before calculation of SD} 23 | 24 | \item{include_scale}{logical indicating whether to append window size to the layer names (default = FALSE)} 25 | 26 | \item{filename}{character Output filename.} 27 | 28 | \item{overwrite}{logical. If TRUE, filename is overwritten (default is FALSE).} 29 | 30 | \item{wopt}{list with named options for writing files as in writeRaster} 31 | } 32 | \value{ 33 | a SpatRaster or RasterLayer 34 | } 35 | \description{ 36 | Calculates Roughness Index-Elevation. This is the standard deviation of residual topography in a focal window where residual topography is calculated as the focal pixel minus the focal mean. 37 | } 38 | \details{ 39 | Note the original paper by Cavalli et al (2008) uses a fixed 5x5 window and uses 25 as the denominator indicating use of the population standard deviation. This implementation provides a flexible window size and istead calculates the sample standard deviation which uses a denominator of n-1. 40 | } 41 | \examples{ 42 | r<- erupt() 43 | rie<- RIE(r, w=c(5,5), na.rm = TRUE) 44 | plot(rie) 45 | } 46 | \references{ 47 | Cavalli, M., Tarolli, P., Marchi, L., Dalla Fontana, G., 2008. The effectiveness of airborne LiDAR data in the recognition of channel-bed morphology. CATENA 73, 249–260. https://doi.org/10.1016/j.catena.2007.11.001 48 | } 49 | -------------------------------------------------------------------------------- /man/RelPos.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/RelPos.R 3 | \name{RelPos} 4 | \alias{RelPos} 5 | \title{Calculates Relative Position of a focal cell} 6 | \usage{ 7 | RelPos( 8 | r, 9 | w = dplyr::case_when(tolower(shape) == "rectangle" ~ 3, tolower(shape) == "circle" & 10 | isTRUE(tolower(unit) == "cell") ~ 1, tolower(shape) == "circle" & 11 | isTRUE(tolower(unit) == "map") ~ max(terra::res(r))), 12 | shape = "rectangle", 13 | stand = "none", 14 | exclude_center = FALSE, 15 | unit = "cell", 16 | fun = "mean", 17 | na.rm = FALSE, 18 | include_scale = FALSE, 19 | filename = NULL, 20 | overwrite = FALSE, 21 | wopt = list() 22 | ) 23 | } 24 | \arguments{ 25 | \item{r}{DTM as a SpatRaster or RasterLayer.} 26 | 27 | \item{w}{For a "rectangle" focal window, a vector of length 2 containing odd numbers specifying dimensions where the first number is the number of rows and the second is the number of columns (or a single number if the number of rows and columns is equal). For a "circle" shaped focal window, a single integer representing the radius in "cell" or "map" units or a focal weights matrix created by MultiscaleDTM::circle_window. The default radius is 1 cell if unit= "cell" or the maximum of the x and y cell resolution if unit="map". For an "annulus" shaped focal window, a vector of length 2 specifying c(inner, outer) radii of the annulus in "cell" or "map" units or a focal weights matrix created by MultiscaleDTM::annulus_window. Inner radius must be less than or equal to outer radius. There is no default size for an annulus window. If a "custom" focal window shape is used, w must be a focal weights matrix with 1's for included values and NAs for excluded values.} 28 | 29 | \item{shape}{Character representing the shape of the focal window. Either "rectangle" (default), "circle", or "annulus", or "custom". If a "custom" shape is used, w must be a focal weights matrix.} 30 | 31 | \item{stand}{Standardization method. Either "none" (the default), "range" or "sd" indicating whether the relative position should be standardized by dividing by the standard deviation or range of included values in the focal window. If stand is 'none' the layer name will be "rpos", otherwise it will be "srpos" to indicate that the layer has been standardized.} 32 | 33 | \item{exclude_center}{Logical indicating whether to exclude the central value from focal calculations (Default=FALSE). Use FALSE for DMV and TRUE for TPI. Note, if a focal weights matrix is supplied to w, setting exclude_center=TRUE will overwrite the center value of w to NA, but setting exclude_center=FALSE will not overwrite the central value to be 1.} 34 | 35 | \item{unit}{Unit for w if shape is 'circle' or 'annulus' and it is a vector (default is unit="cell"). For circular and annulus shaped windows specified with a matrix, unit is ignored and extracted directly from w. For rectangular and custom focal windows set unit='cell' or set unit to NA/NULL.} 36 | 37 | \item{fun}{Function to apply to included values to determine the reference elevation. Accepted values are "mean","median", "min", and "max". The default is "mean"} 38 | 39 | \item{na.rm}{Logical indicating whether or not to remove NA values before calculations.} 40 | 41 | \item{include_scale}{Logical indicating whether to append window size to the layer names (default = FALSE) or a character vector specifying the name you would like to append or a number specifying the number of significant digits. If include_scale = TRUE the number of rows and number of columns will be appended for rectangular or custom windows. For circular windows it will be a single number representing the radius. For annulus windows it will be the inner and outer radius. If unit="map" then window size will have "MU" after the number indicating that the number represents the scale in map units (note units can be extracted from w created with MultiscaleDTM::circle_window and MultiscaleDTM::annulus_window).} 42 | 43 | \item{filename}{Character output filename.} 44 | 45 | \item{overwrite}{Logical. If TRUE, filename is overwritten (default is FALSE).} 46 | 47 | \item{wopt}{List with named options for writing files as in writeRaster.} 48 | } 49 | \value{ 50 | A SpatRaster or RasterLayer. 51 | } 52 | \description{ 53 | Calculates the relative position of a focal cell, which represents whether an area is a local high or low. Relative position is the value of the focal cell minus the value of a reference elevation (often the mean of included values in the focal window but see "fun" argument). Positive values indicate local highs (i.e. peaks) and negative values indicate local lows (i.e. depressions). Relative Position can be expressed in units of the input DTM raster or can standardized relative to the local topography by dividing by the standard deviation or range of included elevation values in the focal window. 54 | } 55 | \examples{ 56 | r<- erupt() 57 | rpos<- RelPos(r, w = c(5,5), shape= "rectangle", exclude_center = TRUE, na.rm = TRUE) 58 | plot(rpos) 59 | } 60 | \references{ 61 | Lecours, V., Devillers, R., Simms, A.E., Lucieer, V.L., Brown, C.J., 2017. Towards a Framework for Terrain Attribute Selection in Environmental Studies. Environmental Modelling & Software 89, 19-30. https://doi.org/10.1016/j.envsoft.2016.11.027 62 | 63 | Lundblad, E.R., Wright, D.J., Miller, J., Larkin, E.M., Rinehart, R., Naar, D.F., Donahue, B.T., Anderson, S.M., Battista, T., 2006. A benthic terrain classification scheme for American Samoa. Marine Geodesy 29, 89–111. https://doi.org/10.1080/01490410600738021 64 | 65 | Weiss, A., 2001. Topographic Position and Landforms Analysis. Presented at the ESRI user conference, San Diego, CA. 66 | 67 | Wilson, J.P., Gallant, J.C. (Eds.), 2000. Terrain Analysis: Principles and Applications. John Wiley & Sons, Inc. 68 | } 69 | -------------------------------------------------------------------------------- /man/SAPA.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/SAPA.R 3 | \name{SAPA} 4 | \alias{SAPA} 5 | \title{Calculates surface area to planar area rugosity} 6 | \usage{ 7 | SAPA( 8 | r = NULL, 9 | w = 1, 10 | slope_correction = TRUE, 11 | na.rm = FALSE, 12 | include_scale = FALSE, 13 | slope_layer = NULL, 14 | sa_layer = NULL, 15 | check = TRUE, 16 | tol = 1e-04, 17 | filename = NULL, 18 | overwrite = FALSE, 19 | wopt = list() 20 | ) 21 | } 22 | \arguments{ 23 | \item{r}{DTM as a SpatRaster or RasterLayer in a projected coordinate system where map units match elevation/depth units (Not used if both slope_layer and sa_layer are specified).} 24 | 25 | \item{w}{A single number or a vector of length 2 (row, column) specifying the dimensions of the rectangular window over which surface area will be summed. Window size must be an odd number. 1 refers to "native" scale and surface area and planar area will be calculated per cell (the traditional implementation).} 26 | 27 | \item{slope_correction}{Whether to use the arc-chord ratio to correct planar area for slope (default is TRUE)} 28 | 29 | \item{na.rm}{logical indicating whether to remove/account for NAs in calculations. If FALSE any calculations involving NA will be NA. If TRUE, NA values will be removed and accounted for.} 30 | 31 | \item{include_scale}{logical indicating whether to append window size to the layer names (default = FALSE)} 32 | 33 | \item{slope_layer}{Optionally specify an appropriate slope layer IN RADIANS to use. If not supplied, it will be calculated using the SlpAsp function using the "boundary" method. The slope layer should have a window size that is 2 larger than the w specified for SAPA.} 34 | 35 | \item{sa_layer}{Optionally specify a surface area raster that contains the surface area on a per cell level. This can be calculated with the SurfaceArea function. If calculating SAPA at multiple scales it will be more efficient to supply this so that it does not need to be calculated every time.} 36 | 37 | \item{check}{logical indicating whether to check if any values go below the theoretical value of 1 (default is TRUE). If any are found a warning will be displayed and values less than 1 will be replaced with 1. This is ignored if slope_correction is FALSE.} 38 | 39 | \item{tol}{Tolerance related to 'check' when comparing to see if values are less than 1. Values will still be replaced if check is TRUE, but a warning will not be displayed if the amount below 1 is less than or equal to the tolerance (default = 0.0001).} 40 | 41 | \item{filename}{character Output filename.} 42 | 43 | \item{overwrite}{logical. If TRUE, filename is overwritten (default is FALSE).} 44 | 45 | \item{wopt}{list with named options for writing files as in writeRaster} 46 | } 47 | \value{ 48 | a SpatRaster or RasterLayer 49 | } 50 | \description{ 51 | Calculates surface area (Jenness, 2004) to planar area rugosity and by default corrects planar area for slope using the arc-chord ratio (Du Preez, 2015). Additionally, the method has been modified to allow for calculations at multiple different window sizes (see details and Ilich et al. (2023)). 52 | } 53 | \details{ 54 | Planar area is calculated as the x_dis * y_dis if uncorrected for slope and (x_dis * y_dis)/cos(slope) if corrected for slope. When w=1, this is called "native" scale and is equivalent to what is presented in Du Preez (2015) and available in the ArcGIS Benthic Terrain Modeller add-on. In this case operations are performed on a per cell basis where x_dis is the resolution of the raster in the x direction (left/right) and y_dis is the resolution of the raster in the y direction (up/down) and slope is calculated using the Horn (1981) method. To expand this to multiple scales of analysis, at w > 1 slope is calculated based on Misiuk et al (2021) which provides a modification of the Horn method to extend the matric to multiple spatial scales. Planar area is calculated the same way as for w=1 except that now x_dis is the x resolution of the raster * the number of columns in the focal window, and y_dis is y resolution of the raster * the number of rows. For w > 1, surface area is calculated as the sum of surface areas within the focal window. Although the (modified) Horn slope is used by default to be consistent with Du Preez (2015), slope calculated using a different algorithm (e.g. Wood 1996) could be supplied using the slope_layer argument. Additionally, a slope raster can be supplied if you have already calculated it and do not wish to recalculate it. However, be careful to supply a slope layer measured in radians and calculated at the relevant scale (2 larger than the w of SAPA). 55 | } 56 | \examples{ 57 | r<- erupt() 58 | sapa<- SAPA(r, w=c(5,5), slope_correction = TRUE) 59 | plot(sapa) 60 | } 61 | \references{ 62 | Du Preez, C., 2015. A new arc–chord ratio (ACR) rugosity index for quantifying three-dimensional landscape structural complexity. Landscape Ecol 30, 181–192. https://doi.org/10.1007/s10980-014-0118-8 63 | 64 | Horn, B.K., 1981. Hill Shading and the Reflectance Map. Proceedings of the IEEE 69, 14-47. 65 | 66 | Ilich, A. R., Misiuk, B., Lecours, V., & Murawski, S. A. (2023). MultiscaleDTM: An open-source R package for multiscale geomorphometric analysis. Transactions in GIS, 27(4). https://doi.org/10.1111/tgis.13067 67 | 68 | Jenness, J.S., 2004. Calculating landscape surface area from digital elevation models. Wildlife Society Bulletin 32, 829-839. 69 | 70 | Misiuk, B., Lecours, V., Dolan, M.F.J., Robert, K., 2021. Evaluating the Suitability of Multi-Scale Terrain Attribute Calculation Approaches for Seabed Mapping Applications. Marine Geodesy 44, 327-385. https://doi.org/10.1080/01490419.2021.1925789 71 | } 72 | -------------------------------------------------------------------------------- /man/SlpAsp.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/SlpAsp.R 3 | \name{SlpAsp} 4 | \alias{SlpAsp} 5 | \title{Multiscale Slope and Aspect} 6 | \usage{ 7 | SlpAsp( 8 | r, 9 | w = c(3, 3), 10 | unit = "degrees", 11 | method = "queen", 12 | metrics = c("slope", "aspect", "eastness", "northness"), 13 | na.rm = FALSE, 14 | include_scale = FALSE, 15 | mask_aspect = TRUE, 16 | filename = NULL, 17 | overwrite = FALSE, 18 | wopt = list() 19 | ) 20 | } 21 | \arguments{ 22 | \item{r}{DTM as a SpatRaster or RasterLayer in a projected coordinate system where map units match elevation/depth units} 23 | 24 | \item{w}{A vector of length 2 specifying the dimensions of the rectangular window to use where the first number is the number of rows and the second number is the number of columns. Window size must be an odd number.} 25 | 26 | \item{unit}{"degrees" or "radians"} 27 | 28 | \item{method}{"rook", "queen" (default), or "boundary". The method indicates which cells to use to in computations. "rook" uses only the 4 edge cells directly up, down, left, and right; "queen" adds an additional four corner cells; "boundary" uses all edge cells (see details for more information).} 29 | 30 | \item{metrics}{a character string or vector of character strings of which terrain attributes to return. Default is to return c("slope", "aspect", "eastness", "northness"). Additional measures available include "dz.dx" and "dz.dy" which are the x and y components of slope respectively.} 31 | 32 | \item{na.rm}{Logical indicating whether or not to remove NA values before calculations. Not applicable for "rook" method.} 33 | 34 | \item{include_scale}{logical indicating whether to append window size to the layer names (default = FALSE)} 35 | 36 | \item{mask_aspect}{A logical. When mask_aspect is TRUE (the default), if slope evaluates to 0, aspect will be set to NA and both eastness and northness will be set to 0. When mask_aspect is FALSE, when slope is 0 aspect will be pi/2 radians or 90 degrees which is the behavior of raster::terrain, and northness and eastness will be calculated from that.} 37 | 38 | \item{filename}{character Output filename. Can be a single filename, or as many filenames as there are layers to write a file for each layer} 39 | 40 | \item{overwrite}{logical. If TRUE, filename is overwritten (default is FALSE).} 41 | 42 | \item{wopt}{list with named options for writing files as in writeRaster} 43 | } 44 | \value{ 45 | a SpatRaster or RasterStack of slope and/or aspect (and components of aspect) 46 | } 47 | \description{ 48 | Calculates multiscale slope and aspect based on a modified version of the algorithm from Misiuk et al (2021) which extends classical formulations of slope restricted to a 3x3 window to multiple scales by using only cells on the edges of the focal window (see details for more information). 49 | } 50 | \details{ 51 | Slope is calculated atan(sqrt(dz.dx^2 + dz.dy^2)) and aspect is calculated as (-pi/2)-atan_2(dz.dy, dz.dx) and then constrained from 0 to 2 pi/0 to 360 degrees. 52 | dz.dx is the difference in between the weighted mean of the right side of the focal window and weighted mean of the left side of the focal window divided by the x distance of the focal window in map units. 53 | dz.dy is the difference in between the weighted mean of the top side of the focal window and weighted mean of the bottom side of the focal window divided by the y distance of the focal window in map units. 54 | The cells used in these computations is dependent on the "method" chosen. For methods "queen" and "boundary", corner cells have half the weight of all other cells used in the computations. 55 | } 56 | \examples{ 57 | r<- erupt() 58 | slp_asp<- SlpAsp(r = r, w = c(5,5), unit = "degrees", 59 | method = "queen", metrics = c("slope", "aspect", 60 | "eastness", "northness")) 61 | plot(slp_asp) 62 | } 63 | \references{ 64 | Fleming, M.D., Hoffer, R.M., 1979. Machine processing of landsat MSS data and DMA topographic data for forest cover type mapping (No. LARS Technical Report 062879). Laboratory for Applications of Remote Sensing, Purdue University, West Lafayette, Indiana. 65 | 66 | Horn, B.K., 1981. Hill Shading and the Reflectance Map. Proceedings of the IEEE 69, 14-47. 67 | 68 | Misiuk, B., Lecours, V., Dolan, M.F.J., Robert, K., 2021. Evaluating the Suitability of Multi-Scale Terrain Attribute Calculation Approaches for Seabed Mapping Applications. Marine Geodesy 44, 327-385. https://doi.org/10.1080/01490419.2021.1925789 69 | 70 | Ritter, P., 1987. A vector-based slope and aspect generation algorithm. Photogrammetric Engineering and Remote Sensing 53, 1109-1111. 71 | } 72 | -------------------------------------------------------------------------------- /man/SurfaceArea.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/SurfaceArea.R 3 | \name{SurfaceArea} 4 | \alias{SurfaceArea} 5 | \title{Calculates surface area of a DTM} 6 | \usage{ 7 | SurfaceArea( 8 | r, 9 | na.rm = FALSE, 10 | filename = NULL, 11 | overwrite = FALSE, 12 | wopt = list() 13 | ) 14 | } 15 | \arguments{ 16 | \item{r}{DTM as a SpatRaster or RasterLayer in a projected coordinate system where map units match elevation/depth units} 17 | 18 | \item{na.rm}{Logical indicating whether to remove NAs from calculations. When FALSE, the sum of the eight triangles is calculated. When TRUE, the mean of the created triangles is calculated and multiplied by 8 to scale it to the proper area.} 19 | 20 | \item{filename}{character Output filename.} 21 | 22 | \item{overwrite}{logical. If TRUE, filename is overwritten (default is FALSE).} 23 | 24 | \item{wopt}{list with named options for writing files as in writeRaster} 25 | } 26 | \value{ 27 | a SpatRaster or RasterLayer 28 | } 29 | \description{ 30 | Calculates surface area on a per cell basis of a DTM based on Jenness, 2004. 31 | } 32 | \examples{ 33 | r<- erupt() 34 | sa<- SurfaceArea(r) 35 | plot(sa) 36 | } 37 | \references{ 38 | Jenness, J.S., 2004. Calculating landscape surface area from digital elevation models. Wildlife Society Bulletin 32, 829-839. 39 | } 40 | -------------------------------------------------------------------------------- /man/TPI.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/TPI.R 3 | \name{TPI} 4 | \alias{TPI} 5 | \title{Calculates Topographic Position Index} 6 | \usage{ 7 | TPI( 8 | r, 9 | w = dplyr::case_when(tolower(shape) == "rectangle" ~ 3, tolower(shape) == "circle" & 10 | isTRUE(tolower(unit) == "cell") ~ 1, tolower(shape) == "circle" & 11 | isTRUE(tolower(unit) == "map") ~ max(terra::res(r))), 12 | shape = "rectangle", 13 | stand = "none", 14 | unit = "cell", 15 | na.rm = FALSE, 16 | include_scale = FALSE, 17 | filename = NULL, 18 | overwrite = FALSE, 19 | wopt = list() 20 | ) 21 | } 22 | \arguments{ 23 | \item{r}{DTM as a SpatRaster or RasterLayer. TPI calls the function RelPos internally which serves as a general purpose and more flexible function for calculating relative position.} 24 | 25 | \item{w}{For a "rectangle" focal window, a vector of length 2 containing odd numbers specifying dimensions where the first number is the number of rows and the second is the number of columns (or a single number if the number of rows and columns is equal). For a "circle" shaped focal window, a single integer representing the radius in "cell" or "map" units or a focal weights matrix created by MultiscaleDTM::circle_window.} 26 | 27 | \item{shape}{Character representing the shape of the focal window. Either "rectangle" (default) or "circle".} 28 | 29 | \item{stand}{Standardization method. Either "none" (the default), "range" or "sd" indicating whether the TPI should be standardized by dividing by the standard deviation or range of included values in the focal window. If stand is 'none' the layer name will be "tpi", otherwise it will be "stpi" to indicate that the layer has been standardized.} 30 | 31 | \item{unit}{Unit for w if shape is 'circle' and it is a vector (default is unit="cell"). For circular windows specified with a matrix, unit is ignored and extracted directly from w. For rectangular and custom focal windows set unit='cell' or set unit to NA/NULL.} 32 | 33 | \item{na.rm}{Logical indicating whether or not to remove NA values before calculations.} 34 | 35 | \item{include_scale}{Logical indicating whether to append window size to the layer names (default = FALSE) or a character vector specifying the name you would like to append or a number specifying the number of significant digits. If include_scale = TRUE the number of rows and number of columns will be appended for rectangular windows. For circular windows it will be a single number representing the radius. If unit="map" then window size will have "MU" after the number indicating that the number represents the scale in map units (note units can be extracted from w created with MultiscaleDTM::circle_window).} 36 | 37 | \item{filename}{Character output filename.} 38 | 39 | \item{overwrite}{Logical. If TRUE, filename is overwritten (default is FALSE).} 40 | 41 | \item{wopt}{List with named options for writing files as in writeRaster.} 42 | } 43 | \value{ 44 | SpatRaster or RasterLayer. 45 | } 46 | \description{ 47 | Calculates Topographic Position Index (TPI). TPI is a measure of relative position that calculates the the difference between the value of the focal cell and the mean of mean of the surrounding cells (i.e. local mean but excluding the value of the focal cell).Positive values indicate local highs (i.e. peaks) and negative values indicate local lows (i.e. depressions). TPI can be expressed in units of the input DTM raster or can standardized relative to the local topography by dividing by the standard deviation or range of included elevation values in the focal window. 48 | } 49 | \examples{ 50 | r<- erupt() 51 | tpi<- TPI(r, w=c(5,5), shape="rectangle", stand="none", na.rm = TRUE) 52 | plot(tpi) 53 | } 54 | \references{ 55 | Weiss, A., 2001. Topographic Position and Landforms Analysis. Presented at the ESRI user conference, San Diego, CA. 56 | } 57 | -------------------------------------------------------------------------------- /man/VRM.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/VRM.R 3 | \name{VRM} 4 | \alias{VRM} 5 | \title{Implementation of the Sappington et al., (2007) vector ruggedness measure} 6 | \usage{ 7 | VRM( 8 | r, 9 | w = c(3, 3), 10 | na.rm = FALSE, 11 | include_scale = FALSE, 12 | filename = NULL, 13 | overwrite = FALSE, 14 | wopt = list() 15 | ) 16 | } 17 | \arguments{ 18 | \item{r}{DTM as a SpatRaster or RasterLayer} 19 | 20 | \item{w}{A vector of length 2 specifying the dimensions of the rectangular window to use where the first number is the number of rows and the second number is the number of columns. Window size must be an odd number. Default is 3x3.} 21 | 22 | \item{na.rm}{A logical indicating whether or not to remove NA values before calculations. See details for more information.} 23 | 24 | \item{include_scale}{logical indicating whether to append window size to the layer names (default = FALSE)} 25 | 26 | \item{filename}{character Output filename.} 27 | 28 | \item{overwrite}{logical. If TRUE, filename is overwritten (default is FALSE).} 29 | 30 | \item{wopt}{list with named options for writing files as in writeRaster} 31 | } 32 | \value{ 33 | a RasterLayer 34 | } 35 | \description{ 36 | Implementation of the Sappington et al., (2007) vector ruggedness measure, modified from Evans (2021). 37 | } 38 | \details{ 39 | If the crs is cartesian, when na.rm=TRUE, NA's will be removed from the slope/aspect calculations. When the crs is lat/lon, na.rm=TRUE will not affect the calculation of slope/aspect as terra::terrain will be used since it can calculate slope and aspect for spherical geometry but it does not support na.rm. In both cases when na.rm=TRUE, the x, y, and z components will be summed with na.rm=TRUE, and the N used in the denominator of the VRM equation will be the number of non-NA cells in the window rather than the total number of cells. 40 | } 41 | \examples{ 42 | r<- erupt() 43 | vrm<- VRM(r, w=c(5,5), na.rm = TRUE) 44 | plot(vrm) 45 | } 46 | \references{ 47 | Evans JS (2021). spatialEco. R package version 1.3-6, https://github.com/jeffreyevans/spatialEco. 48 | 49 | Sappington, J.M., Longshore, K.M., Thompson, D.B., 2007. Quantifying Landscape Ruggedness for Animal Habitat Analysis: A Case Study Using Bighorn Sheep in the Mojave Desert. The Journal of Wildlife Management 71, 1419-1426. https://doi.org/10.2193/2005-723 50 | } 51 | -------------------------------------------------------------------------------- /man/annulus_window.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/RelPos.R 3 | \name{annulus_window} 4 | \alias{annulus_window} 5 | \title{Creates annulus focal window} 6 | \usage{ 7 | annulus_window(radius, unit, resolution) 8 | } 9 | \arguments{ 10 | \item{radius}{radius of inner annulus c(inner,outer). Inner radius must be less than or equal to outer radius.} 11 | 12 | \item{unit}{unit for radius. Either "cell" (number of cells, the default) or "map" for map units (e.g. meters).} 13 | 14 | \item{resolution}{resolution of intended raster layer (one number or a vector of length 2). Only necessary if unit= "map"} 15 | } 16 | \value{ 17 | a matrix of 1's and NA's showing which cells to include and exclude respectively in focal calculations. It also contains attributes attributes 'unit', 'scale', and 'shape'. 18 | } 19 | \description{ 20 | Creates annulus focal window around central pixel. 21 | } 22 | -------------------------------------------------------------------------------- /man/circle_window.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/RelPos.R 3 | \name{circle_window} 4 | \alias{circle_window} 5 | \title{Creates circular focal window} 6 | \usage{ 7 | circle_window(radius, unit, resolution, return_dismat = FALSE) 8 | } 9 | \arguments{ 10 | \item{radius}{radius of circular window} 11 | 12 | \item{unit}{unit for radius. Either "cell" (number of cells) or "map" for map units (e.g. meters).} 13 | 14 | \item{resolution}{resolution of intended raster layer (one number or a vector of length 2). Only necessary if unit= "map"} 15 | 16 | \item{return_dismat}{logical, if TRUE return a matrix of distances from focal cell instead of a matrix to pass to terra::focal.} 17 | } 18 | \value{ 19 | a matrix of 1's and NA's showing which cells to include and exclude respectively in focal calculations, or if return_dismat=TRUE, a matrix indicating the distance from the focal cell. It also contains attributes attributes 'unit', 'scale', and 'shape' if return_dismat=FALSE, and if return_dismat=TRUE the attribute 'unit'. 20 | } 21 | \description{ 22 | Creates circular focal window around central pixel. 23 | } 24 | -------------------------------------------------------------------------------- /man/classify_features_ff.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/Qfit.R 3 | \name{classify_features_ff} 4 | \alias{classify_features_ff} 5 | \title{Helper function factory to classify morphometric features} 6 | \usage{ 7 | classify_features_ff(slope_tolerance = 1, curvature_tolerance = 1e-04) 8 | } 9 | \arguments{ 10 | \item{slope_tolerance}{Slope tolerance that defines a 'flat' surface (degrees; default is 1.0). Relevant for the features layer.} 11 | 12 | \item{curvature_tolerance}{Curvature tolerance that defines 'planar' surface (default is 0.0001). Relevant for the features layer.} 13 | } 14 | \value{ 15 | A function that can be passed to raster::overlay to classify morphometric features 16 | } 17 | \description{ 18 | Helper function factory to classify morphometric features according to a modified version of Wood 1996 page 120 19 | } 20 | \references{ 21 | Wood, J., 1996. The geomorphological characterisation of digital elevation models (Ph.D.). University of Leicester. 22 | } 23 | -------------------------------------------------------------------------------- /man/erupt.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/erupt.R 3 | \name{erupt} 4 | \alias{erupt} 5 | \title{Create georeferenced version of R's built in volcano dataset} 6 | \usage{ 7 | erupt() 8 | } 9 | \value{ 10 | SpatRaster 11 | } 12 | \description{ 13 | Create georeferenced version of R's built in volcano dataset. Useful dataset for generating quick examples. 14 | } 15 | \examples{ 16 | r<- erupt() 17 | } 18 | -------------------------------------------------------------------------------- /man/explore_terrain.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/explore_terrain.R 3 | \name{explore_terrain} 4 | \alias{explore_terrain} 5 | \title{Interactive Shiny app to look at terrain attributes} 6 | \usage{ 7 | explore_terrain() 8 | } 9 | \value{ 10 | No return value, launches Shiny app. 11 | } 12 | \description{ 13 | Interactive Shiny app to look at terrain attributes based on a surface fit using a Wood/Evans Quadratic Equation: Z =ax^2+by^2+cxy+dx+ey(+f) 14 | } 15 | \references{ 16 | Evans, I.S., 1980. An integrated system of terrain analysis and slope mapping. Zeitschrift f¨ur Geomorphologic Suppl-Bd 36, 274–295. 17 | 18 | Wood, J., 1996. The geomorphological characterisation of digital elevation models (Ph.D.). University of Leicester. 19 | 20 | Minár, J., Evans, I.S., Jenčo, M., 2020. A comprehensive system of definitions of land surface (topographic) curvatures, with implications for their application in geoscience modelling and prediction. Earth-Science Reviews 211, 103414. https://doi.org/10.1016/j.earscirev.2020.103414 21 | } 22 | -------------------------------------------------------------------------------- /man/figures/Qfit_annotated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailich/MultiscaleDTM/7edec48c2d1b1e5c8975cfdcb9e1db694271a0f8/man/figures/Qfit_annotated.png -------------------------------------------------------------------------------- /man/figures/README-AdjSD-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailich/MultiscaleDTM/7edec48c2d1b1e5c8975cfdcb9e1db694271a0f8/man/figures/README-AdjSD-1.png -------------------------------------------------------------------------------- /man/figures/README-BPI-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailich/MultiscaleDTM/7edec48c2d1b1e5c8975cfdcb9e1db694271a0f8/man/figures/README-BPI-1.png -------------------------------------------------------------------------------- /man/figures/README-DMV-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailich/MultiscaleDTM/7edec48c2d1b1e5c8975cfdcb9e1db694271a0f8/man/figures/README-DMV-1.png -------------------------------------------------------------------------------- /man/figures/README-RIE-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailich/MultiscaleDTM/7edec48c2d1b1e5c8975cfdcb9e1db694271a0f8/man/figures/README-RIE-1.png -------------------------------------------------------------------------------- /man/figures/README-RP-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailich/MultiscaleDTM/7edec48c2d1b1e5c8975cfdcb9e1db694271a0f8/man/figures/README-RP-1.png -------------------------------------------------------------------------------- /man/figures/README-SAPA-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailich/MultiscaleDTM/7edec48c2d1b1e5c8975cfdcb9e1db694271a0f8/man/figures/README-SAPA-1.png -------------------------------------------------------------------------------- /man/figures/README-SlpAsp-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailich/MultiscaleDTM/7edec48c2d1b1e5c8975cfdcb9e1db694271a0f8/man/figures/README-SlpAsp-1.png -------------------------------------------------------------------------------- /man/figures/README-TPI-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailich/MultiscaleDTM/7edec48c2d1b1e5c8975cfdcb9e1db694271a0f8/man/figures/README-TPI-1.png -------------------------------------------------------------------------------- /man/figures/README-Topo-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailich/MultiscaleDTM/7edec48c2d1b1e5c8975cfdcb9e1db694271a0f8/man/figures/README-Topo-1.png -------------------------------------------------------------------------------- /man/figures/README-VRM-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailich/MultiscaleDTM/7edec48c2d1b1e5c8975cfdcb9e1db694271a0f8/man/figures/README-VRM-1.png -------------------------------------------------------------------------------- /man/figures/RIE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailich/MultiscaleDTM/7edec48c2d1b1e5c8975cfdcb9e1db694271a0f8/man/figures/RIE.png -------------------------------------------------------------------------------- /man/figures/SAPA_annotated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailich/MultiscaleDTM/7edec48c2d1b1e5c8975cfdcb9e1db694271a0f8/man/figures/SAPA_annotated.png -------------------------------------------------------------------------------- /man/figures/SlpAsp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailich/MultiscaleDTM/7edec48c2d1b1e5c8975cfdcb9e1db694271a0f8/man/figures/SlpAsp.png -------------------------------------------------------------------------------- /man/figures/VRM_annotated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailich/MultiscaleDTM/7edec48c2d1b1e5c8975cfdcb9e1db694271a0f8/man/figures/VRM_annotated.png -------------------------------------------------------------------------------- /man/figures/VRM_focal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailich/MultiscaleDTM/7edec48c2d1b1e5c8975cfdcb9e1db694271a0f8/man/figures/VRM_focal.png -------------------------------------------------------------------------------- /man/figures/WindowShapes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailich/MultiscaleDTM/7edec48c2d1b1e5c8975cfdcb9e1db694271a0f8/man/figures/WindowShapes.png -------------------------------------------------------------------------------- /man/figures/adj_sd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailich/MultiscaleDTM/7edec48c2d1b1e5c8975cfdcb9e1db694271a0f8/man/figures/adj_sd.png -------------------------------------------------------------------------------- /man/figures/chaintape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailich/MultiscaleDTM/7edec48c2d1b1e5c8975cfdcb9e1db694271a0f8/man/figures/chaintape.png -------------------------------------------------------------------------------- /man/figures/kwindow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailich/MultiscaleDTM/7edec48c2d1b1e5c8975cfdcb9e1db694271a0f8/man/figures/kwindow.png -------------------------------------------------------------------------------- /man/figures/qmetrics.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailich/MultiscaleDTM/7edec48c2d1b1e5c8975cfdcb9e1db694271a0f8/man/figures/qmetrics.jpg -------------------------------------------------------------------------------- /man/kmax.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/curvatures.R 3 | \name{kmax} 4 | \alias{kmax} 5 | \title{Calculate max curvature} 6 | \usage{ 7 | kmax(a, b, c, d, e) 8 | } 9 | \arguments{ 10 | \item{a}{regression coefficient} 11 | 12 | \item{b}{regression coefficient} 13 | 14 | \item{c}{regression coefficient} 15 | 16 | \item{d}{regression coefficient} 17 | 18 | \item{e}{regression coefficient} 19 | } 20 | \description{ 21 | Calculate max curvature, kmax, from the equation Z =ax^2+by^2+cxy+dx+ey(+f). 22 | } 23 | \references{ 24 | Evans, I.S., 1980. An integrated system of terrain analysis and slope mapping. Zeitschrift f¨ur Geomorphologic Suppl-Bd 36, 274–295. 25 | 26 | Wood, J., 1996. The geomorphological characterisation of digital elevation models (Ph.D.). University of Leicester. 27 | 28 | Minár, J., Evans, I.S., Jenčo, M., 2020. A comprehensive system of definitions of land surface (topographic) curvatures, with implications for their application in geoscience modelling and prediction. Earth-Science Reviews 211, 103414. https://doi.org/10.1016/j.earscirev.2020.103414 29 | } 30 | -------------------------------------------------------------------------------- /man/kmean.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/curvatures.R 3 | \name{kmean} 4 | \alias{kmean} 5 | \title{Calculate mean curvature} 6 | \usage{ 7 | kmean(a, b, c, d, e) 8 | } 9 | \arguments{ 10 | \item{a}{regression coefficient} 11 | 12 | \item{b}{regression coefficient} 13 | 14 | \item{c}{regression coefficient} 15 | 16 | \item{d}{regression coefficient} 17 | 18 | \item{e}{regression coefficient} 19 | } 20 | \description{ 21 | Calculate mean curvature, kmean, from the equation Z =ax^2+by^2+cxy+dx+ey(+f). 22 | } 23 | \references{ 24 | Evans, I.S., 1980. An integrated system of terrain analysis and slope mapping. Zeitschrift f¨ur Geomorphologic Suppl-Bd 36, 274–295. 25 | 26 | Wood, J., 1996. The geomorphological characterisation of digital elevation models (Ph.D.). University of Leicester. 27 | 28 | Minár, J., Evans, I.S., Jenčo, M., 2020. A comprehensive system of definitions of land surface (topographic) curvatures, with implications for their application in geoscience modelling and prediction. Earth-Science Reviews 211, 103414. https://doi.org/10.1016/j.earscirev.2020.103414 29 | } 30 | -------------------------------------------------------------------------------- /man/kmin.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/curvatures.R 3 | \name{kmin} 4 | \alias{kmin} 5 | \title{Calculate min curvature} 6 | \usage{ 7 | kmin(a, b, c, d, e) 8 | } 9 | \arguments{ 10 | \item{a}{regression coefficient} 11 | 12 | \item{b}{regression coefficient} 13 | 14 | \item{c}{regression coefficient} 15 | 16 | \item{d}{regression coefficient} 17 | 18 | \item{e}{regression coefficient} 19 | } 20 | \description{ 21 | Calculate min curvature, kmin, from the equation Z =ax^2+by^2+cxy+dx+ey(+f). 22 | } 23 | \references{ 24 | Evans, I.S., 1980. An integrated system of terrain analysis and slope mapping. Zeitschrift f¨ur Geomorphologic Suppl-Bd 36, 274–295. 25 | 26 | Wood, J., 1996. The geomorphological characterisation of digital elevation models (Ph.D.). University of Leicester. 27 | 28 | Minár, J., Evans, I.S., Jenčo, M., 2020. A comprehensive system of definitions of land surface (topographic) curvatures, with implications for their application in geoscience modelling and prediction. Earth-Science Reviews 211, 103414. https://doi.org/10.1016/j.earscirev.2020.103414 29 | } 30 | -------------------------------------------------------------------------------- /man/knc.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/curvatures.R 3 | \name{knc} 4 | \alias{knc} 5 | \title{Calculate normal contour curvature} 6 | \usage{ 7 | knc(a, b, c, d, e) 8 | } 9 | \arguments{ 10 | \item{a}{regression coefficient} 11 | 12 | \item{b}{regression coefficient} 13 | 14 | \item{c}{regression coefficient} 15 | 16 | \item{d}{regression coefficient} 17 | 18 | \item{e}{regression coefficient} 19 | } 20 | \description{ 21 | Calculate normal contour curvature (kn)c, which is the principal representative of the plan curvature group based on regression coefficients from the equation Z =ax^2+by^2+cxy+dx+ey(+f). 22 | } 23 | \references{ 24 | Evans, I.S., 1980. An integrated system of terrain analysis and slope mapping. Zeitschrift f¨ur Geomorphologic Suppl-Bd 36, 274–295. 25 | 26 | Wood, J., 1996. The geomorphological characterisation of digital elevation models (Ph.D.). University of Leicester. 27 | 28 | Minár, J., Evans, I.S., Jenčo, M., 2020. A comprehensive system of definitions of land surface (topographic) curvatures, with implications for their application in geoscience modelling and prediction. Earth-Science Reviews 211, 103414. https://doi.org/10.1016/j.earscirev.2020.103414 29 | } 30 | -------------------------------------------------------------------------------- /man/kns.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/curvatures.R 3 | \name{kns} 4 | \alias{kns} 5 | \title{Calculate normal slope line curvature} 6 | \usage{ 7 | kns(a, b, c, d, e) 8 | } 9 | \arguments{ 10 | \item{a}{regression coefficient} 11 | 12 | \item{b}{regression coefficient} 13 | 14 | \item{c}{regression coefficient} 15 | 16 | \item{d}{regression coefficient} 17 | 18 | \item{e}{regression coefficient} 19 | } 20 | \description{ 21 | Calculate normal slope line curvature (kn)s, which is the principal representative of the profile curvature group based on regression coefficients from the equation Z =ax^2+by^2+cxy+dx+ey(+f). 22 | } 23 | \references{ 24 | Evans, I.S., 1980. An integrated system of terrain analysis and slope mapping. Zeitschrift f¨ur Geomorphologic Suppl-Bd 36, 274–295. 25 | 26 | Wood, J., 1996. The geomorphological characterisation of digital elevation models (Ph.D.). University of Leicester. 27 | 28 | Minár, J., Evans, I.S., Jenčo, M., 2020. A comprehensive system of definitions of land surface (topographic) curvatures, with implications for their application in geoscience modelling and prediction. Earth-Science Reviews 211, 103414. https://doi.org/10.1016/j.earscirev.2020.103414 29 | } 30 | -------------------------------------------------------------------------------- /man/ku.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/curvatures.R 3 | \name{ku} 4 | \alias{ku} 5 | \title{Calculate unsphericity curvature} 6 | \usage{ 7 | ku(a, b, c, d, e) 8 | } 9 | \arguments{ 10 | \item{a}{regression coefficient} 11 | 12 | \item{b}{regression coefficient} 13 | 14 | \item{c}{regression coefficient} 15 | 16 | \item{d}{regression coefficient} 17 | 18 | \item{e}{regression coefficient} 19 | } 20 | \description{ 21 | Calculate unsphericity curvature, ku, from the equation Z =ax^2+by^2+cxy+dx+ey(+f). 22 | } 23 | \references{ 24 | Evans, I.S., 1980. An integrated system of terrain analysis and slope mapping. Zeitschrift f¨ur Geomorphologic Suppl-Bd 36, 274–295. 25 | 26 | Wood, J., 1996. The geomorphological characterisation of digital elevation models (Ph.D.). University of Leicester. 27 | 28 | Minár, J., Evans, I.S., Jenčo, M., 2020. A comprehensive system of definitions of land surface (topographic) curvatures, with implications for their application in geoscience modelling and prediction. Earth-Science Reviews 211, 103414. https://doi.org/10.1016/j.earscirev.2020.103414 29 | } 30 | -------------------------------------------------------------------------------- /man/outlier_filter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/Qfit.R 3 | \name{outlier_filter} 4 | \alias{outlier_filter} 5 | \title{Helper function to filter outliers from regression parameters using interquartile range} 6 | \usage{ 7 | outlier_filter(params, outlier_quantile, wopt = list()) 8 | } 9 | \arguments{ 10 | \item{params}{regression parameters for fitted surface} 11 | 12 | \item{outlier_quantile}{A numeric vector of length two or three. If two numbers are used it specifies the lower (Q1) and upper (Q2) quantiles used for determining the interquantile range (IQR). These values should be between 0 and 1 with Q2 > Q1. An optional third number can be used to specify a the size of a regular sample to be taken which can be useful if the full dataset is too large to fit in memory. Values are considered outliers if they are less than Q1-(100\emph{IQR) or greater than Q2+(100}IQR), where IQR=Q2-Q1.} 13 | 14 | \item{wopt}{list with named options for writing files as in writeRaster} 15 | } 16 | \description{ 17 | Helper function to filter outliers from regression parameters using interquartile range 18 | } 19 | -------------------------------------------------------------------------------- /man/tgc.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/curvatures.R 3 | \name{tgc} 4 | \alias{tgc} 5 | \title{Calculate contour geodesic torsion} 6 | \usage{ 7 | tgc(a, b, c, d, e) 8 | } 9 | \arguments{ 10 | \item{a}{regression coefficient} 11 | 12 | \item{b}{regression coefficient} 13 | 14 | \item{c}{regression coefficient} 15 | 16 | \item{d}{regression coefficient} 17 | 18 | \item{e}{regression coefficient} 19 | } 20 | \description{ 21 | Calculate contour geodesic torsion (tg)c, which is the principal representative of the twisting curvature group based on regression coefficients from the equation Z =ax^2+by^2+cxy+dx+ey(+f). 22 | } 23 | \references{ 24 | Evans, I.S., 1980. An integrated system of terrain analysis and slope mapping. Zeitschrift f¨ur Geomorphologic Suppl-Bd 36, 274–295. 25 | 26 | Wood, J., 1996. The geomorphological characterisation of digital elevation models (Ph.D.). University of Leicester. 27 | 28 | Minár, J., Evans, I.S., Jenčo, M., 2020. A comprehensive system of definitions of land surface (topographic) curvatures, with implications for their application in geoscience modelling and prediction. Earth-Science Reviews 211, 103414. https://doi.org/10.1016/j.earscirev.2020.103414 29 | } 30 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.so 3 | *.dll 4 | -------------------------------------------------------------------------------- /src/AdjSD.cpp: -------------------------------------------------------------------------------- 1 | #define ARMA_WARN_LEVEL 1 2 | #include 3 | // [[Rcpp::depends(RcppArmadillo)]] 4 | using namespace Rcpp; 5 | using namespace arma; 6 | 7 | //SD from planar fit with na.rm=TRUE 8 | // [[Rcpp::export]] 9 | arma::vec C_AdjSD_narmT(const arma::vec& z, 10 | const arma::mat& X_full, 11 | size_t ni, size_t nw) { 12 | arma::vec out(ni, arma::fill::value(NA_REAL)); 13 | unsigned int thresh = 4; 14 | 15 | for (size_t i = 0; i < ni; ++i) { 16 | size_t start = i * nw; 17 | arma::vec zw_full = z.subvec(start, start + nw - 1); 18 | arma::uvec non_na_idx = arma::find_finite(zw_full); 19 | 20 | if (non_na_idx.n_elem >= thresh) { 21 | arma::vec zw = zw_full.elem(non_na_idx); 22 | 23 | arma::vec unique_vals = arma::unique(zw); 24 | if (unique_vals.n_elem == 1) { 25 | out[i] = 0; 26 | } else { 27 | arma::mat X = X_full.rows(non_na_idx); 28 | arma::vec resid = zw - X * arma::solve(X, zw); 29 | out[i] = arma::stddev(resid); 30 | } 31 | } 32 | } 33 | return out; 34 | } 35 | 36 | //SD from planar fit with na.rm=FALSE 37 | // [[Rcpp::export]] 38 | arma::vec C_AdjSD_narmF(const arma::vec& z, 39 | const arma::mat& X, 40 | const arma::mat& Xt, 41 | const arma::mat& XtX_inv, 42 | size_t ni, size_t nw) { 43 | arma::vec out(ni, arma::fill::value(NA_REAL)); 44 | 45 | for (size_t i = 0; i < ni; ++i) { 46 | size_t start = i * nw; 47 | arma::vec Z = z.subvec(start, start + nw - 1); 48 | 49 | if (!Z.has_nan()) { 50 | arma::vec unique_vals = arma::unique(Z); 51 | if (unique_vals.n_elem == 1) { 52 | out[i] = 0; 53 | } else { 54 | arma::vec resid = Z - X * (XtX_inv * (Xt * Z)); 55 | out[i] = arma::stddev(resid); 56 | } 57 | } 58 | } 59 | 60 | return out; 61 | } -------------------------------------------------------------------------------- /src/CountVals.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace Rcpp; 3 | 4 | // [[Rcpp::export]] 5 | NumericVector C_CountVals(const NumericVector& z, 6 | size_t ni, size_t nw) { 7 | NumericVector out(ni, NA_REAL); 8 | const double* z_ptr = z.begin(); // Direct pointer access to z 9 | 10 | for (size_t i = 0; i < ni; ++i) { 11 | size_t start = i * nw; 12 | size_t count = 0; 13 | for (size_t j = 0; j < nw; ++j) { 14 | double val = z_ptr[start + j]; 15 | if (!std::isnan(val)) { 16 | ++count; 17 | } 18 | } 19 | 20 | out[i] = count; 21 | } 22 | 23 | return out; 24 | } 25 | -------------------------------------------------------------------------------- /src/Makevars: -------------------------------------------------------------------------------- 1 | CXX_STD = CXX17 2 | PKG_CXXFLAGS = $(SHLIB_OPENMP_CXXFLAGS) 3 | PKG_LIBS = $(SHLIB_OPENMP_CXXFLAGS) $(LAPACK_LIBS) $(BLAS_LIBS) $(FLIBS) 4 | -------------------------------------------------------------------------------- /src/Makevars.win: -------------------------------------------------------------------------------- 1 | CXX_STD = CXX17 2 | PKG_CXXFLAGS = $(SHLIB_OPENMP_CXXFLAGS) 3 | PKG_LIBS = $(SHLIB_OPENMP_CXXFLAGS) $(LAPACK_LIBS) $(BLAS_LIBS) $(FLIBS) 4 | -------------------------------------------------------------------------------- /src/Qfit.cpp: -------------------------------------------------------------------------------- 1 | #define ARMA_WARN_LEVEL 1 2 | #include 3 | // [[Rcpp::depends(RcppArmadillo)]] 4 | using namespace Rcpp; 5 | using namespace arma; 6 | 7 | 8 | //Fit Wood/Evans Quadratic Surface or Plane with Intercept 9 | 10 | //na.rm=TRUE, force_center=FALSE 11 | // [[Rcpp::export]] 12 | arma::mat C_Qfit1_narmT(const arma::vec& z, 13 | const arma::mat& X_full, 14 | const bool return_intercept, 15 | size_t ni, size_t nw) { 16 | 17 | unsigned int thresh = X_full.n_cols; 18 | 19 | int nlyr = return_intercept ? X_full.n_cols : X_full.n_cols - 1; 20 | 21 | arma::mat out(ni, nlyr, arma::fill::value(NA_REAL)); 22 | 23 | for (size_t i = 0; i < ni; ++i) { 24 | size_t start = i * nw; 25 | arma::vec zw = z.subvec(start, start + nw - 1); 26 | arma::uvec valid_idx = arma::find_finite(zw); 27 | 28 | if (valid_idx.n_elem >= thresh) { 29 | arma::vec zw_clean = zw.elem(valid_idx); 30 | arma::mat X = X_full.rows(valid_idx); 31 | 32 | arma::vec unique_vals = arma::unique(zw_clean); 33 | if (unique_vals.n_elem == 1) { 34 | out.row(i).zeros(); 35 | if (return_intercept) { 36 | out(i, nlyr - 1) = unique_vals[0]; // Last coefficient (e.g., intercept "f") 37 | } 38 | } else { 39 | // Solve using least squares: B = solve(X, Z) 40 | arma::vec coef = arma::solve(X, zw_clean); 41 | if (return_intercept) { 42 | out.row(i) = coef.t(); // Full coefficients (including intercept) 43 | } else { 44 | out.row(i) = coef.subvec(0, coef.n_elem - 2).t(); // Exclude intercept 45 | }} 46 | } 47 | } 48 | return out; 49 | } 50 | 51 | //na.rm=FALSE, force_center=FALSE 52 | // [[Rcpp::export]] 53 | arma::mat C_Qfit1_narmF(const arma::vec& z, 54 | const arma::mat& X, 55 | const arma::mat& Xt, 56 | const arma::mat& XtX_inv, 57 | const bool return_intercept, 58 | size_t ni, size_t nw) { 59 | 60 | int nlyr = return_intercept ? X.n_cols : X.n_cols - 1; 61 | arma::mat out(ni, nlyr, arma::fill::value(NA_REAL)); 62 | 63 | for (size_t i = 0; i < ni; ++i) { 64 | size_t start = i * nw; 65 | arma::vec zw = z.subvec(start, start + nw - 1); 66 | 67 | if (!zw.has_nan()) { 68 | arma::vec unique_vals = arma::unique(zw); 69 | 70 | if (unique_vals.n_elem == 1) { 71 | out.row(i).zeros(); 72 | if (return_intercept) { 73 | out(i, nlyr - 1) = unique_vals[0]; // Last coefficient (e.g., intercept "f") 74 | } 75 | } else { 76 | arma::vec coef = XtX_inv * (Xt * zw); 77 | if (return_intercept) { 78 | out.row(i) = coef.t(); // Full coefficients (including intercept) 79 | } else { 80 | out.row(i) = coef.subvec(0, coef.n_elem - 2).t(); // Exclude intercept 81 | }} 82 | } 83 | } 84 | 85 | return out; 86 | } 87 | 88 | 89 | //Fit Wood/Evans Quadratic Surface or Plane forced through center 90 | 91 | //na.rm=TRUE, force_center=TRUE 92 | // [[Rcpp::export]] 93 | arma::mat C_Qfit2_narmT(const arma::vec& z, 94 | const arma::mat& X_full, 95 | size_t ni, size_t nw) { 96 | 97 | int nlyr = X_full.n_cols; 98 | arma::mat out(ni, nlyr, arma::fill::value(NA_REAL)); 99 | 100 | unsigned int thresh = nlyr; //Setting to nlayer makes it work for planar fit too 101 | 102 | for (size_t i = 0; i < ni; ++i) { 103 | size_t start = i * nw; 104 | arma::vec zw = z.subvec(start, start + nw - 1); 105 | 106 | // Centering the window values 107 | double center_val = zw(nw / 2); 108 | zw -= center_val; 109 | 110 | arma::uvec valid_idx = arma::find_finite(zw); 111 | if (valid_idx.n_elem >= thresh) { 112 | arma::vec zw_clean = zw.elem(valid_idx); 113 | arma::mat X = X_full.rows(valid_idx); 114 | 115 | arma::vec unique_vals = arma::unique(zw_clean); 116 | if (unique_vals.n_elem == 1) { 117 | out.row(i).zeros(); 118 | } else { 119 | // Least squares solution 120 | arma::vec coef = arma::solve(X, zw_clean); 121 | out.row(i) = coef.t(); // row vector 122 | } 123 | } 124 | } 125 | 126 | return out; 127 | } 128 | 129 | //na.rm=FALSE, force_center=TRUE 130 | // [[Rcpp::export]] 131 | arma::mat C_Qfit2_narmF(const arma::vec& z, 132 | const arma::mat& X, 133 | const arma::mat& Xt, 134 | const arma::mat& XtX_inv, 135 | size_t ni, size_t nw) { 136 | 137 | int nlyr = X.n_cols; 138 | arma::mat out(ni, nlyr, arma::fill::value(NA_REAL)); 139 | 140 | for (size_t i = 0; i < ni; ++i) { 141 | size_t start = i * nw; 142 | arma::vec Z = z.subvec(start, start + nw - 1); 143 | 144 | // Center Z using middle value 145 | double center_val = Z(Z.n_elem / 2); 146 | Z -= center_val; 147 | 148 | if (!Z.has_nan()) { 149 | arma::vec unique_vals = arma::unique(Z); 150 | 151 | if (unique_vals.n_elem == 1) { 152 | out.row(i).zeros(); // All coefficients = 0 153 | } else { 154 | // OLS coefficients using precomputed XtX_inv and Xt 155 | arma::vec coef = XtX_inv * (Xt * Z); 156 | out.row(i) = coef.t(); 157 | } 158 | } 159 | } 160 | 161 | return out; 162 | } 163 | 164 | 165 | //Planar fit (Not needed. Generalized Qfit) 166 | 167 | //na.rm=TRUE, force_center=FALSE 168 | // arma::mat C_Pfit1_narmT(const arma::vec& z, 169 | // const arma::mat& X_full, 170 | // size_t ni, size_t nw) { 171 | // 172 | // int nlyr = X_full.n_cols-1; 173 | // arma::mat out(ni, nlyr, arma::fill::value(NA_REAL)); 174 | // 175 | // unsigned int thresh = 3; 176 | // 177 | // for (size_t i = 0; i < ni; ++i) { 178 | // size_t start = i * nw; 179 | // arma::vec zw = z.subvec(start, start + nw - 1); 180 | // arma::uvec valid_idx = arma::find_finite(zw); 181 | // 182 | // if (valid_idx.n_elem >= thresh) { 183 | // arma::vec zw_clean = zw.elem(valid_idx); 184 | // arma::mat X = X_full.rows(valid_idx); 185 | // 186 | // arma::vec unique_vals = arma::unique(zw_clean); 187 | // if (unique_vals.n_elem == 1) { 188 | // out.row(i).zeros(); 189 | // } else { 190 | // // Solve using least squares: B = solve(X, Z) 191 | // arma::vec coef = arma::solve(X, zw_clean); 192 | // out.row(i) = coef.subvec(0, coef.n_elem - 2).t(); // Transpose to write as row 193 | // } 194 | // } 195 | // } 196 | // 197 | // return out; 198 | // } 199 | 200 | //na.rm=FALSE, force_center=FALSE (Not Used) 201 | // arma::mat C_Pfit1_narmF(const arma::vec& z, 202 | // const arma::mat& X, 203 | // const arma::mat& Xt, 204 | // const arma::mat& XtX_inv, 205 | // size_t ni, size_t nw) { 206 | // 207 | // int nlyr = X.n_cols - 1; 208 | // arma::mat out(ni, nlyr, arma::fill::value(NA_REAL)); 209 | // 210 | // for (size_t i = 0; i < ni; ++i) { 211 | // size_t start = i * nw; 212 | // arma::vec zw = z.subvec(start, start + nw - 1); 213 | // 214 | // if (!zw.has_nan()) { 215 | // arma::vec unique_vals = arma::unique(zw); 216 | // 217 | // if (unique_vals.n_elem == 1) { 218 | // out.row(i).zeros(); 219 | // } else { 220 | // arma::vec coef = XtX_inv * (Xt * zw); 221 | // out.row(i) = coef.subvec(0, coef.n_elem - 2).t(); // Transpose to write as row 222 | // } 223 | // } 224 | // } 225 | // return out; 226 | // } 227 | 228 | -------------------------------------------------------------------------------- /src/RcppExports.cpp: -------------------------------------------------------------------------------- 1 | // Generated by using Rcpp::compileAttributes() -> do not edit by hand 2 | // Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 3 | 4 | #include 5 | #include 6 | 7 | using namespace Rcpp; 8 | 9 | #ifdef RCPP_USE_GLOBAL_ROSTREAM 10 | Rcpp::Rostream& Rcpp::Rcout = Rcpp::Rcpp_cout_get(); 11 | Rcpp::Rostream& Rcpp::Rcerr = Rcpp::Rcpp_cerr_get(); 12 | #endif 13 | 14 | // C_AdjSD_narmT 15 | arma::vec C_AdjSD_narmT(const arma::vec& z, const arma::mat& X_full, size_t ni, size_t nw); 16 | RcppExport SEXP _MultiscaleDTM_C_AdjSD_narmT(SEXP zSEXP, SEXP X_fullSEXP, SEXP niSEXP, SEXP nwSEXP) { 17 | BEGIN_RCPP 18 | Rcpp::RObject rcpp_result_gen; 19 | Rcpp::RNGScope rcpp_rngScope_gen; 20 | Rcpp::traits::input_parameter< const arma::vec& >::type z(zSEXP); 21 | Rcpp::traits::input_parameter< const arma::mat& >::type X_full(X_fullSEXP); 22 | Rcpp::traits::input_parameter< size_t >::type ni(niSEXP); 23 | Rcpp::traits::input_parameter< size_t >::type nw(nwSEXP); 24 | rcpp_result_gen = Rcpp::wrap(C_AdjSD_narmT(z, X_full, ni, nw)); 25 | return rcpp_result_gen; 26 | END_RCPP 27 | } 28 | // C_AdjSD_narmF 29 | arma::vec C_AdjSD_narmF(const arma::vec& z, const arma::mat& X, const arma::mat& Xt, const arma::mat& XtX_inv, size_t ni, size_t nw); 30 | RcppExport SEXP _MultiscaleDTM_C_AdjSD_narmF(SEXP zSEXP, SEXP XSEXP, SEXP XtSEXP, SEXP XtX_invSEXP, SEXP niSEXP, SEXP nwSEXP) { 31 | BEGIN_RCPP 32 | Rcpp::RObject rcpp_result_gen; 33 | Rcpp::RNGScope rcpp_rngScope_gen; 34 | Rcpp::traits::input_parameter< const arma::vec& >::type z(zSEXP); 35 | Rcpp::traits::input_parameter< const arma::mat& >::type X(XSEXP); 36 | Rcpp::traits::input_parameter< const arma::mat& >::type Xt(XtSEXP); 37 | Rcpp::traits::input_parameter< const arma::mat& >::type XtX_inv(XtX_invSEXP); 38 | Rcpp::traits::input_parameter< size_t >::type ni(niSEXP); 39 | Rcpp::traits::input_parameter< size_t >::type nw(nwSEXP); 40 | rcpp_result_gen = Rcpp::wrap(C_AdjSD_narmF(z, X, Xt, XtX_inv, ni, nw)); 41 | return rcpp_result_gen; 42 | END_RCPP 43 | } 44 | // C_CountVals 45 | NumericVector C_CountVals(const NumericVector& z, size_t ni, size_t nw); 46 | RcppExport SEXP _MultiscaleDTM_C_CountVals(SEXP zSEXP, SEXP niSEXP, SEXP nwSEXP) { 47 | BEGIN_RCPP 48 | Rcpp::RObject rcpp_result_gen; 49 | Rcpp::RNGScope rcpp_rngScope_gen; 50 | Rcpp::traits::input_parameter< const NumericVector& >::type z(zSEXP); 51 | Rcpp::traits::input_parameter< size_t >::type ni(niSEXP); 52 | Rcpp::traits::input_parameter< size_t >::type nw(nwSEXP); 53 | rcpp_result_gen = Rcpp::wrap(C_CountVals(z, ni, nw)); 54 | return rcpp_result_gen; 55 | END_RCPP 56 | } 57 | // C_Qfit1_narmT 58 | arma::mat C_Qfit1_narmT(const arma::vec& z, const arma::mat& X_full, const bool return_intercept, size_t ni, size_t nw); 59 | RcppExport SEXP _MultiscaleDTM_C_Qfit1_narmT(SEXP zSEXP, SEXP X_fullSEXP, SEXP return_interceptSEXP, SEXP niSEXP, SEXP nwSEXP) { 60 | BEGIN_RCPP 61 | Rcpp::RObject rcpp_result_gen; 62 | Rcpp::RNGScope rcpp_rngScope_gen; 63 | Rcpp::traits::input_parameter< const arma::vec& >::type z(zSEXP); 64 | Rcpp::traits::input_parameter< const arma::mat& >::type X_full(X_fullSEXP); 65 | Rcpp::traits::input_parameter< const bool >::type return_intercept(return_interceptSEXP); 66 | Rcpp::traits::input_parameter< size_t >::type ni(niSEXP); 67 | Rcpp::traits::input_parameter< size_t >::type nw(nwSEXP); 68 | rcpp_result_gen = Rcpp::wrap(C_Qfit1_narmT(z, X_full, return_intercept, ni, nw)); 69 | return rcpp_result_gen; 70 | END_RCPP 71 | } 72 | // C_Qfit1_narmF 73 | arma::mat C_Qfit1_narmF(const arma::vec& z, const arma::mat& X, const arma::mat& Xt, const arma::mat& XtX_inv, const bool return_intercept, size_t ni, size_t nw); 74 | RcppExport SEXP _MultiscaleDTM_C_Qfit1_narmF(SEXP zSEXP, SEXP XSEXP, SEXP XtSEXP, SEXP XtX_invSEXP, SEXP return_interceptSEXP, SEXP niSEXP, SEXP nwSEXP) { 75 | BEGIN_RCPP 76 | Rcpp::RObject rcpp_result_gen; 77 | Rcpp::RNGScope rcpp_rngScope_gen; 78 | Rcpp::traits::input_parameter< const arma::vec& >::type z(zSEXP); 79 | Rcpp::traits::input_parameter< const arma::mat& >::type X(XSEXP); 80 | Rcpp::traits::input_parameter< const arma::mat& >::type Xt(XtSEXP); 81 | Rcpp::traits::input_parameter< const arma::mat& >::type XtX_inv(XtX_invSEXP); 82 | Rcpp::traits::input_parameter< const bool >::type return_intercept(return_interceptSEXP); 83 | Rcpp::traits::input_parameter< size_t >::type ni(niSEXP); 84 | Rcpp::traits::input_parameter< size_t >::type nw(nwSEXP); 85 | rcpp_result_gen = Rcpp::wrap(C_Qfit1_narmF(z, X, Xt, XtX_inv, return_intercept, ni, nw)); 86 | return rcpp_result_gen; 87 | END_RCPP 88 | } 89 | // C_Qfit2_narmT 90 | arma::mat C_Qfit2_narmT(const arma::vec& z, const arma::mat& X_full, size_t ni, size_t nw); 91 | RcppExport SEXP _MultiscaleDTM_C_Qfit2_narmT(SEXP zSEXP, SEXP X_fullSEXP, SEXP niSEXP, SEXP nwSEXP) { 92 | BEGIN_RCPP 93 | Rcpp::RObject rcpp_result_gen; 94 | Rcpp::RNGScope rcpp_rngScope_gen; 95 | Rcpp::traits::input_parameter< const arma::vec& >::type z(zSEXP); 96 | Rcpp::traits::input_parameter< const arma::mat& >::type X_full(X_fullSEXP); 97 | Rcpp::traits::input_parameter< size_t >::type ni(niSEXP); 98 | Rcpp::traits::input_parameter< size_t >::type nw(nwSEXP); 99 | rcpp_result_gen = Rcpp::wrap(C_Qfit2_narmT(z, X_full, ni, nw)); 100 | return rcpp_result_gen; 101 | END_RCPP 102 | } 103 | // C_Qfit2_narmF 104 | arma::mat C_Qfit2_narmF(const arma::vec& z, const arma::mat& X, const arma::mat& Xt, const arma::mat& XtX_inv, size_t ni, size_t nw); 105 | RcppExport SEXP _MultiscaleDTM_C_Qfit2_narmF(SEXP zSEXP, SEXP XSEXP, SEXP XtSEXP, SEXP XtX_invSEXP, SEXP niSEXP, SEXP nwSEXP) { 106 | BEGIN_RCPP 107 | Rcpp::RObject rcpp_result_gen; 108 | Rcpp::RNGScope rcpp_rngScope_gen; 109 | Rcpp::traits::input_parameter< const arma::vec& >::type z(zSEXP); 110 | Rcpp::traits::input_parameter< const arma::mat& >::type X(XSEXP); 111 | Rcpp::traits::input_parameter< const arma::mat& >::type Xt(XtSEXP); 112 | Rcpp::traits::input_parameter< const arma::mat& >::type XtX_inv(XtX_invSEXP); 113 | Rcpp::traits::input_parameter< size_t >::type ni(niSEXP); 114 | Rcpp::traits::input_parameter< size_t >::type nw(nwSEXP); 115 | rcpp_result_gen = Rcpp::wrap(C_Qfit2_narmF(z, X, Xt, XtX_inv, ni, nw)); 116 | return rcpp_result_gen; 117 | END_RCPP 118 | } 119 | // C_TriArea 120 | double C_TriArea(const double a, const double b, const double c); 121 | RcppExport SEXP _MultiscaleDTM_C_TriArea(SEXP aSEXP, SEXP bSEXP, SEXP cSEXP) { 122 | BEGIN_RCPP 123 | Rcpp::RObject rcpp_result_gen; 124 | Rcpp::RNGScope rcpp_rngScope_gen; 125 | Rcpp::traits::input_parameter< const double >::type a(aSEXP); 126 | Rcpp::traits::input_parameter< const double >::type b(bSEXP); 127 | Rcpp::traits::input_parameter< const double >::type c(cSEXP); 128 | rcpp_result_gen = Rcpp::wrap(C_TriArea(a, b, c)); 129 | return rcpp_result_gen; 130 | END_RCPP 131 | } 132 | // C_SurfaceArea 133 | NumericVector C_SurfaceArea(const NumericVector& z, const double x_res, const double y_res, const bool na_rm, size_t ni, size_t nw); 134 | RcppExport SEXP _MultiscaleDTM_C_SurfaceArea(SEXP zSEXP, SEXP x_resSEXP, SEXP y_resSEXP, SEXP na_rmSEXP, SEXP niSEXP, SEXP nwSEXP) { 135 | BEGIN_RCPP 136 | Rcpp::RObject rcpp_result_gen; 137 | Rcpp::RNGScope rcpp_rngScope_gen; 138 | Rcpp::traits::input_parameter< const NumericVector& >::type z(zSEXP); 139 | Rcpp::traits::input_parameter< const double >::type x_res(x_resSEXP); 140 | Rcpp::traits::input_parameter< const double >::type y_res(y_resSEXP); 141 | Rcpp::traits::input_parameter< const bool >::type na_rm(na_rmSEXP); 142 | Rcpp::traits::input_parameter< size_t >::type ni(niSEXP); 143 | Rcpp::traits::input_parameter< size_t >::type nw(nwSEXP); 144 | rcpp_result_gen = Rcpp::wrap(C_SurfaceArea(z, x_res, y_res, na_rm, ni, nw)); 145 | return rcpp_result_gen; 146 | END_RCPP 147 | } 148 | 149 | static const R_CallMethodDef CallEntries[] = { 150 | {"_MultiscaleDTM_C_AdjSD_narmT", (DL_FUNC) &_MultiscaleDTM_C_AdjSD_narmT, 4}, 151 | {"_MultiscaleDTM_C_AdjSD_narmF", (DL_FUNC) &_MultiscaleDTM_C_AdjSD_narmF, 6}, 152 | {"_MultiscaleDTM_C_CountVals", (DL_FUNC) &_MultiscaleDTM_C_CountVals, 3}, 153 | {"_MultiscaleDTM_C_Qfit1_narmT", (DL_FUNC) &_MultiscaleDTM_C_Qfit1_narmT, 5}, 154 | {"_MultiscaleDTM_C_Qfit1_narmF", (DL_FUNC) &_MultiscaleDTM_C_Qfit1_narmF, 7}, 155 | {"_MultiscaleDTM_C_Qfit2_narmT", (DL_FUNC) &_MultiscaleDTM_C_Qfit2_narmT, 4}, 156 | {"_MultiscaleDTM_C_Qfit2_narmF", (DL_FUNC) &_MultiscaleDTM_C_Qfit2_narmF, 6}, 157 | {"_MultiscaleDTM_C_TriArea", (DL_FUNC) &_MultiscaleDTM_C_TriArea, 3}, 158 | {"_MultiscaleDTM_C_SurfaceArea", (DL_FUNC) &_MultiscaleDTM_C_SurfaceArea, 6}, 159 | {NULL, NULL, 0} 160 | }; 161 | 162 | RcppExport void R_init_MultiscaleDTM(DllInfo *dll) { 163 | R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); 164 | R_useDynamicSymbols(dll, FALSE); 165 | } 166 | -------------------------------------------------------------------------------- /src/SurfaceArea.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace Rcpp; 3 | 4 | 5 | // Calculate area of triangle based on side lengths 6 | // [[Rcpp::export]] 7 | double C_TriArea (const double a, 8 | const double b, 9 | const double c){ 10 | double s = (a+b+c)/2; 11 | double out =sqrt(s*(s-a)*(s-b)*(s-c)); 12 | return out; 13 | } 14 | 15 | // [[Rcpp::export]] 16 | NumericVector C_SurfaceArea(const NumericVector& z, 17 | const double x_res, 18 | const double y_res, 19 | const bool na_rm, 20 | size_t ni, size_t nw) { 21 | NumericVector out(ni, NA_REAL); 22 | 23 | double Lx2 = x_res * x_res; 24 | double Ly2 = y_res * y_res; 25 | double Ld2 = Lx2 + Ly2; 26 | 27 | const double* z_ptr = z.begin(); 28 | 29 | for (size_t i = 0; i < ni; ++i) { 30 | const double* zw = z_ptr + i * nw; 31 | 32 | // Fast access to elevation values 33 | double A = zw[0], B = zw[1], C = zw[2], D = zw[3], E = zw[4], 34 | F = zw[5], G = zw[6], H = zw[7], I = zw[8]; 35 | //|A|B|C| 36 | //|D|E|F| 37 | //|G|H|I| 38 | 39 | // Horizontal 40 | double AB = sqrt(Lx2 + pow(A - B, 2)) / 2; 41 | double BC = sqrt(Lx2 + pow(B - C, 2)) / 2; 42 | double DE = sqrt(Lx2 + pow(D - E, 2)) / 2; 43 | double EF = sqrt(Lx2 + pow(E - F, 2)) / 2; 44 | double GH = sqrt(Lx2 + pow(G - H, 2)) / 2; 45 | double HI = sqrt(Lx2 + pow(H - I, 2)) / 2; 46 | // Vertical 47 | double AD = sqrt(Ly2 + pow(A - D, 2)) / 2; 48 | double BE = sqrt(Ly2 + pow(B - E, 2)) / 2; 49 | double CF = sqrt(Ly2 + pow(C - F, 2)) / 2; 50 | double DG = sqrt(Ly2 + pow(D - G, 2)) / 2; 51 | double EH = sqrt(Ly2 + pow(E - H, 2)) / 2; 52 | double FI = sqrt(Ly2 + pow(F - I, 2)) / 2; 53 | 54 | // Diagonal 55 | double EA = sqrt(Ld2 + pow(E - A, 2)) / 2; 56 | double EC = sqrt(Ld2 + pow(E - C, 2)) / 2; 57 | double EG = sqrt(Ld2 + pow(E - G, 2)) / 2; 58 | double EI = sqrt(Ld2 + pow(E - I, 2)) / 2; 59 | 60 | // Triangles 61 | double tri[8] = { 62 | C_TriArea(EA, AB, BE), 63 | C_TriArea(BE, BC, EC), 64 | C_TriArea(AD, DE, EA), 65 | C_TriArea(EC, CF, EF), 66 | C_TriArea(DE, DG, EG), 67 | C_TriArea(EF, FI, EI), 68 | C_TriArea(EG, EH, GH), 69 | C_TriArea(EH, EI, HI) 70 | }; 71 | 72 | if (na_rm) { 73 | double sum = 0; 74 | int count = 0; 75 | for (int t = 0; t < 8; ++t) { 76 | if (!std::isnan(tri[t])) { 77 | sum += tri[t]; 78 | count++; 79 | } 80 | } 81 | if (count > 0) out[i] = (sum / count) * 8; 82 | } else { 83 | bool has_na = false; 84 | for (int t = 0; t < 8; ++t) { 85 | if (std::isnan(tri[t])) { 86 | has_na = true; 87 | break; 88 | } 89 | } 90 | if (!has_na) { 91 | out[i] = tri[0] + tri[1] + tri[2] + tri[3] + tri[4] + tri[5] + tri[6] + tri[7]; 92 | } 93 | } 94 | } 95 | 96 | return out; 97 | } -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | # This file is part of the standard setup for testthat. 2 | # It is recommended that you do not modify it. 3 | # 4 | # Where should you do additional test configuration? 5 | # Learn more about the roles of various files in: 6 | # * https://r-pkgs.org/testing-design.html#sec-tests-files-overview 7 | # * https://testthat.r-lib.org/articles/special-files.html 8 | 9 | library(testthat) 10 | library(MultiscaleDTM) 11 | 12 | test_check("MultiscaleDTM") 13 | -------------------------------------------------------------------------------- /tests/testthat/test-functions.R: -------------------------------------------------------------------------------- 1 | r<- erupt() 2 | set.seed(5) 3 | r[sample(1:ncell(r), size = 500, replace = FALSE)]<- NA 4 | 5 | # groundhog.library("MultiscaleDTM", date = "2024-7-1") # Surfaces except for SAPA generated with this 6 | 7 | 8 | test_that("Test SlpAsp_queen", { 9 | test<- SlpAsp(r = r, w = c(5,7), unit = "degrees", method = "queen", metrics = c("slope", "aspect", "eastness", "northness"), na.rm=TRUE) |> values(mat=TRUE) 10 | expect<- readRDS(system.file("testdata", "slp_asp_queen.RDS", package= "MultiscaleDTM")) 11 | expect_equal(test, expect) 12 | }) 13 | 14 | test_that("Test SlpAsp_rook", { 15 | test<- SlpAsp(r = r, w = c(5,7), unit = "degrees", method = "queen", metrics = c("slope", "aspect", "eastness", "northness"), na.rm=TRUE) |> values(mat=TRUE) 16 | expect<- readRDS(system.file("testdata", "slp_asp_rook.RDS", package= "MultiscaleDTM")) 17 | expect_equal(test, expect) 18 | }) 19 | 20 | test_that("Test Qfit", { 21 | test<- Qfit(r, w = c(5,7), unit = "degrees", metrics = c("elev", "qslope", "qaspect", "qeastness", "qnorthness", "profc", "planc", "twistc", "meanc", "maxc", "minc", "features"), na.rm = TRUE) |> values(mat=TRUE) 22 | expect<- readRDS(system.file("testdata", "qmetrics.RDS", package= "MultiscaleDTM")) 23 | expect_equal(test, expect) 24 | }) 25 | 26 | test_that("Test equivalence of Pfit and Qfit", { 27 | test<- Pfit(r, w = c(5,7), unit = "degrees", metrics = c("pslope", "paspect", "peastness", "pnorthness"), na.rm = FALSE) |> values(mat=TRUE) |> `dimnames<-`(NULL) 28 | expect<- Qfit(r, w = c(5,7), unit = "degrees", metrics = c("qslope", "qaspect", "qeastness", "qnorthness"), na.rm = FALSE, outlier_quantile = c(0,1)) |> values(mat=TRUE) |> `dimnames<-`(NULL) 29 | expect_equal(test, expect) 30 | }) 31 | 32 | test_that("Test VRM", { 33 | test<- VRM(r, w=c(5,7), na.rm = TRUE) |> values(mat=TRUE) 34 | expect<- readRDS(system.file("testdata", "vrm.RDS", package= "MultiscaleDTM")) 35 | expect_equal(test, expect) 36 | }) 37 | 38 | 39 | test_that("Test SAPA", { 40 | test<- SAPA(r, w=c(5,7), slope_correction = FALSE, na.rm=TRUE) |> values(mat=TRUE) 41 | expect<- readRDS(system.file("testdata", "sapa.RDS", package= "MultiscaleDTM")) 42 | expect_equal(test, expect) 43 | }) 44 | 45 | test_that("Test AdjSD", { 46 | test<- AdjSD(r, w=c(5,7), na.rm = TRUE) |> values(mat=TRUE) 47 | expect<- readRDS(system.file("testdata", "adj_sd.RDS", package= "MultiscaleDTM")) 48 | expect_equal(test,expect) 49 | }) 50 | 51 | test_that("Test RIE", { 52 | test<- RIE(r, w=c(5,7), na.rm = TRUE) |> values(mat=TRUE) 53 | expect<- readRDS(system.file("testdata", "rie.RDS", package= "MultiscaleDTM")) 54 | expect_equal(test, expect) 55 | }) 56 | 57 | test_that("Test RelPos", { 58 | test<- RelPos(r, w=matrix(data = c(1,NA,1), nrow = 3, ncol=3), shape = "custom", fun = "median", na.rm = TRUE) |> values(mat=TRUE) 59 | expect<- readRDS(system.file("testdata", "rp.RDS", package= "MultiscaleDTM")) 60 | expect_equal(test, expect) 61 | }) 62 | 63 | test_that("Test TPI", { 64 | test<- TPI(r, w=c(5,5), shape= "rectangle", na.rm = TRUE) |> values(mat=TRUE) 65 | expect<- readRDS(system.file("testdata", "tpi.RDS", package= "MultiscaleDTM")) 66 | expect_equal(test, expect) 67 | }) 68 | 69 | test_that("Test DMV", { 70 | test<- DMV(r, w=2, shape= "circle", na.rm = TRUE, stand="range") |> values(mat=TRUE) 71 | expect<- readRDS(system.file("testdata", "dmv.RDS", package= "MultiscaleDTM")) 72 | expect_equal(test, expect) 73 | }) 74 | 75 | test_that("Test BPI", { 76 | test<- BPI(r, w = c(4,6), unit = "cell", stand= "sd", na.rm = TRUE) |> values(mat=TRUE) 77 | expect<- readRDS(system.file("testdata", "bpi.RDS", package= "MultiscaleDTM")) 78 | expect_equal(test, expect) 79 | }) 80 | 81 | -------------------------------------------------------------------------------- /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.R 3 | -------------------------------------------------------------------------------- /vignettes/README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "README" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{README} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | --- 9 | 10 | ```{r, include = FALSE} 11 | knitr::opts_chunk$set( 12 | collapse = TRUE, 13 | comment = "#>", 14 | echo = TRUE, warning = FALSE, message = FALSE) 15 | ``` 16 | 17 | ```{r setup, include=FALSE} 18 | md_fig_dir<- "../man/figures/" #Path relative to this Rmd 19 | R_fig_dir<- "../figures/" #Path relative to child Rmd 20 | ``` 21 | 22 | ```{r child='../man/fragments/README_Frag.Rmd'} 23 | ``` 24 | --------------------------------------------------------------------------------